Project

General

Profile

Statistics
| Revision:

root / trunk / code / projects / colonet / server / ConnectionPool.cpp @ 681

History | View | Annotate | Download (12.1 KB)

1 11 emarinel
/**
2 29 jknichel
 * @file ConnectionPool.cpp
3
 *
4 11 emarinel
 * @author Jason Knichel
5
 * @date 7/22/07
6 144 jknichel
 *
7 11 emarinel
 */
8
9
#include <sys/select.h>
10
#include <ctype.h>
11
#include <errno.h>
12
#include <unistd.h>
13
#include <string.h>
14
#include <stdlib.h>
15
#include <stdio.h>
16
17 391 emarinel
#include <ConnectionPool.h>
18
#include <Command.h>
19
#include <colonet_defs.h>
20 161 emarinel
21 118 emarinel
#include <colonet_wireless.h>
22 11 emarinel
23 29 jknichel
/**
24
 * @brief The default constructor for ConnectionPool
25 642 jknichel
 *
26
 * @param cs The colonet server instance to use with this class
27 29 jknichel
 */
28 453 emarinel
ConnectionPool::ConnectionPool(ColonetServer* cs) {
29
  colonet_server = cs;
30
31 11 emarinel
  max_file_descriptor = 0;
32
  next_available_slot = 0;
33
  number_clients_ready = 0;
34
35
  FD_ZERO(&ready_set);
36
  FD_ZERO(&read_set);
37
  FD_ZERO(&write_set);
38
39
  memset(&client_file_descriptor_array, 0, sizeof(int)*MAX_CONNECTIONS);
40
  memset(&read_buffer, 0, sizeof(char *)*MAX_CONNECTIONS);
41
  memset(&read_buffer_size, 0, sizeof(int)*MAX_CONNECTIONS);
42
  memset(&write_buffer, 0, sizeof(char *)*MAX_CONNECTIONS);
43
  memset(&write_buffer_size, 0, sizeof(int)*MAX_CONNECTIONS);
44
}
45
46 29 jknichel
/**
47
 * @brief The destructor for ConnectionPool
48
 */
49 11 emarinel
ConnectionPool::~ConnectionPool() {
50
}
51
52 29 jknichel
/**
53
 * @brief Adds a client to the connection pool
54
 *
55
 * @param client_file_descriptor The file descriptor to add to the connection pool
56
 *
57
 * @return 0 on success, negative error code on failure
58
 */
59 11 emarinel
int ConnectionPool::add_client(int client_file_descriptor) {
60
  if (client_file_descriptor < 0) {
61
    return ERROR_INVALID_CLIENT_DESCRIPTOR;
62
  }
63
64
  if (next_available_slot == MAX_CONNECTIONS) {
65
    return ERROR_TOO_MANY_CLIENTS;
66
  }
67
68
  if (client_file_descriptor > max_file_descriptor) {
69
    max_file_descriptor = client_file_descriptor;
70
  }
71
72 642 jknichel
        //add the new file descriptor to the set of ready descriptors
73 11 emarinel
  FD_SET(client_file_descriptor, &ready_set);
74
75
  int next_slot = next_available_slot;
76
77 642 jknichel
        //put the new client into the connection pool
78 11 emarinel
  client_file_descriptor_array[next_slot] = client_file_descriptor;
79
  read_buffer[next_slot] = (char*) malloc(sizeof(char) * READ_BUFFER_SIZE);
80
  if (!(read_buffer[next_slot])) {
81
    return ERROR_ALLOCATING_MEMORY;
82
  }
83
  read_buffer_size[next_slot] = 0;
84 642 jknichel
85 11 emarinel
  write_buffer[next_slot] = (char *)malloc(sizeof(char) * WRITE_BUFFER_SIZE);
86
  if (!(write_buffer[next_slot])) {
87
    free(read_buffer[next_slot]);
88
    return ERROR_ALLOCATING_MEMORY;
89
  }
90
  write_buffer_size[next_slot] = 0;
91
92
  next_available_slot++;
93
94
  return 0;
95
}
96
97 29 jknichel
/**
98
 * @brief Removes a client from the connection pool
99
 *
100
 * @param The index in the pool of the client to remove
101
 *
102
 * @return 0 on success, negative error code on failure
103
 */
104 27 jknichel
int ConnectionPool::remove_client(int pool_index) {
105
  if (pool_index < 0 || pool_index >= next_available_slot) {
106 11 emarinel
    return ERROR_INVALID_CLIENT_DESCRIPTOR;
107
  }
108
109 27 jknichel
  int client_file_descriptor = client_file_descriptor_array[pool_index];
110 11 emarinel
111 642 jknichel
        //clear this client from all lists of file descriptors
112 27 jknichel
  if (FD_ISSET(client_file_descriptor, &ready_set)) {
113
    FD_CLR(client_file_descriptor, &ready_set);
114 11 emarinel
  }
115 27 jknichel
  if (FD_ISSET(client_file_descriptor, &read_set)) {
116
    FD_CLR(client_file_descriptor, &read_set);
117 11 emarinel
  }
118 27 jknichel
  if (FD_ISSET(client_file_descriptor, &write_set)) {
119
    FD_CLR(client_file_descriptor, &write_set);
120 11 emarinel
  }
121
122 642 jknichel
        //free up the resources this client used
123 27 jknichel
  free(read_buffer[pool_index]);
124
  free(write_buffer[pool_index]);
125 642 jknichel
126
        //shift the other clients down
127 27 jknichel
  for (int j = pool_index; j < next_available_slot - 1; j++) {
128
    client_file_descriptor_array[pool_index] = client_file_descriptor_array[pool_index+1];
129
    read_buffer[pool_index] = read_buffer[pool_index+1];
130
    read_buffer_size[pool_index] = read_buffer_size[pool_index+1];
131
    write_buffer[pool_index] = write_buffer[pool_index+1];
132
    write_buffer_size[pool_index] = write_buffer_size[pool_index+1];
133 11 emarinel
  }
134
  next_available_slot--;
135
  int temp_max_file_descriptor = 0;
136
137 642 jknichel
        //figure out the newest max file descriptor
138 11 emarinel
  for (int j = 0; j < next_available_slot; j++) {
139
    if (client_file_descriptor_array[j] > temp_max_file_descriptor)
140
      temp_max_file_descriptor = client_file_descriptor_array[j];
141
  }
142
  max_file_descriptor = temp_max_file_descriptor;
143
144 623 emarinel
  printf("Client disconnected.\n");
145 11 emarinel
146
  return 0;
147
}
148
149 29 jknichel
/**
150
 * @brief Checks the status of the clients
151
 *
152
 * Sees is any clients are ready to read from their file descriptor or are
153
 *  ready to write to their file descriptor.
154
 *
155
 * @return 0 on success, negative error code on error
156
 */
157 11 emarinel
//TODO: test that it drops commands properly if it gets sent too much data
158
//      do we want it to drop the data or drop the connection?
159 118 emarinel
int ConnectionPool::check_clients() {
160 11 emarinel
  int i;
161
162
  for (i = 0; i < next_available_slot; i++) {
163 145 jknichel
    int client_file_descriptor = client_file_descriptor_array[i];
164 11 emarinel
165 642 jknichel
                //check to see if this client is ready to be read from
166 11 emarinel
    if (FD_ISSET(client_file_descriptor, &read_set)) {
167 145 jknichel
      if (read_data(i, client_file_descriptor) == DECREMENT_INDEX_COUNTER) {
168 642 jknichel
                                i--;
169
                                continue;
170 11 emarinel
      }
171
    }
172
173 642 jknichel
                //check to see if this client is ready to write to
174 11 emarinel
    if (FD_ISSET(client_file_descriptor, &write_set)) {
175 145 jknichel
      write_data(i, client_file_descriptor);
176 11 emarinel
    }
177
  }
178
179
  return 0;
180
}
181
182 29 jknichel
/**
183
 * @brief Puts text into a write buffer that will be written to a client's file
184
 *  descriptor sometime when the client is ready to write.
185
 *
186 681 emarinel
 * @param pool_index Index in the pool of the client to write to.  -1 for global.
187 29 jknichel
 * @param message The message to be written
188
 * @param length The length of the message
189
 *
190
 * @return 0 on success, negative error code on failure
191
 */
192 14 jknichel
int ConnectionPool::write_to_client(int pool_index, char * message, int length) {
193 22 jknichel
  if (!message) {
194
    return ERROR_INVALID_MESSAGE;
195
  }
196
197
  if (length < 0) {
198
    return ERROR_INVALID_MESSAGE_LENGTH;
199
  }
200
201 34 emarinel
  if (length > (WRITE_BUFFER_SIZE-write_buffer_size[pool_index])) {
202
    //TODO: make this a logging statement instead of a print statement
203
    printf("There is not enough room in the write buffer to send the data to the client.\n");
204
    return ERROR_NOT_ENOUGH_ROOM;
205
  }
206 11 emarinel
207 681 emarinel
  if (pool_index == -1) {
208
    // Global message.
209
    for (int i = 0; i < number_clients_ready; i++) {
210
      memcpy(write_buffer[i], message, length);
211
      write_buffer_size[i] += length;
212
    }
213
  } else {
214
    if (pool_index < 0 || pool_index >= next_available_slot) {
215
      return ERROR_INVALID_CLIENT_ID;
216
    }
217 334 gtress
218 681 emarinel
    //printf("Connection pool: attempting to write [%s], length %i to index %i.\n", message, length, pool_index);
219
    memcpy(write_buffer[pool_index], message, length);
220
    write_buffer_size[pool_index] += length;
221
  }
222 14 jknichel
223
  return 0;
224
}
225
226 29 jknichel
/**
227
 * @brief Sets the socket to listen on
228
 *
229
 * @param listen_socket The socket to listen on
230
 *
231
 * @return void
232
 */
233 143 jknichel
void ConnectionPool::add_new_socket_to_pool(int new_socket) {
234
  if (new_socket < 0)
235 58 jknichel
    return;
236
237 143 jknichel
  FD_SET(new_socket, &ready_set);
238
239
  if (new_socket > max_file_descriptor) {
240
    max_file_descriptor = new_socket;
241
  }
242 11 emarinel
}
243
244 29 jknichel
/**
245
 * @brief Find out what file descriptors are ready to write to and read from
246
 *
247
 * @param listen_socket The socket to listen on
248
 * @param select_timeout The timeout for the select statement
249
 *
250
 * @return 0
251
 */
252 58 jknichel
int ConnectionPool::perform_select(int listen_socket) {
253 11 emarinel
  read_set = ready_set;
254
  write_set = ready_set;
255
256 58 jknichel
  struct timeval select_timeout;
257
  memset(&select_timeout, 0, sizeof(select_timeout));
258
259 11 emarinel
  //TODO(Jason): think about why I put this there
260
  if (max_file_descriptor < listen_socket)
261
    max_file_descriptor = listen_socket;
262
263 642 jknichel
        //call select.  select sees if any file descriptor in the read set is ready to be read and modifies the read set.
264
        // It sees if any file descriptor in the write set is ready to be written to and modifies the write set.
265 58 jknichel
  number_clients_ready = select(max_file_descriptor+1, &(read_set), &(write_set), NULL, &select_timeout);
266 11 emarinel
267 59 jknichel
  if (number_clients_ready < 0) {
268
    perror(__FUNCTION__);
269
  }
270
271 11 emarinel
  return 0;
272
}
273
274 642 jknichel
/**
275
 * @return 1 if the socket is ready to be read, 0 otherwise
276
 */
277 11 emarinel
int ConnectionPool::is_socket_ready_to_read(int socket) {
278
  return FD_ISSET(socket, &read_set);
279
}
280
281 642 jknichel
/**
282
 * @return the number of clients that are ready
283
 */
284 11 emarinel
int ConnectionPool::get_number_clients_ready() {
285
  return number_clients_ready;
286
}
287
288 424 emarinel
/**
289 642 jknichel
 * @brief Reads data from a particular client and checks to see if it contains a command and parses that command
290
 *   or buffers the data assuming the rest of the command will be sent later
291
 *
292
 * @param pool_index The index in the connection pool of the client to read from
293
 * @param client_file_descriptor The file descriptor of the client to read from
294
 *
295
 * @return 0 on success, negative error code on error
296
 */
297 145 jknichel
int ConnectionPool::read_data(int pool_index, int client_file_descriptor) {
298
  char temporary_buffer[READ_BUFFER_SIZE];
299
  char temporary_command_buffer[READ_BUFFER_SIZE+1];
300
  int num_bytes_read;
301
  int length;
302
  int command_length;
303
304 642 jknichel
        //read data that the client is trying to send us
305 145 jknichel
  num_bytes_read = read(client_file_descriptor, temporary_buffer, READ_BUFFER_SIZE);
306
307 642 jknichel
        //check to see if the client disconnected
308 145 jknichel
  if (num_bytes_read == 0 || (num_bytes_read == -1 && errno == ECONNRESET)) {
309
    remove_client(pool_index);
310
    return DECREMENT_INDEX_COUNTER;
311
  }
312
313 642 jknichel
314 145 jknichel
  while (num_bytes_read > 0) {
315
    length = num_bytes_read;
316
317 642 jknichel
                //make sure this new data won't overflow the read buffer
318 145 jknichel
    if (length + read_buffer_size[pool_index] > READ_BUFFER_SIZE) {
319
      length = READ_BUFFER_SIZE - read_buffer_size[pool_index];
320
    }
321
322 642 jknichel
                //put the newly read data into the read buffer for this client
323 145 jknichel
    memcpy(read_buffer[pool_index]+read_buffer_size[pool_index], temporary_buffer, length);
324
    read_buffer_size[pool_index] += length;
325
    num_bytes_read -= length;
326
327
    if (num_bytes_read > 0) {
328
      memmove(temporary_buffer, temporary_buffer+length, READ_BUFFER_SIZE - length);
329
    }
330
331
    char* newline_position;
332
333
    while ((newline_position = strstr(read_buffer[pool_index], "\n"))) {
334 642 jknichel
      //if no newline is found in the entire readbuffer (when its full),
335
      // toss out the command
336 145 jknichel
      // because either the command being given is too long or someone is trying
337
      // to do something bad to the server
338
      //TODO: this is from before all this code was put in the loop.  reconsider
339
      //      how to check this error condition and do it elsewhere
340
      if (!newline_position && (read_buffer_size[pool_index] == READ_BUFFER_SIZE)) {
341 424 emarinel
        read_buffer_size[pool_index] = 0;
342
        break;
343 145 jknichel
      }
344
345
      //if no newline is found then there is not a command in the buffer
346
      if (!newline_position) {
347 424 emarinel
        break;
348 145 jknichel
      }
349
350
      command_length = (newline_position - read_buffer[pool_index])+1;
351
352
      //the newline was found in garbage in the currently not used portion
353
      // of the read buffer
354
      if (command_length > read_buffer_size[pool_index]) {
355 424 emarinel
        break;
356 145 jknichel
      }
357
358
      memcpy(temporary_command_buffer, read_buffer[pool_index], command_length);
359
      //do command_length-1 to get rid of the newline terminating the command
360
      temporary_command_buffer[command_length-1] = '\0';
361
      //did this because telnet was putting a \r\n on the end instead of just \n
362
      if (isspace(temporary_command_buffer[command_length-2])) {
363 424 emarinel
        temporary_command_buffer[command_length-2] = '\0';
364 145 jknichel
      }
365
366
      memmove(read_buffer[pool_index], read_buffer[pool_index]+command_length, read_buffer_size[pool_index] - command_length);
367
      read_buffer_size[pool_index] -= command_length;
368
369
      if (command_length > MAX_COMMAND_LEN) {
370 424 emarinel
        fprintf(stderr, "Command was too long.  Tossing command out.\n");
371
        break;
372 145 jknichel
      }
373
374 453 emarinel
      Command command(this, colonet_server);
375 408 emarinel
      if (command.parse_command(temporary_command_buffer, pool_index) != 0) {
376 424 emarinel
        fprintf(stderr, "Error parsing command\n");
377
        break;
378 145 jknichel
      }
379
    }
380
  }
381
382
  return 0;
383
}
384
385 642 jknichel
/**
386
 * @brief This function takes data out of the write buffer for a client and tries to actually send it to the client
387
 *
388
 * @param pool_index The index in the connection pool of the client to write to
389
 * @param client_file_descriptor The file descriptor to write out on
390
 *
391
 * @return 0 on success, negative error code on failure
392
 */
393 145 jknichel
int ConnectionPool::write_data(int pool_index, int client_file_descriptor) {
394
  if (write_buffer_size[pool_index] == 0) {
395
    return 0;
396
  }
397
398
  int sent = write(client_file_descriptor, write_buffer[pool_index], write_buffer_size[pool_index]);
399
  memmove(write_buffer[pool_index], write_buffer[pool_index]+sent, WRITE_BUFFER_SIZE - sent);
400 161 emarinel
  write_buffer_size[pool_index] -= sent;
401 145 jknichel
402
  return 0;
403
}