Support UDP input. Also fix some issues with socket closing.
[cubemap] / acceptor.cpp
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <signal.h>
5 #include <errno.h>
6 #include <arpa/inet.h>
7 #include <sys/ioctl.h>
8 #include <sys/poll.h>
9 #include <sys/socket.h>
10
11 #include "acceptor.h"
12 #include "serverpool.h"
13 #include "state.pb.h"
14
15 using namespace std;
16
17 extern ServerPool *servers;
18 extern volatile bool hupped;
19
20 int create_server_socket(int port, SocketType socket_type)
21 {
22         int server_sock;
23         if (socket_type == TCP_SOCKET) {
24                 server_sock = socket(PF_INET6, SOCK_STREAM, IPPROTO_TCP);
25         } else {
26                 assert(socket_type == UDP_SOCKET);
27                 server_sock = socket(PF_INET6, SOCK_DGRAM, IPPROTO_UDP);
28         }
29         if (server_sock == -1) {
30                 perror("socket");
31                 exit(1);
32         }
33
34         int one = 1;
35         if (setsockopt(server_sock, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one)) == -1) {
36                 perror("setsockopt(SO_REUSEADDR)");
37                 exit(1);
38         }
39
40         // We want dual-stack sockets. (Sorry, OpenBSD and Windows XP...)
41         int zero = 0;
42         if (setsockopt(server_sock, IPPROTO_IPV6, IPV6_V6ONLY, &zero, sizeof(zero)) == -1) {
43                 perror("setsockopt(IPV6_V6ONLY)");
44                 exit(1);
45         }
46
47         // Set as non-blocking, so the acceptor thread can notice that we want to shut it down.
48         if (ioctl(server_sock, FIONBIO, &one) == -1) {
49                 perror("ioctl(FIONBIO)");
50                 exit(1);
51         }
52
53         sockaddr_in6 addr;
54         memset(&addr, 0, sizeof(addr));
55         addr.sin6_family = AF_INET6;
56         addr.sin6_port = htons(port);
57
58         if (bind(server_sock, reinterpret_cast<sockaddr *>(&addr), sizeof(addr)) == -1) {
59                 perror("bind");
60                 exit(1);
61         }
62
63         if (socket_type == TCP_SOCKET) {
64                 if (listen(server_sock, 128) == -1) {
65                         perror("listen");
66                         exit(1);
67                 }
68         }
69
70         return server_sock;
71 }
72         
73 Acceptor::Acceptor(int server_sock, int port)
74         : server_sock(server_sock),
75           port(port)
76 {
77 }
78
79 Acceptor::Acceptor(const AcceptorProto &serialized)
80         : server_sock(serialized.server_sock()),
81           port(serialized.port())
82 {
83 }
84
85 AcceptorProto Acceptor::serialize() const
86 {
87         AcceptorProto serialized;
88         serialized.set_server_sock(server_sock);
89         serialized.set_port(port);
90         return serialized;
91 }
92
93 void Acceptor::close_socket()
94 {
95         int ret;
96         do {
97                 ret = close(server_sock);
98         } while (ret == -1 && errno == EINTR);
99
100         if (ret == -1) {
101                 perror("close");
102         }
103 }
104
105 void Acceptor::do_work()
106 {
107         while (!hupped) {
108                 // Since we are non-blocking, we need to wait for the right state first.
109                 // Wait up to 50 ms, then check hupped.
110                 pollfd pfd;
111                 pfd.fd = server_sock;
112                 pfd.events = POLLIN;
113
114                 int nfds = poll(&pfd, 1, 50);
115                 if (nfds == 0 || (nfds == -1 && errno == EINTR)) {
116                         continue;
117                 }
118                 if (nfds == -1) {
119                         perror("poll");
120                         usleep(100000);
121                         continue;
122                 }
123
124                 sockaddr_in6 addr;
125                 socklen_t addrlen = sizeof(addr);
126
127                 // Get a new socket.
128                 int sock = accept(server_sock, reinterpret_cast<sockaddr *>(&addr), &addrlen);
129                 if (sock == -1 && errno == EINTR) {
130                         continue;
131                 }
132                 if (sock == -1) {
133                         perror("accept");
134                         usleep(100000);
135                         continue;
136                 }
137
138                 // Set the socket as nonblocking.
139                 int one = 1;
140                 if (ioctl(sock, FIONBIO, &one) == -1) {
141                         perror("FIONBIO");
142                         exit(1);
143                 }
144
145                 // Pick a server, round-robin, and hand over the socket to it.
146                 servers->add_client(sock);
147         }
148 }