Imported version 0.2-1
[mstardict] / src / lib / sockets.cpp
1 #ifdef HAVE_CONFIG_H\r
2 #  include "config.h"\r
3 #endif\r
4 \r
5 \r
6 #include <cstdio>\r
7 #include <cstring>\r
8 #include <glib.h>\r
9 \r
10 #if defined(_WIN32)\r
11 \r
12 # include <winsock2.h>\r
13 \r
14 #if defined(_MSC_VER)\r
15 #pragma comment(lib,"WS2_32.lib")\r
16 #endif\r
17 \r
18 # define EINPROGRESS    WSAEINPROGRESS\r
19 # define EWOULDBLOCK    WSAEWOULDBLOCK\r
20 # define ETIMEDOUT          WSAETIMEDOUT\r
21 # define EAGAIN                 WSAEWOULDBLOCK\r
22 # define EINTR                  WSAEINTR\r
23 #else\r
24 extern "C" {\r
25 # include <unistd.h>\r
26 # include <sys/types.h>\r
27 # include <sys/socket.h>\r
28 # include <netinet/in.h>\r
29 # include <netdb.h>\r
30 # include <cerrno>\r
31 # include <fcntl.h>\r
32 }\r
33 #endif  // _WIN32\r
34 \r
35 #include "sockets.hpp"\r
36 \r
37 std::map<std::string, in_addr_t> Socket::dns_map;\r
38 \r
39 #if defined(_WIN32)\r
40   \r
41 static void initWinSock()\r
42 {\r
43   static bool wsInit = false;\r
44   if (! wsInit)\r
45   {\r
46     WORD wVersionRequested = MAKEWORD( 2, 0 );\r
47     WSADATA wsaData;\r
48     WSAStartup(wVersionRequested, &wsaData);\r
49     wsInit = true;\r
50   }\r
51 }\r
52 \r
53 #else\r
54 \r
55 #define initWinSock()\r
56 \r
57 #endif // _WIN32\r
58 \r
59 \r
60 // These errors are not considered fatal for an IO operation; the operation will be re-tried.\r
61 \r
62 static inline bool\r
63 nonFatalError()\r
64 {\r
65   int err = Socket::get_error_code();\r
66   return (err == EINPROGRESS || err == EAGAIN || err == EWOULDBLOCK || err == EINTR);\r
67 }\r
68 \r
69 \r
70 \r
71 int\r
72 Socket::socket()\r
73 {\r
74   initWinSock();\r
75   return (int) ::socket(AF_INET, SOCK_STREAM, 0);\r
76 }\r
77 \r
78 \r
79 void\r
80 Socket::close(int fd)\r
81 {\r
82 #if defined(_WIN32)\r
83   closesocket(fd);\r
84 #else\r
85   ::close(fd);\r
86 #endif // _WIN32\r
87 }\r
88 \r
89 \r
90 \r
91 \r
92 bool\r
93 Socket::set_non_blocking(int fd)\r
94 {\r
95 #if defined(_WIN32)\r
96   unsigned long flag = 1;\r
97   return (ioctlsocket((SOCKET)fd, FIONBIO, &flag) == 0);\r
98 #else\r
99   return (fcntl(fd, F_SETFL, O_NONBLOCK) == 0);\r
100 #endif // _WIN32\r
101 }\r
102 \r
103 \r
104 bool\r
105 Socket::set_reuse_addr(int fd)\r
106 {\r
107   // Allow this port to be re-bound immediately so server re-starts are not delayed\r
108   int sflag = 1;\r
109   return (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, (const char *)&sflag, sizeof(sflag)) == 0);\r
110 }\r
111 \r
112 \r
113 // Bind to a specified port\r
114 bool \r
115 Socket::bind(int fd, int port)\r
116 {\r
117   struct sockaddr_in saddr;\r
118   memset(&saddr, 0, sizeof(saddr));\r
119   saddr.sin_family = AF_INET;\r
120   saddr.sin_addr.s_addr = htonl(INADDR_ANY);\r
121   saddr.sin_port = htons((u_short) port);\r
122   return (::bind(fd, (struct sockaddr *)&saddr, sizeof(saddr)) == 0);\r
123 }\r
124 \r
125 \r
126 // Set socket in listen mode\r
127 bool Socket::listen(int fd, int backlog)\r
128 {\r
129   return (::listen(fd, backlog) == 0);\r
130 }\r
131 \r
132 \r
133 int Socket::accept(int fd)\r
134 {\r
135   struct sockaddr_in addr;\r
136 #if defined(_WIN32)\r
137   int\r
138 #else\r
139   socklen_t\r
140 #endif\r
141     addrlen = sizeof(addr);\r
142 \r
143   return (int) ::accept(fd, (struct sockaddr*)&addr, &addrlen);\r
144 }\r
145 \r
146 gboolean Socket::dns_main_thread_cb(gpointer data)\r
147 {\r
148     DnsQueryData *query_data = (DnsQueryData *)data;\r
149     if (query_data->resolved) {\r
150                 dns_map[query_data->host] = query_data->sa;\r
151         }\r
152     query_data->func(query_data->data, query_data->resolved, query_data->sa);\r
153     delete query_data;\r
154     return FALSE;\r
155 }\r
156 \r
157 gpointer Socket::dns_thread(gpointer data)\r
158 {\r
159     DnsQueryData *query_data = (DnsQueryData *)data;\r
160     struct  hostent *phost;\r
161 #ifndef _WIN32    \r
162     struct  hostent hostinfo;\r
163     char buf[1024];\r
164     int ret;\r
165     if (!gethostbyname_r(query_data->host.c_str(), &hostinfo, buf,\r
166         sizeof(buf), &phost, &ret)) {\r
167         query_data->sa = ((in_addr*)(hostinfo.h_addr))->s_addr;\r
168         query_data->resolved = true;\r
169     } else {\r
170         query_data->resolved = false;\r
171     }\r
172 #else\r
173         //static GStaticMutex mutex = G_STATIC_MUTEX_INIT;\r
174         //g_static_mutex_lock (&mutex);\r
175         if (isalpha(query_data->host[0])) {\r
176                 phost = gethostbyname(query_data->host.c_str());\r
177         } else {\r
178                 unsigned int addr;\r
179                 addr = inet_addr(query_data->host.c_str());\r
180                 phost = gethostbyaddr((char *)&addr, 4, AF_INET);\r
181         }\r
182         if (phost) {\r
183                 query_data->sa = ((in_addr*)(phost->h_addr))->s_addr;\r
184                 query_data->resolved = true;\r
185         } else {\r
186                 query_data->resolved = false;\r
187         }\r
188         //g_static_mutex_unlock (&mutex);\r
189 #endif                     \r
190     /* back to main thread */\r
191     g_idle_add(dns_main_thread_cb, query_data);\r
192     return NULL;\r
193 }\r
194 \r
195 void Socket::resolve(std::string& host, gpointer data, on_resolved_func func)\r
196 {\r
197         initWinSock();\r
198         std::map<std::string, in_addr_t>::iterator iter;\r
199         iter = dns_map.find(host);\r
200         if (iter != dns_map.end()) {\r
201                 func(data, true, iter->second);\r
202                 return;\r
203         }\r
204         DnsQueryData *query_data = new DnsQueryData();\r
205         query_data->host = host;\r
206         query_data->data = data;\r
207         query_data->func = func;\r
208         g_thread_create(dns_thread, query_data, FALSE, NULL);\r
209 }\r
210 \r
211 void Socket::connect(int socket, in_addr_t sa, int port, gpointer data, on_connected_func func)\r
212 {\r
213         ConnectData *connect_data = new ConnectData();\r
214         connect_data->sd = socket;\r
215         connect_data->sa = sa;\r
216         connect_data->port = port;\r
217         connect_data->data = data;\r
218         connect_data->func = func;\r
219         g_thread_create(connect_thread, connect_data, FALSE, NULL);\r
220 }\r
221 \r
222 gpointer Socket::connect_thread(gpointer data)\r
223 {\r
224     ConnectData *connect_data = (ConnectData *)data;\r
225         struct sockaddr_in saddr;\r
226         memset(&saddr, 0, sizeof(saddr));\r
227         saddr.sin_family = AF_INET;\r
228         saddr.sin_addr.s_addr = connect_data->sa;\r
229         saddr.sin_port = htons((u_short) connect_data->port);\r
230 \r
231         // For asynch operation, this will return EWOULDBLOCK (windows) or\r
232         // EINPROGRESS (linux) and we just need to wait for the socket to be writable...\r
233         int result = ::connect(connect_data->sd, (struct sockaddr *)&saddr, sizeof(saddr));\r
234         connect_data->succeeded = (result == 0);\r
235 \r
236     /* back to main thread */\r
237     g_idle_add(connect_main_thread_cb, connect_data);\r
238     return NULL;\r
239 }\r
240 \r
241 gboolean Socket::connect_main_thread_cb(gpointer data)\r
242 {\r
243     ConnectData *connect_data = (ConnectData *)data;\r
244         connect_data->func(connect_data->data, connect_data->succeeded);\r
245     delete connect_data;\r
246     return FALSE;\r
247 }\r
248     \r
249 // Read available text from the specified socket. Returns false on error.\r
250 bool Socket::nb_read(int fd, std::string& s, bool *eof)\r
251 {\r
252   const int READ_SIZE = 4096;   // Number of bytes to attempt to read at a time\r
253   char readBuf[READ_SIZE];\r
254 \r
255   bool wouldBlock = false;\r
256   *eof = false;\r
257 \r
258   while ( ! wouldBlock && ! *eof) {\r
259 #if defined(_WIN32)\r
260     int n = recv(fd, readBuf, READ_SIZE-1, 0);\r
261 #else\r
262     int n = read(fd, readBuf, READ_SIZE-1);\r
263 #endif\r
264     g_debug("Socket::nbRead: read/recv returned %d.", n);\r
265 \r
266 \r
267     if (n > 0) {\r
268       readBuf[n] = 0;\r
269       s.append(readBuf, n);\r
270     } else if (n == 0) {\r
271       *eof = true;\r
272     } else if (nonFatalError()) {\r
273       wouldBlock = true;\r
274     } else {\r
275       return false;   // Error\r
276     }\r
277   }\r
278   return true;\r
279 }\r
280 \r
281 \r
282 // Write text to the specified socket. Returns false on error.\r
283 bool Socket::nb_write(int fd, std::string& s, int *bytesSoFar)\r
284 {\r
285   int nToWrite = int(s.length()) - *bytesSoFar;\r
286   char *sp = const_cast<char*>(s.c_str()) + *bytesSoFar;\r
287   bool wouldBlock = false;\r
288 \r
289   while ( nToWrite > 0 && ! wouldBlock ) {\r
290 #if defined(_WIN32)\r
291     int n = send(fd, sp, nToWrite, 0);\r
292 #else\r
293     int n = write(fd, sp, nToWrite);\r
294 #endif\r
295     g_debug("Socket::nbWrite: send/write returned %d.", n);\r
296 \r
297     if (n > 0) {\r
298       sp += n;\r
299       *bytesSoFar += n;\r
300       nToWrite -= n;\r
301     } else if (nonFatalError()) {\r
302       wouldBlock = true;\r
303     } else {\r
304       return false;   // Error\r
305     }\r
306   }\r
307   return true;\r
308 }\r
309 \r
310 \r
311 // Returns last errno\r
312 int Socket::get_error_code()\r
313 {\r
314 #if defined(_WIN32)\r
315   return WSAGetLastError();\r
316 #else\r
317   return errno;\r
318 #endif\r
319 }\r
320 \r
321 \r
322 // Returns message corresponding to last errno\r
323 std::string Socket::get_error_msg()\r
324 {\r
325 //Actually works on windows, but may be better use FormatMessage?\r
326   return strerror(get_error_code());\r
327 \r
328 }\r
329 \r