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
}