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 | } |