Project

General

Profile

Statistics
| Revision:

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

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