File: socket.c

package info (click to toggle)
exonerate 2.2.0-6
  • links: PTS, VCS
  • area: main
  • in suites: jessie, jessie-kfreebsd, wheezy
  • size: 3,652 kB
  • ctags: 3,891
  • sloc: ansic: 40,966; sh: 3,257; makefile: 819
file content (353 lines) | stat: -rw-r--r-- 12,367 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
/****************************************************************\
*                                                                *
*  Simple client-server code library                             *
*                                                                *
*  Guy St.C. Slater..   mailto:guy@ebi.ac.uk                     *
*  Copyright (C) 2000-2008.  All Rights Reserved.                *
*                                                                *
*  This source code is distributed under the terms of the        *
*  GNU General Public License, version 3. See the file COPYING   *
*  or http://www.gnu.org/licenses/gpl.txt for details            *
*                                                                *
*  If you use this code, please keep this notice intact.         *
*                                                                *
\****************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <string.h>    /* For bcopy() */
#include <unistd.h>    /* For close() */
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/time.h>
#include <netinet/tcp.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <netdb.h>
#include <fcntl.h>
#include <signal.h>   /* For sigaction() */
#include <sys/wait.h> /* For wait()   */

#include "socket.h"

#define Socket_BUFSIZE BUFSIZ

static SocketConnection *SocketConnection_create(gchar *host,
                                                 gint  port){
    register SocketConnection *connection
     = g_new(SocketConnection, 1);
    register gint result;
    int flag = 1;
    if((connection->sock = socket(AF_INET, SOCK_STREAM, 0)) < 0){
        perror("opening stream socket");
        exit(1);
        }
    connection->host = g_strdup(host);
    connection->port = port;
    result = setsockopt(connection->sock, IPPROTO_TCP, TCP_NODELAY,
                                 (char*)&flag, sizeof(int));
    if(result < 0)
        perror("setting socket options");
    return connection;
    }

SocketClient *SocketClient_create(gchar *host, gint port){
    register SocketClient *client = g_new(SocketClient, 1);
    struct sockaddr_in server;
    struct hostent *hp = gethostbyname(host);
    register gchar *reply;
    if(!hp){
        perror("lookup up hostname");
        exit(1);
        }
    client->connection = SocketConnection_create(host, port);
#if 0
    /* Make non-blocking */
    if(fcntl(client->connection->sock, F_SETFL, O_NDELAY) == -1){
        perror("Tcp: Could not set O_NDELAY on socket with fcntl");
        exit(1);
        }
#endif /* 0 */
    server.sin_family = AF_INET;
    server.sin_port = htons(port);
    bcopy(hp->h_addr, &server.sin_addr, hp->h_length);
    if(connect(client->connection->sock, (struct sockaddr*)&server,
               sizeof(server)) < 0){
        perror("connecting client socket");
        exit(1);
        }
    /* Send a packet to the server to test the connection.
     * If we get any reply, the connection is OK.
     *
     * FIXME: There should be a better way of doing this,
     *        but I can't find a method that works portably.
     */
    reply = SocketClient_send(client, "ping");
    if(reply){
        g_free(reply);
    } else {
        SocketClient_destroy(client);
        return NULL;
        }
    return client;
    }

static void SocketConnection_destroy(SocketConnection *connection){
    if(close(connection->sock) == -1){
        perror("closing socket");
        exit(1);
        }
    g_free(connection->host);
    g_free(connection);
    return;
    }

static gchar *SocketConnection_read(gint sock){
    register gint i, len, line_complete = 0, line_expect = 1;
    register gchar *reply;
    register GString *string = g_string_sized_new(Socket_BUFSIZE);
    register gboolean line_count_given = FALSE;
    gchar buffer[Socket_BUFSIZE+1];
    do {
        if((len = recv(sock, buffer, Socket_BUFSIZE, 0)) <= 0){
            len = 0;
            break;
            }
        buffer[len] = '\0';
        for(i = 0; i < len; i++)
            if(buffer[i] == '\n')
                line_complete++;
        if((!string->len) && (len > 10))
            if(!strncmp(buffer, "linecount:", 10)){
                line_expect = atoi(&buffer[11]);
                line_count_given = TRUE;
                if(line_expect < 2)
                    g_error("linecount: must be > 1");
                }
        if(line_complete > line_expect){
            if(line_count_given)
                g_error("Received [%d] socket message lines, but expected [%d]",
                   line_complete, line_count_given);
            else
                g_error("Multiline socket messages must use linecount:");
            }
        g_string_append(string, buffer);
    } while(line_complete < line_expect);
    if(string->len){
        reply = string->str;
        g_string_free(string, FALSE);
        return reply;
        }
    g_string_free(string, TRUE);
    return NULL;
    }

static void Socket_send_msg(gint sock, gchar *msg, gchar *err_msg){
    register gint start = 0, len, msglen = strlen(msg);
    do {
        if((len = send(sock, msg+start, msglen-start, 0)) < 0){
            perror(err_msg);
            exit(1);
            }
        start += len;
    } while(start < msglen);
    return;
    }

static void Socket_send(gint sock, gchar *msg, gchar *err_msg){
    register gint i, line_count = 0;
    register GString *full_msg = g_string_sized_new(strlen(msg)+32);
    for(i = 0; msg[i]; i++)
        if(msg[i] == '\n')
            line_count++;
    if(line_count > 1)
        g_string_sprintfa(full_msg, "linecount: %d\n", line_count+1);
    g_string_sprintfa(full_msg, "%s", msg);
    if(!line_count)
        g_string_append_c(full_msg, '\n');
    Socket_send_msg(sock, full_msg->str, err_msg);
    g_string_free(full_msg, TRUE);
    return;
    }

gchar *SocketClient_send(SocketClient *client, gchar *msg){
    Socket_send(client->connection->sock, msg, "writing client message");
    return SocketConnection_read(client->connection->sock);
    }

void SocketClient_destroy(SocketClient *client){
    SocketConnection_destroy(client->connection);
    g_free(client);
    return;
    }

static gint global_connection_count = 0;

static void SocketServer_reap_dead_children(int signum){
    signal(SIGCHLD, SIG_IGN);
    while(waitpid(-1, NULL, WNOHANG) > 0)
        g_message("Cleaned up dead child process [%d]",
                  global_connection_count);
    signal(SIGCHLD, SocketServer_reap_dead_children);
    global_connection_count--;
    return;
    }

static void SocketServer_shutdown(int signum){
    g_message("Server shutting down");
    exit(0);
    return;
    }

SocketServer *SocketServer_create(gint port, gint max_connections,
              SocketProcessFunc server_process_func,
              SocketConnectionOpenFunc connection_open_func,
              SocketConnectionCloseFunc connection_close_func,
              gpointer user_data){
    register SocketServer *server = g_new(SocketServer, 1);
    struct sockaddr_in sock_server;
    socklen_t len = sizeof(sock_server);
    server->connection = SocketConnection_create("localhost", port);
    g_assert(server_process_func);
    server->server_process_func = server_process_func;
    server->connection_open_func = connection_open_func;
    server->connection_close_func = connection_close_func;
    server->user_data = user_data;
    server->max_connections = max_connections;
    sock_server.sin_family = AF_INET;
    sock_server.sin_addr.s_addr = INADDR_ANY;
    sock_server.sin_port = htons(port);
    if(bind(server->connection->sock,
            (struct sockaddr*)&sock_server, len)){
        perror("binding stream socket");
        exit(1);
        }
    if(getsockname(server->connection->sock,
                   (struct sockaddr*)&sock_server, &len)){
        perror("getting socket name");
        exit(1);
        }
    server->connection->port = ntohs(sock_server.sin_port);
    signal(SIGCHLD, SocketServer_reap_dead_children);
    signal(SIGTERM, SocketServer_shutdown);
#ifdef USE_PTHREADS
    server->sspd = g_new0(SocketServer_pthread_Data, max_connections);
    pthread_mutex_init(&server->connection_mutex, NULL);
#endif /* USE_PTHREADS */
    return server;
    }

static void SocketServer_process_connection(SocketServer *server, int msgsock){
    register gpointer connection_data = server->connection_open_func
                    ? server->connection_open_func(server->user_data)
                    : NULL;
    register gchar *msg;
    register gboolean ok = TRUE;
    gchar *reply;
    do {
        msg = SocketConnection_read(msgsock);
        reply = NULL;
        if(!msg)
            break;
        ok = server->server_process_func(msg, &reply, connection_data,
                                         server->user_data);
        g_free(msg);
        if(reply){
            Socket_send(msgsock, reply, "writing reply");
            g_free(reply);
            }
    } while(ok);
    if(server->connection_close_func)
        server->connection_close_func(connection_data, server->user_data);
    return;
    }

#ifdef USE_PTHREADS
static void *SocketServer_pthread_func(void* data){
    register SocketServer_pthread_Data *sspd = (SocketServer_pthread_Data*)data;
    SocketServer_process_connection(sspd->server, sspd->msgsock);
    pthread_mutex_lock(&sspd->server->connection_mutex);
    g_message("cleaning up connection [%d]", global_connection_count);
    global_connection_count--;
    close(sspd->msgsock);
    sspd->in_use = FALSE;
    pthread_mutex_unlock(&sspd->server->connection_mutex);
    pthread_exit(NULL);
    return NULL;
    }
#endif /* USE_PTHREADS */

gboolean SocketServer_listen(SocketServer *server){
    register int msgsock;
    register gint i;
    struct sockaddr_in client_addr;
    socklen_t client_len = sizeof(struct sockaddr_in);
#ifdef USE_PTHREADS
    pthread_attr_t pt_attr;
    pthread_attr_init(&pt_attr);
    pthread_attr_setdetachstate(&pt_attr, PTHREAD_CREATE_DETACHED);
#endif /* USE_PTHREADS */
    /**/
    listen(server->connection->sock, 5);
    msgsock = accept(server->connection->sock,
                     (struct sockaddr*)&client_addr, &client_len);
    if(msgsock == -1){
        perror("server accept");
        close(msgsock);
        exit(1);
        }
    /* FIXME: send an busy warning message back to the client ? */
#ifdef USE_PTHREADS
    pthread_mutex_lock(&server->connection_mutex);
    if(global_connection_count >= server->max_connections){
        pthread_mutex_unlock(&server->connection_mutex);
        g_message("Max connections reached");
    } else {
        for(i = 0; i < server->max_connections; i++)
            if(!server->sspd[i].in_use){
                server->sspd[i].in_use = TRUE;
                break;
                }
        server->sspd[i].server = server;
        server->sspd[i].msgsock = dup(msgsock);
        if(server->sspd[i].msgsock < 0)
            perror("duplicating socket for pthread");
        global_connection_count++;
        g_message("opened connection [%d/%d] from [%s]",
                   global_connection_count,
                   server->max_connections,
                   inet_ntoa(client_addr.sin_addr));
        pthread_mutex_unlock(&server->connection_mutex);
        pthread_create(&server->sspd[i].thread, &pt_attr,
                       SocketServer_pthread_func, (void*)&server->sspd[i]);
        }
#else /* USE_PTHREADS */
    if(global_connection_count >= server->max_connections){
        g_message("Max connections reached");
    } else {
        if(fork() == 0){
            SocketServer_process_connection(server, msgsock);
            exit(0);
        } else {
            global_connection_count++;
            g_message("opened connection [%d/%d] from [%s]",
                    global_connection_count,
                    server->max_connections,
                    inet_ntoa(client_addr.sin_addr));
            }
        }
#endif /* USE_PTHREADS */
    close(msgsock);
    return TRUE; /* Keep server running */
    }

void SocketServer_destroy(SocketServer *server){
    SocketConnection_destroy(server->connection);
#ifdef USE_PTHREADS
    g_free(server->sspd);
    pthread_mutex_destroy(&server->connection_mutex);
#endif /* USE_PTHREADS */
    g_free(server);
    return;
    }