micclient: fix typo in comment
[rofl0r-rocksock.git] / rocksockserver.c
blobe3a763f4229a251c34ee7a6dd7bf560d43eafc07
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 #define LOGP(X) do { if(srv->perr) srv->perr(X); } while(0)
28 #ifdef USE_LIBULZ
29 #include "../lib/include/strlib.h"
30 #include "../lib/include/timelib.h"
31 #else
32 #include <stdio.h>
33 #include <arpa/inet.h>
34 #define microsleep(X) usleep(X)
35 static inline char* my_intToString(int i, char *b, size_t s) {
36 int x = snprintf(b, s, "%d", i);
37 if(x > 0 && x < s) return b;
38 return 0;
40 #define intToString(i, b) my_intToString(i, b, sizeof(b))
41 #define ipv4fromstring(s, b) inet_aton(s, (struct in_addr *)(void*)(b))
42 #endif
44 typedef struct {
45 const char* host;
46 unsigned short port;
47 #ifndef IPV4_ONLY
48 struct addrinfo* hostaddr;
49 #else
50 struct sockaddr_in hostaddr;
51 #endif
52 } rs_hostInfo;
54 int rocksockserver_resolve_host(rs_hostInfo* hostinfo) {
55 if (!hostinfo || !hostinfo->host || !hostinfo->port) return -1;
56 #ifndef IPV4_ONLY
57 char pbuf[8];
58 char* ports;
59 int ret;
60 struct addrinfo hints;
62 memset(&hints, 0, sizeof(hints));
63 hints.ai_family = AF_UNSPEC;
64 hints.ai_socktype = SOCK_STREAM;
65 hints.ai_flags = AI_PASSIVE;
66 if(!(ports = intToString(hostinfo->port, pbuf))) return -1;
67 return getaddrinfo(hostinfo->host, ports, &hints, &hostinfo->hostaddr);
68 #else
69 memset(&hostinfo->hostaddr, 0, sizeof(struct sockaddr_in));
70 ipv4fromstring(hostinfo->host, (unsigned char*) &hostinfo->hostaddr.sin_addr);
71 hostinfo->hostaddr.sin_family = AF_INET;
72 hostinfo->hostaddr.sin_port = htons(hostinfo->port);
73 return 0;
74 #endif
77 /* returns 0 on success.
78 possible error return codes:
79 -1: erroneus parameter
80 -2: bind() failed
81 -3: socket() failed
82 -4: listen() failed
83 positive number: dns error, pass to gai_strerror()
85 int rocksockserver_init(rocksockserver* srv, const char* listenip, unsigned short port, void* userdata) {
86 int ret = 0;
87 int yes = 1;
88 rs_hostInfo conn;
89 if(!srv || !listenip || !port) return -1;
90 conn.host = listenip;
91 conn.port = port;
92 FD_ZERO(&srv->master);
93 srv->userdata = userdata;
94 srv->sleeptime_us = 20000; // set a reasonable default value. it's a compromise between throughput and cpu usage basically.
95 ret = rocksockserver_resolve_host(&conn);
96 if(ret) return ret;
97 #ifndef IPV4_ONLY
98 struct addrinfo* p;
99 for(p = conn.hostaddr; p != NULL; p = p->ai_next) {
100 srv->listensocket = socket(p->ai_family, p->ai_socktype, p->ai_protocol);
101 if (srv->listensocket < 0) {
102 continue;
105 // lose the pesky "address already in use" error message
106 setsockopt(srv->listensocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
108 if (bind(srv->listensocket, p->ai_addr, p->ai_addrlen) < 0) {
109 close(srv->listensocket);
110 continue;
113 break;
115 if (!p) {
116 LOGP("bind");
117 ret = -2;
119 freeaddrinfo(conn.hostaddr);
120 if(ret == -2) return -2;
121 #else
122 srv->listensocket = socket(AF_INET, SOCK_STREAM, 0);
123 if(srv->listensocket < 0) {
124 LOGP("socket");
125 return -3;
127 setsockopt(srv->listensocket, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(int));
128 if(bind(srv->listensocket, (struct sockaddr*) &conn.hostaddr, sizeof(struct sockaddr_in)) < 0) {
129 close(srv->listensocket);
130 LOGP("bind");
131 return -2;
134 #endif
135 // listen
136 if (listen(srv->listensocket, 10) == -1) {
137 LOGP("listen");
138 ret = -4;
139 } else {
140 FD_SET(srv->listensocket, &srv->master);
141 srv->maxfd = srv->listensocket;
143 return ret;
146 int rocksockserver_disconnect_client(rocksockserver* srv, int client) {
147 if(client < 0 || client > USER_MAX_FD) return -1;
148 if(FD_ISSET(client, &srv->master)) {
149 close(client);
150 FD_CLR(client, &srv->master);
151 if(client == srv->maxfd)
152 srv->maxfd--;
153 srv->numfds--;
154 return 0;
156 return 1;
159 void rocksockserver_watch_fd(rocksockserver* srv, int newfd) {
160 FD_SET(newfd, &srv->master);
161 if (newfd > srv->maxfd)
162 srv->maxfd = newfd;
165 int rocksockserver_loop(rocksockserver* srv,
166 char* buf, size_t bufsize,
167 int (*on_clientconnect) (void* userdata, struct sockaddr_storage* clientaddr, int fd),
168 int (*on_clientread) (void* userdata, int fd, size_t nread),
169 int (*on_clientwantsdata) (void* userdata, int fd),
170 int (*on_clientdisconnect) (void* userdata, int fd)
172 fd_set read_fds, write_fds;
173 int newfd, k;
174 int lastfd = 3;
175 #ifdef IS_LITTLE_ENDIAN
176 int i;
177 size_t j;
178 #endif
179 ptrdiff_t nbytes;
180 struct sockaddr_storage remoteaddr; // client address
181 socklen_t addrlen;
182 char* fdptr;
183 fd_set* setptr;
185 for(;;) {
187 read_fds = srv->master;
188 write_fds = srv->master;
190 if ((srv->numfds = select(srv->maxfd+1, &read_fds, &write_fds, NULL, NULL)) && srv->numfds == -1)
191 LOGP("select");
193 if(!srv->numfds) continue;
195 // optimization for the case searched_fd = lastfd, when we only have to handle one connection.
196 // i guess that should be the majority of cases.
197 k = lastfd;
198 setptr = &write_fds;
199 if(FD_ISSET(k, setptr)) goto gotcha;
200 setptr = &read_fds;
201 if(FD_ISSET(k, setptr)) goto gotcha;
203 nextfd:
204 setptr = &write_fds;
205 loopstart:
206 fdptr = (char*) setptr;
207 #ifdef IS_LITTLE_ENDIAN
208 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)
209 if( *(size_t*)(fdptr + i)) {
210 for(j = 0; j <= sizeof(size_t); j++) {
211 if(fdptr[i + j]) {
212 for(k = (i + j) * CHAR_BIT; k <= srv->maxfd; k++) {
213 #else
214 for(k = 0; k <= srv->maxfd; k++) {
215 #endif
216 if(FD_ISSET(k, setptr)) {
217 gotcha:
218 srv->numfds--;
219 FD_CLR(k, setptr);
220 if(setptr == &write_fds)
221 goto handlewrite;
222 else
223 goto handleread;
226 #ifdef IS_LITTLE_ENDIAN
232 #endif
234 if(setptr == &write_fds) {
235 setptr = &read_fds;
236 goto loopstart;
237 } else {
238 LOGP("FATAL");
240 printf("maxfd %d, k %d, numfds %d, set %d\n", srv->maxfd, k, srv->numfds, *(int*)(fdptr));
241 for(k = 0; k < USER_MAX_FD; k++)
242 if(FD_ISSET(k, setptr))
243 printf("bit set: %d\n", k);
245 return 1;
248 handleread:
249 //printf("read_fd %d\n", k);
250 if (k == srv->listensocket) {
251 // new connection available
252 addrlen = sizeof(remoteaddr);
253 newfd = accept(srv->listensocket, (struct sockaddr *)&remoteaddr, &addrlen);
255 if (newfd == -1) {
256 LOGP("accept");
257 } else {
258 if(newfd >= USER_MAX_FD)
259 close(newfd); // only USER_MAX_FD connections can be handled.
260 else {
261 FD_SET(newfd, &srv->master);
262 if (newfd > srv->maxfd)
263 srv->maxfd = newfd;
264 if(on_clientconnect) on_clientconnect(srv->userdata, &remoteaddr, newfd);
267 } else {
268 if(buf && k != srv->signalfd) {
269 if ((nbytes = recv(k, buf, bufsize, 0)) <= 0) {
270 if (nbytes == 0) {
271 if(on_clientdisconnect) on_clientdisconnect(srv->userdata, k);
272 } else {
273 LOGP("recv");
275 rocksockserver_disconnect_client(srv, k);
276 } else {
277 if(on_clientread) on_clientread(srv->userdata, k, nbytes);
279 } else {
281 if(on_clientread) on_clientread(srv->userdata, k, 0);
284 goto zzz;
286 handlewrite:
288 //printf("write_fd %d\n", k);
289 if(on_clientwantsdata) on_clientwantsdata(srv->userdata, k);
291 zzz:
292 if(srv->numfds > 0) goto nextfd;
293 lastfd = k;
294 microsleep(srv->sleeptime_us);
296 return 0;