Subversion Repositories HomeAutomation

Rev

Go to most recent revision | 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;
30
 
31
	Thread<AsyncSocket>();
32
}
33
 
34
AsyncSocket::~AsyncSocket()
35
{
36
	if (mySocket != -1)
37
	{
38
		::close(mySocket);
39
		mySocket = -1;
40
	}
41
 
42
	stop();
43
}
44
 
45
void AsyncSocket::run()
46
{
47
	SyslogStream &slog = SyslogStream::getInstance();
48
 
49
	reconnectLoop();
50
 
51
	char buf[MAXBUFFER + 1];
52
	string data;
53
	int status;
54
 
55
	AsyncSocket::mySemaphore.lock();
56
 
57
	try
58
	{
59
		while (1)
60
		{
61
			mySemaphore.wait();
62
 
63
			//cout << "Socket awoken...\n";
64
 
65
			while (myOutQueue.size() > 0)
66
			{
67
				data = myOutQueue.pop();
68
 
69
				//slog << "Sending: " << data << "\n";
70
 
71
				status = ::send(mySocket, data.c_str(), data.size(), 0);
72
 
73
				if (status == -1)
74
				{
75
					switch (errno)
76
					{
77
						case EACCES:
78
						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).)");
79
 
80
						case EAGAIN:
81
						//slog << "The socket is marked non-blocking and the requested operation would block.\n";
82
						break;
83
 
84
						case EBADF:
85
						throw new SocketException("An invalid descriptor was specified.");
86
 
87
						case ECONNRESET:
88
						throw new SocketException("Connection reset by peer.");
89
 
90
						case EDESTADDRREQ:
91
						throw new SocketException("The socket is not connection-mode, and no peer address is set.");
92
 
93
						case EFAULT:
94
						throw new SocketException("An invalid user space address was specified for an argument.");
95
 
96
						case EINTR:
97
						throw new SocketException("A signal occurred before any data was transmitted; see signal(7).");
98
 
99
						case EINVAL:
100
						throw new SocketException("Invalid argument passed.");
101
 
102
						case EISCONN:
103
						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.)");
104
 
105
						case EMSGSIZE:
106
						throw new SocketException("The socket type requires that message be sent atomically, and the size of the message to be sent made this impossible.");
107
 
108
						case ENOBUFS:
109
						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.)");
110
 
111
						case ENOMEM:
112
						throw new SocketException("No memory available.");
113
 
114
						case ENOTCONN:
115
						throw new SocketException("The socket is not connected, and no target has been given.");
116
 
117
						case ENOTSOCK:
118
						throw new SocketException("The argument s is not a socket.");
119
 
120
						case EOPNOTSUPP:
121
						throw new SocketException("Some bit in the flags argument is inappropriate for the socket type.");
122
 
123
						case EPIPE:
124
						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.");
125
 
126
						default:
127
						slog << "Unknow exception: " << errno << "\n";
128
						break;
129
					}
130
				}
131
			}
132
 
133
			memset(buf, 0, MAXBUFFER + 1);
134
 
135
			status = ::recv(mySocket, buf, MAXBUFFER, 0);
136
 
137
			if (status == -1)
138
			{
139
				switch (errno)
140
				{
141
					case EAGAIN:
142
					//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";
143
					break;
144
 
145
					case EBADF:
146
					throw new SocketException("The argument s is an invalid descriptor.");
147
 
148
					case ECONNREFUSED:
149
					throw new SocketException("A remote host refused to allow the network connection (typically because it is not running the requested service).");
150
 
151
					case EFAULT:
152
					throw new SocketException("The receive buffer pointer(s) point outside the process's address space.");
153
 
154
					case EINTR:
155
					throw new SocketException("The receive was interrupted by delivery of a signal before any data were available; see signal(7).");
156
 
157
					case EINVAL:
158
					throw new SocketException("Invalid argument passed.");
159
 
160
					case ENOMEM:
161
					throw new SocketException("Could not allocate memory for recvmsg().");
162
 
163
					case ENOTCONN:
164
					throw new SocketException("The socket is associated with a connection-oriented protocol and has not been connected (see connect(2) and  accept(2)).");
165
 
166
					case ENOTSOCK:
167
					throw new SocketException("The argument s does not refer to a socket.");
168
 
169
					default:
170
					slog << "Unknow exception: " << errno << "\n";
171
					break;
172
				}
173
			}
174
			else if (status == 0)
175
			{
176
				slog << "Disconnected from server.\n";
177
				reconnectLoop();
178
			}
179
			else if (status > 0)
180
			{
181
				data = buf;
182
 
183
				myInQueue.push(data);
184
 
185
				setEvent(ASYNCSOCKET_EVENT_DATA);
186
			}
187
		}
188
	}
189
	catch (SocketException *e)
190
	{
191
		slog << "Exception: " << e->getDescription() << "\n";
192
		mySemaphore.unlock();
193
		setEvent(ASYNCSOCKET_EVENT_DIED);
194
		stop();
195
	}
196
 
197
	close();
198
 
199
	mySemaphore.unlock();
200
}
201
 
202
void AsyncSocket::reconnectLoop()
203
{
204
	SyslogStream &slog = SyslogStream::getInstance();
205
 
206
	while (1)
207
	{
208
		try
209
		{
210
			slog << "Trying to connect...\n";
211
			connect();
212
			slog << "Connection established.\n";
213
			break;
214
		}
215
		catch (SocketException *e)
216
		{
217
			slog << "Could not connect: " << e->getDescription() << "\n";
218
			slog << "Will try again in " << myReconnectTimeout << " seconds\n";
219
			sleep(myReconnectTimeout);
220
		}
221
	}
222
}
223
 
224
void AsyncSocket::connect()
225
{
226
	if (mySocket != -1)
227
	{
228
		::close(mySocket);
229
		mySocket = -1;
230
	}
231
 
232
	mySocket = ::socket(AF_INET, SOCK_STREAM, 0);
233
 
234
	// TIME_WAIT - argh
235
	int on = 1;
236
	int status = setsockopt(mySocket, SOL_SOCKET, SO_REUSEADDR, (const char*)&on, sizeof(on));
237
	if (status == -1)
238
	{
239
		throw new SocketException("Connect: " + itos(errno));
240
	}
241
 
242
	memset(&myAddressStruct, 0, sizeof(myAddressStruct));
243
 
244
	myAddressStruct.sin_family = AF_INET;
245
	myAddressStruct.sin_port = htons(myPort);
246
 
247
	status = inet_pton(AF_INET, myAddress.c_str(), &myAddressStruct.sin_addr);
248
 
249
	if (status == -1)
250
	{
251
		if (errno == EAFNOSUPPORT)
252
			throw new SocketException("Connect: EAFNOSUPPORT");
253
	}
254
 
255
	status = ::connect(mySocket, (sockaddr*)&myAddressStruct, sizeof(myAddressStruct));
256
 
257
	if (status == -1)
258
	{
259
		switch (errno)
260
		{
261
			case EACCES:
262
			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).)");
263
 
264
			case EPERM:
265
			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.");
266
 
267
			case EADDRINUSE:
268
			throw new SocketException("Local address is already in use.");
269
 
270
			case EAFNOSUPPORT:
271
			throw new SocketException("The passed address didn't have the correct address family in its sa_family field.");
272
 
273
			case EAGAIN:
274
			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.");
275
 
276
			case EALREADY:
277
			throw new SocketException("The socket is non-blocking and a previous connection attempt has not yet been completed.");
278
 
279
			case EBADF:
280
			throw new SocketException("The file descriptor is not a valid index in the descriptor table.");
281
 
282
			case ECONNREFUSED:
283
			throw new SocketException("No-one listening on the remote address.");
284
 
285
			case EFAULT:
286
			throw new SocketException("The socket structure address is outside the user's address space.");
287
 
288
			case EINPROGRESS:
289
			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).");
290
 
291
			case EINTR:
292
			throw new SocketException("The system call was interrupted by a signal that was caught; see signal(7).");
293
 
294
			case EISCONN:
295
			throw new SocketException("The socket is already connected.");
296
 
297
			case ENETUNREACH:
298
			throw new SocketException("Network is unreachable.");
299
 
300
			case ENOTSOCK:
301
			throw new SocketException("The file descriptor is not associated with a socket.");
302
 
303
			case ETIMEDOUT:
304
			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.");
305
 
306
			default:
307
			throw new SocketException("Other errors may be generated by the underlying protocol modules. : " + itos(errno));
308
		}
309
	}
310
 
311
	struct sigaction saio;
312
	saio.sa_handler = AsyncSocket::signalHandler;
313
	sigemptyset(&saio.sa_mask);
314
	saio.sa_flags = 0;
315
	saio.sa_restorer = NULL;
316
	sigaction(SIGIO, &saio, NULL);
317
 
318
	fcntl(mySocket, F_SETOWN, getpid());
319
	int flags = fcntl(mySocket, F_GETFL);
320
 
321
	if (flags < 0)
322
		throw new SocketException("Async socket fcntl failed");
323
 
324
	fcntl(mySocket, F_SETFL, flags | O_NONBLOCK | FASYNC);
325
}
326
 
327
void AsyncSocket::close()
328
{
329
	if (mySocket != -1)
330
	{
331
		::close(mySocket);
332
		mySocket = -1;
333
		setEvent(ASYNCSOCKET_EVENT_CLOSED);
334
	}
335
}
336
 
337
void AsyncSocket::setReconnectTimeout(unsigned int timeout)
338
{
339
	myReconnectTimeout = timeout;
340
}
341
 
342
void AsyncSocket::startEvent()
343
{
344
	myEventSemaphore.lock();
345
}
346
 
347
int AsyncSocket::getEvent()
348
{
349
	int event = myEvent;
350
	myEvent = ASYNCSOCKET_EVENT_NONE;
351
	return event;
352
}
353
 
354
void AsyncSocket::waitForEvent()
355
{
356
	myEventSemaphore.wait();
357
}
358
 
359
void AsyncSocket::stopEvent()
360
{
361
	myEventSemaphore.unlock();
362
}
363
 
364
void AsyncSocket::setAddress(string address, int port)
365
{
366
	myAddress = address;
367
	myPort = port;
368
}
369
 
370
bool AsyncSocket::availableData()
371
{
372
	return (myInQueue.size() > 0);
373
}
374
 
375
string AsyncSocket::getData()
376
{
377
	return myInQueue.pop();
378
}
379
 
380
bool AsyncSocket::sendData(string data)
381
{
382
	myOutQueue.push(data);
383
	mySemaphore.broadcast();
384
	return true;
385
}
386
 
387
void AsyncSocket::setEvent(int event)
388
{
389
	myEventSemaphore.lock();
390
	myEvent = event;
391
	myEventSemaphore.unlock();
392
	myEventSemaphore.broadcast();
393
}
394
 
395
void AsyncSocket::signalHandler(int signum)
396
{
397
	//cout << "DEBUG: signalHandler signum: " << signum << endl;
398
	mySemaphore.broadcast();
399
}