update README
[rofl0r-rocksock.git] / rocksockserver.c
blob6a40dc3fba6ab68e85d9b61520d40e8c638913fe
1 /*
3 * author: rofl0r
5 * License: LGPL 2.1+ with static linking exception
8 */
10 #include <string.h>
11 #include <netdb.h>
12 #include <unistd.h>
13 #include <stddef.h>
14 #include <sys/socket.h>
15 #include <sys/select.h>
16 #include <netinet/in.h>
17 #include <time.h>
18 #include <errno.h>
19 #include <limits.h>
20 #include <stdlib.h>
22 #include "rocksockserver.h"
24 #include "endianness.h"
26 #ifndef NO_LOG
27 #include "../lib/include/logger.h"
28 #endif
29 #include "../lib/include/strlib.h"
30 #include "../lib/include/stringptr.h"
31 #include "../lib/include/timelib.h"
33 typedef struct {
34 char* host;
35 unsigned short port;
36 #ifndef IPV4_ONLY
37 struct addrinfo* hostaddr;
38 #else
39 struct sockaddr_in hostaddr;
40 #endif
41 } rs_hostInfo;
43 int rocksockserver_resolve_host(rs_hostInfo* hostinfo) {
44 if (!hostinfo || !hostinfo->host || !hostinfo->port) return -1;
45 #ifndef IPV4_ONLY
46 char pbuf[8];
47 char* ports;
48 int ret;
49 struct addrinfo hints;
51 memset(&hints, 0, sizeof(hints));
52 hints.ai_family = AF_UNSPEC;
53 hints.ai_socktype = SOCK_STREAM;
54 hints.ai_flags = AI_PASSIVE;
55 if(!(ports = intToString(hostinfo->port, pbuf))) return -1;
57 ret = getaddrinfo(hostinfo->host, ports, &hints, &hostinfo->hostaddr);
58 if(!ret) {
59 return 0;
60 } else {
61 #ifndef NO_LOG
62 log_put(1, VARISL("error resolving: "), VARICC(gai_strerror(ret)), NULL);
63 #endif
64 return ret;
66 #else
67 memset(&hostinfo->hostaddr, 0, sizeof(struct sockaddr_in));
68 ipv4fromstring(hostinfo->host, (unsigned char*) &hostinfo->hostaddr.sin_addr);
69 hostinfo->hostaddr.sin_family = AF_INET;
70 hostinfo->hostaddr.sin_port = htons(hostinfo->port);
71 return 0;
72 #endif
75 int rocksockserver_init(rocksockserver* srv, char* listenip, unsigned short port, void* userdata) {
76 int ret = 0;
77 int yes = 1;
78 rs_hostInfo conn;
79 if(!srv || !listenip || !port) return -1;
80 conn.host = listenip;
81 conn.port = port;
82 FD_ZERO(&srv->master);
83 srv->userdata = userdata;
84 srv->sleeptime_us = 20000; // set a reasonable default value. it's a compromise between throughput and cpu usage basically.
85 ret = rocksockserver_resolve_host(&conn);
86 if(ret) return ret;
87 #ifndef IPV4_ONLY
88 struct addrinfo* p;
89 for(p = conn.hostaddr; p != NULL; p = p->ai_next) {
90 srv->listensocket = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
91 if (srv->listensocket < 0) {
92 continue;
95 // lose the pesky "address already in use" error message
96 setsockopt(srv->listensocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
98 if (bind(srv->listensocket, p->ai_addr, p->ai_addrlen) < 0) {
99 close(srv->listensocket);
100 continue;
103 break;
105 if (!p) {
106 # ifndef NO_LOG
107 log_puts(1, SPLITERAL("selectserver: failed to bind\n"));
108 # endif
109 ret = -1;
111 freeaddrinfo(conn.hostaddr);
112 if(ret == -1) return -1;
113 #else
114 srv->listensocket = socket(AF_INET, SOCK_STREAM, 0);
115 if(srv->listensocket < 0) {
116 # ifndef NO_LOG
117 log_perror("socket");
118 # endif
119 return -1;
121 setsockopt(srv->listensocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
122 if(bind(srv->listensocket, (struct sockaddr*) &conn.hostaddr, sizeof(struct sockaddr_in)) < 0) {
123 close(srv->listensocket);
124 # ifndef NO_LOG
125 log_perror("bind");
126 # endif
127 return -1;
130 #endif
131 // listen
132 if (listen(srv->listensocket, 10) == -1) {
133 #ifndef NO_LOG
134 log_perror("listen");
135 #endif
136 ret = -2;
137 } else {
138 FD_SET(srv->listensocket, &srv->master);
139 srv->maxfd = srv->listensocket;
141 return ret;
144 int rocksockserver_disconnect_client(rocksockserver* srv, int client) {
145 if(client < 0 || client > USER_MAX_FD) return -1;
146 if(FD_ISSET(client, &srv->master)) {
147 close(client);
148 FD_CLR(client, &srv->master);
149 if(client == srv->maxfd)
150 srv->maxfd--;
151 srv->numfds--;
152 return 0;
154 return 1;
157 void rocksockserver_watch_fd(rocksockserver* srv, int newfd) {
158 FD_SET(newfd, &srv->master);
159 if (newfd > srv->maxfd)
160 srv->maxfd = newfd;
163 int rocksockserver_loop(rocksockserver* srv,
164 char* buf, size_t bufsize,
165 int (*on_clientconnect) (void* userdata, struct sockaddr_storage* clientaddr, int fd),
166 int (*on_clientread) (void* userdata, int fd, size_t nread),
167 int (*on_clientwantsdata) (void* userdata, int fd),
168 int (*on_clientdisconnect) (void* userdata, int fd)
170 fd_set read_fds, write_fds;
171 int newfd, k;
172 int lastfd = 3;
173 #ifdef IS_LITTLE_ENDIAN
174 int i;
175 size_t j;
176 #endif
177 ptrdiff_t nbytes;
178 struct sockaddr_storage remoteaddr; // client address
179 socklen_t addrlen;
180 char* fdptr;
181 fd_set* setptr;
183 for(;;) {
185 read_fds = srv->master;
186 write_fds = srv->master;
188 if ((srv->numfds = select(srv->maxfd+1, &read_fds, &write_fds, NULL, NULL)) && srv->numfds == -1)
189 #ifndef NO_LOG
190 log_perror("select");
191 #else
193 #endif
195 if(!srv->numfds) continue;
197 // optimization for the case searched_fd = lastfd, when we only have to handle one connection.
198 // i guess that should be the majority of cases.
199 k = lastfd;
200 setptr = &write_fds;
201 if(FD_ISSET(k, setptr)) goto gotcha;
202 setptr = &read_fds;
203 if(FD_ISSET(k, setptr)) goto gotcha;
205 nextfd:
206 setptr = &write_fds;
207 loopstart:
208 fdptr = (char*) setptr;
209 #ifdef IS_LITTLE_ENDIAN
210 for(i = 0; i * CHAR_BIT <= srv->maxfd; i+= sizeof(size_t)) { // we assume that sizeof(fd_set) is a multiple of sizeof(size_t)
211 if( *(size_t*)(fdptr + i)) {
212 for(j = 0; j <= sizeof(size_t); j++) {
213 if(fdptr[i + j]) {
214 for(k = (i + j) * CHAR_BIT; k <= srv->maxfd; k++) {
215 #else
216 for(k = 0; k <= srv->maxfd; k++) {
217 #endif
218 if(FD_ISSET(k, setptr)) {
219 gotcha:
220 srv->numfds--;
221 FD_CLR(k, setptr);
222 if(setptr == &write_fds)
223 goto handlewrite;
224 else
225 goto handleread;
228 #ifdef IS_LITTLE_ENDIAN
234 #endif
236 if(setptr == &write_fds) {
237 setptr = &read_fds;
238 goto loopstart;
239 } else {
240 #ifndef NO_LOG
241 log_puts(2, SPLITERAL("FATAL"));
242 #endif
244 printf("maxfd %d, k %d, numfds %d, set %d\n", srv->maxfd, k, srv->numfds, *(int*)(fdptr));
245 for(k = 0; k < USER_MAX_FD; k++)
246 if(FD_ISSET(k, setptr))
247 printf("bit set: %d\n", k);
249 #ifndef NO_ABORT
250 abort();
251 #else
252 exit(111);
253 #endif
256 handleread:
257 //printf("read_fd %d\n", k);
258 if (k == srv->listensocket) {
259 // new connection available
260 addrlen = sizeof(remoteaddr);
261 newfd = accept(srv->listensocket, (struct sockaddr *)&remoteaddr, &addrlen);
263 if (newfd == -1) {
264 #ifndef NO_LOG
265 log_perror("accept");
266 #endif
267 } else {
268 if(newfd >= USER_MAX_FD)
269 close(newfd); // only USER_MAX_FD connections can be handled.
270 else {
271 FD_SET(newfd, &srv->master);
272 if (newfd > srv->maxfd)
273 srv->maxfd = newfd;
274 if(on_clientconnect) on_clientconnect(srv->userdata, &remoteaddr, newfd);
277 } else {
278 if(buf && k != srv->signalfd) {
279 if ((nbytes = recv(k, buf, bufsize, 0)) <= 0) {
280 if (nbytes == 0) {
281 if(on_clientdisconnect) on_clientdisconnect(srv->userdata, k);
282 } else {
283 #ifndef NO_LOG
284 log_perror("recv");
285 #endif
287 rocksockserver_disconnect_client(srv, k);
288 } else {
289 if(on_clientread) on_clientread(srv->userdata, k, nbytes);
291 } else {
293 if(on_clientread) on_clientread(srv->userdata, k, 0);
296 goto zzz;
298 handlewrite:
300 //printf("write_fd %d\n", k);
301 if(on_clientwantsdata) on_clientwantsdata(srv->userdata, k);
303 zzz:
304 if(srv->numfds > 0) goto nextfd;
305 lastfd = k;
306 microsleep(srv->sleeptime_us);
308 return 0;