Project

General

Profile

Statistics
| Revision:

root / trunk / code / projects / colonet / ColonetServer / ConnectionPool.cpp @ 29

History | View | Annotate | Download (14 KB)

1 11 emarinel
/**
2 29 jknichel
 * @file ConnectionPool.cpp
3
 *
4 11 emarinel
 * @author Jason Knichel
5
 * @date 7/22/07
6
 */
7
8
#include <sys/select.h>
9
#include <ctype.h>
10
#include <errno.h>
11
#include <unistd.h>
12
#include <string.h>
13
#include <stdlib.h>
14
#include <stdio.h>
15
16
#include "includes/ConnectionPool.h"
17
#include "includes/client.h"
18
#include "../lib/colonet_defs.h"
19
20 29 jknichel
/**
21
 * @brief The default constructor for ConnectionPool
22
 */
23 11 emarinel
ConnectionPool::ConnectionPool() {
24
  max_file_descriptor = 0;
25
  next_available_slot = 0;
26
  number_clients_ready = 0;
27
28
  FD_ZERO(&ready_set);
29
  FD_ZERO(&read_set);
30
  FD_ZERO(&write_set);
31
32
  memset(&client_file_descriptor_array, 0, sizeof(int)*MAX_CONNECTIONS);
33
  memset(&read_buffer, 0, sizeof(char *)*MAX_CONNECTIONS);
34
  memset(&read_buffer_size, 0, sizeof(int)*MAX_CONNECTIONS);
35
  memset(&write_buffer, 0, sizeof(char *)*MAX_CONNECTIONS);
36
  memset(&write_buffer_size, 0, sizeof(int)*MAX_CONNECTIONS);
37
}
38
39 29 jknichel
/**
40
 * @brief The destructor for ConnectionPool
41
 */
42 11 emarinel
ConnectionPool::~ConnectionPool() {
43
}
44
45 29 jknichel
/**
46
 * @brief Adds a client to the connection pool
47
 *
48
 * @param client_file_descriptor The file descriptor to add to the connection pool
49
 *
50
 * @return 0 on success, negative error code on failure
51
 */
52 11 emarinel
int ConnectionPool::add_client(int client_file_descriptor) {
53
  if (client_file_descriptor < 0) {
54
    return ERROR_INVALID_CLIENT_DESCRIPTOR;
55
  }
56
57
  if (next_available_slot == MAX_CONNECTIONS) {
58
    return ERROR_TOO_MANY_CLIENTS;
59
  }
60
61
  if (client_file_descriptor > max_file_descriptor) {
62
    max_file_descriptor = client_file_descriptor;
63
  }
64
65
  FD_SET(client_file_descriptor, &ready_set);
66
67
  int next_slot = next_available_slot;
68
69
  client_file_descriptor_array[next_slot] = client_file_descriptor;
70
  read_buffer[next_slot] = (char*) malloc(sizeof(char) * READ_BUFFER_SIZE);
71
  if (!(read_buffer[next_slot])) {
72
    return ERROR_ALLOCATING_MEMORY;
73
  }
74
  read_buffer_size[next_slot] = 0;
75
  write_buffer[next_slot] = (char *)malloc(sizeof(char) * WRITE_BUFFER_SIZE);
76
77
  if (!(write_buffer[next_slot])) {
78
    free(read_buffer[next_slot]);
79
    return ERROR_ALLOCATING_MEMORY;
80
  }
81
82
  write_buffer_size[next_slot] = 0;
83
84
  next_available_slot++;
85
86
  return 0;
87
}
88
89 29 jknichel
/**
90
 * @brief Removes a client from the connection pool
91
 *
92
 * @param The index in the pool of the client to remove
93
 *
94
 * @return 0 on success, negative error code on failure
95
 */
96 27 jknichel
int ConnectionPool::remove_client(int pool_index) {
97
  if (pool_index < 0 || pool_index >= next_available_slot) {
98 11 emarinel
    return ERROR_INVALID_CLIENT_DESCRIPTOR;
99
  }
100
101 27 jknichel
  int client_file_descriptor = client_file_descriptor_array[pool_index];
102 11 emarinel
103 27 jknichel
  if (FD_ISSET(client_file_descriptor, &ready_set)) {
104
    FD_CLR(client_file_descriptor, &ready_set);
105 11 emarinel
  }
106 27 jknichel
  if (FD_ISSET(client_file_descriptor, &read_set)) {
107
    FD_CLR(client_file_descriptor, &read_set);
108 11 emarinel
  }
109 27 jknichel
  if (FD_ISSET(client_file_descriptor, &write_set)) {
110
    FD_CLR(client_file_descriptor, &write_set);
111 11 emarinel
  }
112
113 27 jknichel
  free(read_buffer[pool_index]);
114
  free(write_buffer[pool_index]);
115
  for (int j = pool_index; j < next_available_slot - 1; j++) {
116
    client_file_descriptor_array[pool_index] = client_file_descriptor_array[pool_index+1];
117
    read_buffer[pool_index] = read_buffer[pool_index+1];
118
    read_buffer_size[pool_index] = read_buffer_size[pool_index+1];
119
    write_buffer[pool_index] = write_buffer[pool_index+1];
120
    write_buffer_size[pool_index] = write_buffer_size[pool_index+1];
121 11 emarinel
  }
122
  next_available_slot--;
123
  int temp_max_file_descriptor = 0;
124
125
  for (int j = 0; j < next_available_slot; j++) {
126
    if (client_file_descriptor_array[j] > temp_max_file_descriptor)
127
      temp_max_file_descriptor = client_file_descriptor_array[j];
128
  }
129
  max_file_descriptor = temp_max_file_descriptor;
130
131
  printf("Removing client.\n");
132
133
  return 0;
134
}
135
136 29 jknichel
/**
137
 * @brief Checks the status of the clients
138
 *
139
 * Sees is any clients are ready to read from their file descriptor or are
140
 *  ready to write to their file descriptor.
141
 *
142
 * @param wireless A pointer to the wireless object
143
 *
144
 * @return 0 on success, negative error code on error
145
 */
146 11 emarinel
//TODO: test that it drops commands properly if it gets sent too much data
147
//      do we want it to drop the data or drop the connection?
148 22 jknichel
int ConnectionPool::check_clients(ColonetWireless * wireless) {
149 27 jknichel
  char temporary_buffer[READ_BUFFER_SIZE];
150
  char temporary_command_buffer[READ_BUFFER_SIZE+1];
151 11 emarinel
  int i;
152
  int client_file_descriptor;
153 27 jknichel
  int num_bytes_read;
154
  int length;
155 11 emarinel
  int sent;
156 27 jknichel
  int command_length;
157 11 emarinel
158
  for (i = 0; i < next_available_slot; i++) {
159
    client_file_descriptor = client_file_descriptor_array[i];
160
161
    if (FD_ISSET(client_file_descriptor, &read_set)) {
162 27 jknichel
      num_bytes_read = read(client_file_descriptor, temporary_buffer, READ_BUFFER_SIZE);
163 11 emarinel
164 27 jknichel
      if (num_bytes_read == 0 || (num_bytes_read == -1 && errno == ECONNRESET)) {
165 11 emarinel
        remove_client(i);
166
        i--;
167
        continue;
168
      }
169
170 27 jknichel
      while (num_bytes_read > 0) {
171
        length = num_bytes_read;
172 11 emarinel
173 27 jknichel
        if (length + read_buffer_size[i] > READ_BUFFER_SIZE) {
174
          length = READ_BUFFER_SIZE - read_buffer_size[i];
175 11 emarinel
        }
176
177 27 jknichel
        memcpy(read_buffer[i]+read_buffer_size[i], temporary_buffer, length);
178
        read_buffer_size[i] += length;
179
        num_bytes_read -= length;
180 11 emarinel
181 27 jknichel
        if (num_bytes_read > 0) {
182
          memmove(temporary_buffer, temporary_buffer+length, READ_BUFFER_SIZE - length);
183 11 emarinel
        }
184
185
        printf("Read buffer is %s\n", read_buffer[i]);
186
187 27 jknichel
        char* newline_position;
188 11 emarinel
189 27 jknichel
        while ((newline_position = strstr(read_buffer[i], "\n"))) {
190 11 emarinel
191
          //if no newline if found in the entire readbuffer (when its full),
192
          //toss out the command
193
          // because either the command being given is too long or someone is trying
194
          // to do something bad to the server
195
          //TODO: this is from before all this code was put in the loop.  reconsider
196
          //      how to check this error condition and do it elsewhere
197 27 jknichel
          if (!newline_position && (read_buffer_size[i] == READ_BUFFER_SIZE)) {
198 11 emarinel
            read_buffer_size[i] = 0;
199
            break;
200
          }
201
202
          //if no newline is found then there is not a command in the buffer
203 27 jknichel
          if (!newline_position) {
204 11 emarinel
            break;
205
          }
206
207 27 jknichel
          command_length = (newline_position - read_buffer[i])+1;
208 11 emarinel
209
          //the newline was found in garbage in the currently not used portion
210
          // of the read buffer
211 27 jknichel
          if (command_length > read_buffer_size[i]) {
212 11 emarinel
            break;
213
          }
214
215 27 jknichel
          memcpy(temporary_command_buffer, read_buffer[i], command_length);
216
          //do command_length-1 to get rid of the newline terminating the command
217
          temporary_command_buffer[command_length-1] = '\0';
218 11 emarinel
          //did this because telnet was putting a \r\n on the end instead of just \n
219 27 jknichel
          if (isspace(temporary_command_buffer[command_length-2])) {
220
            temporary_command_buffer[command_length-2] = '\0';
221 11 emarinel
          }
222
223 27 jknichel
          memmove(read_buffer[i], read_buffer[i]+command_length, read_buffer_size[i] - command_length);
224
          read_buffer_size[i] -= command_length;
225 11 emarinel
226 27 jknichel
          if (command_length > MAX_COMMAND_LEN) {
227 11 emarinel
            printf("The command was too long.  Tossing command out.\n");
228
            break;
229
          }
230
231 27 jknichel
          if (parse_command(temporary_command_buffer, i, wireless) < 0) {
232 11 emarinel
            printf("There was an error parsing command\n");
233
            break;
234
          }
235
        }
236
      }
237
    }
238
239
    if (FD_ISSET(client_file_descriptor, &write_set)) {
240
      if (write_buffer_size[i] == 0) {
241
        continue;
242
      }
243
244
      sent = write(client_file_descriptor, write_buffer[i], write_buffer_size[i]);
245
      memmove(write_buffer[i], write_buffer[i]+sent, WRITE_BUFFER_SIZE - sent);
246
      write_buffer_size[i] -= sent;
247
    }
248
  }
249
250
  return 0;
251
}
252
253 29 jknichel
/**
254
 * @brief Puts text into a write buffer that will be written to a client's file
255
 *  descriptor sometime when the client is ready to write.
256
 *
257
 * @param pool_index Index in the pool of the client to write to
258
 * @param message The message to be written
259
 * @param length The length of the message
260
 *
261
 * @return 0 on success, negative error code on failure
262
 */
263 14 jknichel
int ConnectionPool::write_to_client(int pool_index, char * message, int length) {
264 22 jknichel
  if (pool_index < 0 || pool_index >= next_available_slot) {
265
    return ERROR_INVALID_CLIENT_ID;
266
  }
267
268
  if (!message) {
269
    return ERROR_INVALID_MESSAGE;
270
  }
271
272
  if (length < 0) {
273
    return ERROR_INVALID_MESSAGE_LENGTH;
274
  }
275
276 14 jknichel
  if (length > (WRITE_BUFFER_SIZE-write_buffer_size[pool_index]))
277
    {
278
      //TODO: make this a logging statement instead of a print statement
279
      printf("There is not enough room in the write buffer to send the data to the client.\n");
280 22 jknichel
      return ERROR_NOT_ENOUGH_ROOM;
281 14 jknichel
    }
282 11 emarinel
283 14 jknichel
  memcpy(write_buffer[pool_index], message, length);
284
  write_buffer_size[pool_index] += length;
285
286
  return 0;
287
}
288
289 29 jknichel
/**
290
 * @brief Sets the socket to listen on
291
 *
292
 * @param listen_socket The socket to listen on
293
 *
294
 * @return void
295
 */
296 11 emarinel
//TODO: put error checking on listen_socket to make sure its a valid socket
297
void ConnectionPool::set_listen_socket_in_ready_set(int listen_socket) {
298
  FD_SET(listen_socket, &ready_set);
299
}
300
301 29 jknichel
/**
302
 * @todo Since the listen socket is now a member of the class, the first parameter can be removed
303
 * @todo Does the select timeout need to be passed in?  or can it be specified in the method?
304
 *
305
 * @brief Find out what file descriptors are ready to write to and read from
306
 *
307
 * @param listen_socket The socket to listen on
308
 * @param select_timeout The timeout for the select statement
309
 *
310
 * @return 0
311
 */
312 11 emarinel
int ConnectionPool::perform_select(int listen_socket, struct timeval * select_timeout) {
313
  read_set = ready_set;
314
  write_set = ready_set;
315
316
  //TODO(Jason): think about why I put this there
317
  if (max_file_descriptor < listen_socket)
318
    max_file_descriptor = listen_socket;
319
320
  number_clients_ready = select(max_file_descriptor+1, &(read_set), &(write_set), NULL, select_timeout);
321
322
  return 0;
323
}
324
325
int ConnectionPool::is_socket_ready_to_read(int socket) {
326
  return FD_ISSET(socket, &read_set);
327
}
328
329
int ConnectionPool::is_socket_ready_to_write(int socket) {
330
  return FD_ISSET(socket, &write_set);
331
}
332
333
int ConnectionPool::get_max_file_descriptor() {
334
  return max_file_descriptor;
335
}
336
337
void ConnectionPool::set_max_file_descriptor(int new_max_file_descriptor) {
338
  max_file_descriptor = new_max_file_descriptor;
339
}
340
341
int ConnectionPool::get_next_available_slot() {
342
  return next_available_slot;
343
}
344
345
int ConnectionPool::get_number_clients_ready() {
346
  return number_clients_ready;
347
}
348
349
void ConnectionPool::set_number_clients_ready(int new_number_clients_ready) {
350
  number_clients_ready = new_number_clients_ready;
351
}
352
353
fd_set ConnectionPool::get_ready_set() {
354
  return ready_set;
355
}
356
357
fd_set ConnectionPool::get_read_set() {
358
  return read_set;
359
}
360
361
void ConnectionPool::set_read_set(fd_set new_set) {
362
  read_set = new_set;
363
}
364
365
fd_set ConnectionPool::get_write_set() {
366
  return write_set;
367
}
368
369
void ConnectionPool::set_write_set(fd_set new_set) {
370
  write_set = new_set;
371
}
372
373
//TODO: write a function to write data into the write buffers
374
//TODO: fix names of variables to obey new style
375 22 jknichel
int ConnectionPool::parse_command(char* command, int pool_index, ColonetWireless * wireless) {
376 11 emarinel
  char tokens[MAX_TOKENS][MAX_TOKEN_SIZE];
377 29 jknichel
  unsigned cnhar int_tokens[MAX_TOKENS];
378 11 emarinel
  int numTokens = 0;
379
  char* endptr = NULL;
380
  int commandID;
381
  int i;
382
  unsigned char args[PACKET_DATA_LEN];
383
384
  memset(args, 1, PACKET_DATA_LEN);
385
386
  if (!command) {
387
    return -1;
388
  }
389
390
  if (pool_index < 0) {
391
    return -1;
392
  }
393
394
  if (pool_index >= next_available_slot) {
395
    return -1;
396
  }
397
398
  if ((numTokens = tokenize_command(command, tokens)) < 0) {
399
    return -1;
400
  }
401
402
  //the 10 in the function call indicates number is base 10
403
  commandID = strtol(tokens[0], &endptr, 10);
404
405
  if (!endptr || *endptr != '\0') {
406
    printf("There was an error converting first token into a number.\n");
407
    return -1;
408
  }
409
410
  if (commandID == SEND_TO_ROBOT || REQUEST_FROM_ROBOT) {
411
    int numIntTokens = numTokens;
412
413
    /* Convert tokens to ints */
414
    for (i = ROBOT_COMMAND_OFFSET; i < numIntTokens; i++) {
415
      int_tokens[i-ROBOT_COMMAND_OFFSET] = atoi(tokens[i]);
416
    }
417
    if (commandID == REQUEST_FROM_ROBOT) {
418
      if (i < MAX_TOKENS) {
419
        for (;i >= 4; i--) {
420
          int_tokens[i] = int_tokens[i-1];
421
        }
422
        int_tokens[3] = pool_index;
423
        numIntTokens++;
424
      } else {
425
        //TODO: send an error back to client here also
426
        fprintf(stderr, "Client attempted to request data from the robot but there was not enough room to put in the client's id.\n");
427
        return -1;
428
      }
429
    }
430
431
    /* Fill args buffer with args */
432
    for (i = ROBOT_COMMAND_LEN; i < numIntTokens-ROBOT_COMMAND_OFFSET; i++) {
433
      args[i-ROBOT_COMMAND_LEN] = int_tokens[i];
434
    }
435
436
    /* Check the tokens */
437
    if (check_tokens(int_tokens, numIntTokens) < 0) {
438
      fprintf(stderr, "%s: Error - Invalid command/request.\n", __FUNCTION__);
439
      return 0;
440
    }
441
442
    /* Send packet to robot */
443
    fprintf(stderr, "Calling wireless->send(%d, %d, %d, args)\n",
444
            int_tokens[0], int_tokens[1], int_tokens[2]);
445 22 jknichel
    wireless->send(int_tokens[0], int_tokens[1], int_tokens[2], args);
446 11 emarinel
  }
447
448
  return 0;
449
}
450
451
int ConnectionPool::tokenize_command(char* command, char tokens[MAX_TOKENS][MAX_TOKEN_SIZE]) {
452
  char* nextToken = command;
453
  char* endToken = NULL;
454
  int numTokens = 0;
455
456
  if (!command) {
457
    return -1;
458
  }
459
460
  while ((endToken = strstr(nextToken, " "))) {
461
    *endToken = '\0';
462
463
    if (strlen(nextToken) > MAX_TOKEN_SIZE-1) {
464
      return -1;
465
    }
466
467
    strcpy(tokens[numTokens], nextToken);
468
469
    numTokens++;
470
    nextToken = endToken + 1;
471
472
    while (isspace(*nextToken) && *nextToken != '\0') {
473
      nextToken++;
474
    }
475
  }
476
477
  if (endToken == NULL && *nextToken != '\0') {
478
    if (strlen(nextToken) > MAX_TOKEN_SIZE-1) {
479
      return -1;
480
    }
481
482
    strcpy(tokens[numTokens], nextToken);
483
484
    numTokens++;
485
  }
486
487
  return numTokens;
488
}
489
490
/** @brief checks a list of tokens to see if it's valid
491
 *
492
 * @return 0 if tokens is valid
493
 */
494
int ConnectionPool::check_tokens(unsigned char* tokens, int numTokens) {
495
  if (numTokens > 3 + PACKET_DATA_LEN) {
496
    /* Too many tokens */
497
    return -1;
498
  }
499
500
  if (numTokens < 3) {
501
    /* Not enough tokens */
502
    return -1;
503
  }
504
505
  if (tokens[1] != COLONET_REQUEST && tokens[1] != COLONET_COMMAND) {
506
    /* Invalid message type */
507
    return -1;
508
  }
509
510
  return 0;
511
}