Subversion Repositories HomeAutomation

Rev

Details | Last modification | View Log | SVN | RSS feed

Rev Author Line No. Line
969 runge 1
/***************************************************************************
2
 *   Copyright (C) December 6, 2008 by Mattias Runge                             *
3
 *   mattias@runge.se                                                      *
4
 *   asyncsocket.cpp                                            *
5
 *                                                                         *
6
 *   This program is free software; you can redistribute it and/or modify  *
7
 *   it under the terms of the GNU General Public License as published by  *
8
 *   the Free Software Foundation; either version 2 of the License, or     *
9
 *   (at your option) any later version.                                   *
10
 *                                                                         *
11
 *   This program is distributed in the hope that it will be useful,       *
12
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of        *
13
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the         *
14
 *   GNU General Public License for more details.                          *
15
 *                                                                         *
16
 *   You should have received a copy of the GNU General Public License     *
17
 *   along with this program; if not, write to the                         *
18
 *   Free Software Foundation, Inc.,                                       *
19
 *   59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.             *
20
 ***************************************************************************/
21
 
22
#include "asyncsocket.h"
23
#include <iostream>
24
Semaphore AsyncSocket::mySemaphore;
25
 
26
AsyncSocket::AsyncSocket()
27
{
28
	mySocket = -1;
29
	myReconnectTimeout = 10;
974 runge 30
	myForceReconnect = false;
969 runge 31
 
32
	Thread<AsyncSocket>();
33
}
34
 
35
AsyncSocket::~AsyncSocket()
36
{
37
	if (mySocket != -1)
38
	{
39
		::close(mySocket);
40
		mySocket = -1;
41
	}
42
 
43
	stop();
44
}
45
 
46
void AsyncSocket::run()
47
{
48
	SyslogStream &slog = SyslogStream::getInstance();
49
 
50
	reconnectLoop();
51
 
52
	char buf[MAXBUFFER + 1];
53
	string data;
54
	int status;
974 runge 55
	int rc;
56
	int timeSince = time(NULL) + 10;
57
 
969 runge 58
	AsyncSocket::mySemaphore.lock();
59
 
60
	try
61
	{
62
		while (1)
63
		{
974 runge 64
			rc = mySemaphore.wait(5);
969 runge 65
 
974 runge 66
			if (myForceReconnect)
67
			{
68
				slog << "Disconnected from server.\n";
69
				reconnectLoop();
70
				timeSince = time(NULL) + 10;
71
				continue;
72
			}
73
 
969 runge 74
			//cout << "Socket awoken...\n";
974 runge 75
 
76
			if (rc == ETIMEDOUT)
77
			{
78
				/* Socket timed out, this means we have not received anything in some time
79
				and we should check the connection */
969 runge 80
 
974 runge 81
				setEvent(ASYNCSOCKET_EVENT_INACTIVITY);
82
				timeSince = time(NULL);
83
			}
84
			else
969 runge 85
			{
974 runge 86
				while (myOutQueue.size() > 0)
87
				{
88
					data = myOutQueue.pop();
969 runge 89
 
974 runge 90
					//slog << "Sending: " << data << "\n";
969 runge 91
 
974 runge 92
					status = ::send(mySocket, data.c_str(), data.size(), 0);
969 runge 93
 
974 runge 94
					//slog << "Status: " << status << "\n";
95
 
96
					if (status == -1)
969 runge 97
					{
974 runge 98
						switch (errno)
99
						{
100
							case EACCES:
101
							throw new SocketException("(For  Unix  domain sockets, which are identified by pathname) Write permission is denied on the destination socket file, or search permission is denied for one of the directories the path prefix.  (See path_resolution(7).)");
969 runge 102
 
974 runge 103
							case EAGAIN:
104
							//slog << "The socket is marked non-blocking and the requested operation would block.\n";
105
							break;
969 runge 106
 
974 runge 107
							case EBADF:
108
							throw new SocketException("An invalid descriptor was specified.");
969 runge 109
 
974 runge 110
							case ECONNRESET:
111
							throw new SocketException("Connection reset by peer.");
969 runge 112
 
974 runge 113
							case EDESTADDRREQ:
114
							throw new SocketException("The socket is not connection-mode, and no peer address is set.");
969 runge 115
 
974 runge 116
							case EFAULT:
117
							throw new SocketException("An invalid user space address was specified for an argument.");
969 runge 118
 
974 runge 119
							case EINTR:
120
							throw new SocketException("A signal occurred before any data was transmitted; see signal(7).");
969 runge 121
 
974 runge 122
							case EINVAL:
123
							throw new SocketException("1Invalid argument passed.");
969 runge 124
 
974 runge 125
							case EISCONN:
126
							throw new SocketException("The connection-mode socket was connected already but a recipient was specified. (Now either this error is returned, or the recipient specification is ignored.)");
969 runge 127
 
974 runge 128
							case EMSGSIZE:
129
							throw new SocketException("The socket type requires that message be sent atomically, and the size of the message to be sent made this impossible.");
969 runge 130
 
974 runge 131
							case ENOBUFS:
132
							throw new SocketException("The output queue for a network interface was full.  This generally indicates that the interface has stopped sending, but may be caused by transient congestion.  (Normally, this does not occur in Linux.  Packets are just silently dropped when a device queue overflows.)");
969 runge 133
 
974 runge 134
							case ENOMEM:
135
							throw new SocketException("No memory available.");
969 runge 136
 
974 runge 137
							case ENOTCONN:
138
							throw new SocketException("The socket is not connected, and no target has been given.");
969 runge 139
 
974 runge 140
							case ENOTSOCK:
141
							throw new SocketException("The argument s is not a socket.");
969 runge 142
 
974 runge 143
							case EOPNOTSUPP:
144
							throw new SocketException("Some bit in the flags argument is inappropriate for the socket type.");
969 runge 145
 
974 runge 146
							case EPIPE:
147
							throw new SocketException("The  local end has been shut down on a connection oriented socket. In this case the process will also receive a SIGPIPE unless MSG_NOSIGNAL is set.");
969 runge 148
 
974 runge 149
							default:
150
							slog << "Unknow exception: " << errno << "\n";
151
							break;
152
						}
969 runge 153
					}
154
				}
155
 
974 runge 156
				memset(buf, 0, MAXBUFFER + 1);
969 runge 157
 
974 runge 158
				status = ::recv(mySocket, buf, MAXBUFFER, 0);
969 runge 159
 
974 runge 160
				//slog << "Receiving: " << buf << "\n";
161
 
162
				if (status == -1)
969 runge 163
				{
974 runge 164
					switch (errno)
165
					{
166
						case EAGAIN:
167
						//slog << "The socket is marked non-blocking and the receive operation would block, or a receive timeout had been set and the timeout expired before data was received.\n";
168
						break;
969 runge 169
 
974 runge 170
						case EBADF:
171
						throw new SocketException("The argument s is an invalid descriptor.");
969 runge 172
 
974 runge 173
						case ECONNREFUSED:
174
						throw new SocketException("A remote host refused to allow the network connection (typically because it is not running the requested service).");
969 runge 175
 
974 runge 176
						case EFAULT:
177
						throw new SocketException("The receive buffer pointer(s) point outside the process's address space.");
969 runge 178
 
974 runge 179
						case EINTR:
180
						throw new SocketException("The receive was interrupted by delivery of a signal before any data were available; see signal(7).");
969 runge 181
 
974 runge 182
						case EINVAL:
183
						//throw new SocketException("2Invalid argument passed.");
184
						slog << "Disconnected from server.\n";
185
						reconnectLoop();
186
						break;
969 runge 187
 
974 runge 188
						case ENOMEM:
189
						throw new SocketException("Could not allocate memory for recvmsg().");
969 runge 190
 
974 runge 191
						case ENOTCONN:
192
						throw new SocketException("The socket is associated with a connection-oriented protocol and has not been connected (see connect(2) and  accept(2)).");
969 runge 193
 
974 runge 194
						case ENOTSOCK:
195
						throw new SocketException("The argument s does not refer to a socket.");
969 runge 196
 
974 runge 197
						default:
198
						slog << "Unknow exception: " << errno << "\n";
199
						break;
200
					}
969 runge 201
				}
974 runge 202
				else if (status == 0)
203
				{
204
					slog << "Disconnected from server.\n";
205
					reconnectLoop();
206
				}
207
				else if (status > 0)
208
				{
209
					data = buf;
969 runge 210
 
974 runge 211
					myInQueue.push(data);
969 runge 212
 
974 runge 213
					setEvent(ASYNCSOCKET_EVENT_DATA);
214
 
215
					timeSince = time(NULL);
216
				}
217
 
218
				if (timeSince + 10 < time(NULL))
219
				{
220
					setEvent(ASYNCSOCKET_EVENT_INACTIVITY);
221
					timeSince = time(NULL);
222
				}
969 runge 223
			}
224
		}
225
	}
226
	catch (SocketException *e)
227
	{
228
		slog << "Exception: " << e->getDescription() << "\n";
229
		mySemaphore.unlock();
230
		setEvent(ASYNCSOCKET_EVENT_DIED);
231
		stop();
232
	}
233
 
234
	close();
235
 
236
	mySemaphore.unlock();
237
}
238
 
239
void AsyncSocket::reconnectLoop()
240
{
241
	SyslogStream &slog = SyslogStream::getInstance();
242
 
243
	while (1)
244
	{
245
		try
246
		{
247
			slog << "Trying to connect...\n";
248
			connect();
249
			slog << "Connection established.\n";
250
			break;
251
		}
252
		catch (SocketException *e)
253
		{
254
			slog << "Could not connect: " << e->getDescription() << "\n";
255
			slog << "Will try again in " << myReconnectTimeout << " seconds\n";
256
			sleep(myReconnectTimeout);
257
		}
258
	}
259
}
260
 
261
void AsyncSocket::connect()
262
{
975 runge 263
	close();
969 runge 264
 
265
	mySocket = ::socket(AF_INET, SOCK_STREAM, 0);
266
 
267
	int on = 1;
268
	int status = setsockopt(mySocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));
269
	if (status == -1)
270
	{
974 runge 271
		throw new SocketException("Connect:Reuseaddress: " + itos(errno));
969 runge 272
	}
273
 
974 runge 274
	///FIXME: Verify that this works
275
	status = setsockopt(mySocket, SOL_SOCKET, SO_KEEPALIVE, (const char*)&on, sizeof(on));
276
	if (status == -1)
277
	{
278
		throw new SocketException("Connect:Keepalive: " + itos(errno));
279
	}
280
 
281
	///FIXME: Verify that this works
282
	status = setsockopt(mySocket, SOL_TCP, TCP_KEEPIDLE, (const char*)&on, sizeof(on));
283
	if (status == -1)
284
	{
285
		throw new SocketException("Connect:Keepidle: " + itos(errno));
286
	}
287
 
969 runge 288
	memset(&myAddressStruct, 0, sizeof(myAddressStruct));
289
 
290
	myAddressStruct.sin_family = AF_INET;
291
	myAddressStruct.sin_port = htons(myPort);
292
 
293
	status = inet_pton(AF_INET, myAddress.c_str(), &myAddressStruct.sin_addr);
294
 
295
	if (status == -1)
296
	{
297
		if (errno == EAFNOSUPPORT)
298
			throw new SocketException("Connect: EAFNOSUPPORT");
299
	}
300
 
301
	status = ::connect(mySocket, (sockaddr*)&myAddressStruct, sizeof(myAddressStruct));
302
 
303
	if (status == -1)
304
	{
305
		switch (errno)
306
		{
307
			case EACCES:
308
			throw new SocketException("For Unix domain sockets, which are identified by pathname: Write permission is denied on the socket file, or search per- mission is denied for one of the directories in the path prefix.  (See also path_resolution(7).)");
309
 
310
			case EPERM:
311
			throw new SocketException("The user tried to connect to a broadcast address without having the socket broadcast  flag  enabled  or  the  connection request failed because of a local firewall rule.");
312
 
313
			case EADDRINUSE:
314
			throw new SocketException("Local address is already in use.");
315
 
316
			case EAFNOSUPPORT:
317
			throw new SocketException("The passed address didn't have the correct address family in its sa_family field.");
318
 
319
			case EAGAIN:
320
			throw new SocketException("No more free local ports or insufficient entries in the routing cache.  For AF_INET see the net.ipv4.ip_local_port_range sysctl in ip(7) on how to increase the number of local ports.");
321
 
322
			case EALREADY:
323
			throw new SocketException("The socket is non-blocking and a previous connection attempt has not yet been completed.");
324
 
325
			case EBADF:
326
			throw new SocketException("The file descriptor is not a valid index in the descriptor table.");
327
 
328
			case ECONNREFUSED:
329
			throw new SocketException("No-one listening on the remote address.");
330
 
331
			case EFAULT:
332
			throw new SocketException("The socket structure address is outside the user's address space.");
333
 
334
			case EINPROGRESS:
335
			throw new SocketException("The socket is non-blocking and the connection cannot be completed immediately.  It is possible to select(2)  or  poll(2) for  completion  by  selecting the socket for writing.  After select(2) indicates writability, use getsockopt(2) to read the SO_ERROR option at level SOL_SOCKET to determine whether connect() completed  successfully  (SO_ERROR  is  zero)  or unsuccessfully (SO_ERROR is one of the usual error codes listed here, explaining the reason for the failure).");
336
 
337
			case EINTR:
338
			throw new SocketException("The system call was interrupted by a signal that was caught; see signal(7).");
339
 
340
			case EISCONN:
341
			throw new SocketException("The socket is already connected.");
342
 
343
			case ENETUNREACH:
344
			throw new SocketException("Network is unreachable.");
345
 
346
			case ENOTSOCK:
347
			throw new SocketException("The file descriptor is not associated with a socket.");
348
 
349
			case ETIMEDOUT:
350
			throw new SocketException("Timeout  while  attempting  connection.  The server may be too busy to accept new connections.  Note that for IP sockets the timeout may be very long when syncookies are enabled on the server.");
351
 
352
			default:
353
			throw new SocketException("Other errors may be generated by the underlying protocol modules. : " + itos(errno));
354
		}
355
	}
356
 
357
	struct sigaction saio;
358
	saio.sa_handler = AsyncSocket::signalHandler;
359
	sigemptyset(&saio.sa_mask);
360
	saio.sa_flags = 0;
361
	saio.sa_restorer = NULL;
362
	sigaction(SIGIO, &saio, NULL);
363
 
364
	fcntl(mySocket, F_SETOWN, getpid());
365
	int flags = fcntl(mySocket, F_GETFL);
366
 
367
	if (flags < 0)
368
		throw new SocketException("Async socket fcntl failed");
369
 
370
	fcntl(mySocket, F_SETFL, flags | O_NONBLOCK | FASYNC);
974 runge 371
 
372
	myForceReconnect = false;
969 runge 373
}
374
 
375
void AsyncSocket::close()
376
{
377
	if (mySocket != -1)
378
	{
379
		::close(mySocket);
380
		mySocket = -1;
381
		setEvent(ASYNCSOCKET_EVENT_CLOSED);
382
	}
383
}
384
 
385
void AsyncSocket::setReconnectTimeout(unsigned int timeout)
386
{
387
	myReconnectTimeout = timeout;
388
}
389
 
390
void AsyncSocket::startEvent()
391
{
392
	myEventSemaphore.lock();
393
}
394
 
395
int AsyncSocket::getEvent()
396
{
397
	int event = myEvent;
398
	myEvent = ASYNCSOCKET_EVENT_NONE;
399
	return event;
400
}
401
 
402
void AsyncSocket::waitForEvent()
403
{
404
	myEventSemaphore.wait();
405
}
406
 
407
void AsyncSocket::stopEvent()
408
{
409
	myEventSemaphore.unlock();
410
}
411
 
412
void AsyncSocket::setAddress(string address, int port)
413
{
414
	myAddress = address;
415
	myPort = port;
416
}
417
 
418
bool AsyncSocket::availableData()
419
{
420
	return (myInQueue.size() > 0);
421
}
422
 
423
string AsyncSocket::getData()
424
{
425
	return myInQueue.pop();
426
}
427
 
428
bool AsyncSocket::sendData(string data)
429
{
430
	myOutQueue.push(data);
431
	mySemaphore.broadcast();
432
	return true;
433
}
434
 
435
void AsyncSocket::setEvent(int event)
436
{
437
	myEventSemaphore.lock();
438
	myEvent = event;
439
	myEventSemaphore.unlock();
440
	myEventSemaphore.broadcast();
441
}
442
 
974 runge 443
void AsyncSocket::forceReconnect()
444
{
445
	myForceReconnect = true;
446
	mySemaphore.broadcast();
447
}
448
 
969 runge 449
void AsyncSocket::signalHandler(int signum)
450
{
974 runge 451
	//FIXME: We must know which socket is ready to read by using select... 
969 runge 452
	//cout << "DEBUG: signalHandler signum: " << signum << endl;
453
	mySemaphore.broadcast();
454
}