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
{
263
	if (mySocket != -1)
264
	{
265
		::close(mySocket);
266
		mySocket = -1;
267
	}
268
 
269
	mySocket = ::socket(AF_INET, SOCK_STREAM, 0);
270
 
271
	int on = 1;
272
	int status = setsockopt(mySocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));
273
	if (status == -1)
274
	{
974 runge 275
		throw new SocketException("Connect:Reuseaddress: " + itos(errno));
969 runge 276
	}
277
 
974 runge 278
	///FIXME: Verify that this works
279
	status = setsockopt(mySocket, SOL_SOCKET, SO_KEEPALIVE, (const char*)&on, sizeof(on));
280
	if (status == -1)
281
	{
282
		throw new SocketException("Connect:Keepalive: " + itos(errno));
283
	}
284
 
285
	///FIXME: Verify that this works
286
	status = setsockopt(mySocket, SOL_TCP, TCP_KEEPIDLE, (const char*)&on, sizeof(on));
287
	if (status == -1)
288
	{
289
		throw new SocketException("Connect:Keepidle: " + itos(errno));
290
	}
291
 
969 runge 292
	memset(&myAddressStruct, 0, sizeof(myAddressStruct));
293
 
294
	myAddressStruct.sin_family = AF_INET;
295
	myAddressStruct.sin_port = htons(myPort);
296
 
297
	status = inet_pton(AF_INET, myAddress.c_str(), &myAddressStruct.sin_addr);
298
 
299
	if (status == -1)
300
	{
301
		if (errno == EAFNOSUPPORT)
302
			throw new SocketException("Connect: EAFNOSUPPORT");
303
	}
304
 
305
	status = ::connect(mySocket, (sockaddr*)&myAddressStruct, sizeof(myAddressStruct));
306
 
307
	if (status == -1)
308
	{
309
		switch (errno)
310
		{
311
			case EACCES:
312
			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).)");
313
 
314
			case EPERM:
315
			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.");
316
 
317
			case EADDRINUSE:
318
			throw new SocketException("Local address is already in use.");
319
 
320
			case EAFNOSUPPORT:
321
			throw new SocketException("The passed address didn't have the correct address family in its sa_family field.");
322
 
323
			case EAGAIN:
324
			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.");
325
 
326
			case EALREADY:
327
			throw new SocketException("The socket is non-blocking and a previous connection attempt has not yet been completed.");
328
 
329
			case EBADF:
330
			throw new SocketException("The file descriptor is not a valid index in the descriptor table.");
331
 
332
			case ECONNREFUSED:
333
			throw new SocketException("No-one listening on the remote address.");
334
 
335
			case EFAULT:
336
			throw new SocketException("The socket structure address is outside the user's address space.");
337
 
338
			case EINPROGRESS:
339
			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).");
340
 
341
			case EINTR:
342
			throw new SocketException("The system call was interrupted by a signal that was caught; see signal(7).");
343
 
344
			case EISCONN:
345
			throw new SocketException("The socket is already connected.");
346
 
347
			case ENETUNREACH:
348
			throw new SocketException("Network is unreachable.");
349
 
350
			case ENOTSOCK:
351
			throw new SocketException("The file descriptor is not associated with a socket.");
352
 
353
			case ETIMEDOUT:
354
			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.");
355
 
356
			default:
357
			throw new SocketException("Other errors may be generated by the underlying protocol modules. : " + itos(errno));
358
		}
359
	}
360
 
361
	struct sigaction saio;
362
	saio.sa_handler = AsyncSocket::signalHandler;
363
	sigemptyset(&saio.sa_mask);
364
	saio.sa_flags = 0;
365
	saio.sa_restorer = NULL;
366
	sigaction(SIGIO, &saio, NULL);
367
 
368
	fcntl(mySocket, F_SETOWN, getpid());
369
	int flags = fcntl(mySocket, F_GETFL);
370
 
371
	if (flags < 0)
372
		throw new SocketException("Async socket fcntl failed");
373
 
374
	fcntl(mySocket, F_SETFL, flags | O_NONBLOCK | FASYNC);
974 runge 375
 
376
	myForceReconnect = false;
969 runge 377
}
378
 
379
void AsyncSocket::close()
380
{
381
	if (mySocket != -1)
382
	{
383
		::close(mySocket);
384
		mySocket = -1;
385
		setEvent(ASYNCSOCKET_EVENT_CLOSED);
386
	}
387
}
388
 
389
void AsyncSocket::setReconnectTimeout(unsigned int timeout)
390
{
391
	myReconnectTimeout = timeout;
392
}
393
 
394
void AsyncSocket::startEvent()
395
{
396
	myEventSemaphore.lock();
397
}
398
 
399
int AsyncSocket::getEvent()
400
{
401
	int event = myEvent;
402
	myEvent = ASYNCSOCKET_EVENT_NONE;
403
	return event;
404
}
405
 
406
void AsyncSocket::waitForEvent()
407
{
408
	myEventSemaphore.wait();
409
}
410
 
411
void AsyncSocket::stopEvent()
412
{
413
	myEventSemaphore.unlock();
414
}
415
 
416
void AsyncSocket::setAddress(string address, int port)
417
{
418
	myAddress = address;
419
	myPort = port;
420
}
421
 
422
bool AsyncSocket::availableData()
423
{
424
	return (myInQueue.size() > 0);
425
}
426
 
427
string AsyncSocket::getData()
428
{
429
	return myInQueue.pop();
430
}
431
 
432
bool AsyncSocket::sendData(string data)
433
{
434
	myOutQueue.push(data);
435
	mySemaphore.broadcast();
436
	return true;
437
}
438
 
439
void AsyncSocket::setEvent(int event)
440
{
441
	myEventSemaphore.lock();
442
	myEvent = event;
443
	myEventSemaphore.unlock();
444
	myEventSemaphore.broadcast();
445
}
446
 
974 runge 447
void AsyncSocket::forceReconnect()
448
{
449
	myForceReconnect = true;
450
	mySemaphore.broadcast();
451
}
452
 
969 runge 453
void AsyncSocket::signalHandler(int signum)
454
{
974 runge 455
	//FIXME: We must know which socket is ready to read by using select... 
969 runge 456
	//cout << "DEBUG: signalHandler signum: " << signum << endl;
457
	mySemaphore.broadcast();
458
}