From: Carl Worth Date: Tue, 15 Nov 2005 17:56:28 +0000 (+0000) Subject: 2005-11-15 Carl Worth X-Git-Url: https://git.cworth.org/git?p=ttt;a=commitdiff_plain;h=907321c065fb1383e800f0794981df91d4327f57;hp=15672ac8305a1c5ba0d9bf6edabb0a194c30628e 2005-11-15 Carl Worth * src/Makefile.am: * src/ttt-error.h: * src/ttt-error.c: (ttt_error_string): Add error values and error strings from protocol. * src/ttt.h: Add typedefs for ttt_client_t and ttt_server_t so that there's no problem with ttt-client.h and ttt-server.h needing to mutually include each other. * src/ttt-server.h: * src/ttt-server.c: (ttt_server_register_client), (ttt_server_unregister_client), (_ttt_server_accept), (main): Push the client request handling thread back down into ttt-client.c where it really does belong. The server code no longer ever reaches down inside the ttt_client_t structure. Export ttt_server_register_client and ttt_server_unregister_client. Add documentation for ttt_server_broadcast. Document locking semantics for all functions which acquire the server lock. * src/ttt-client.h: * src/ttt-client.c: (_ttt_client_create), (_ttt_client_destroy), (_read_into_request_until), (_read_request), (_execute_request), (_handle_requests_thread), (ttt_client_new), (ttt_client_get_id): Pull the ttt_client structure definition back down inside the ttt-client.c file. Don't export ttt_client_create and ttt_client_destroy anymore, but instead just use static _ttt_client_init and _ttt_client_fini. Implement the broadcast by just passing a client pointer around and using client->request rather than passing char *request all over. Document new ttt_client_new and remove ttt_client_read_line. Add locking to ttt_client_send and document it. Add ttt_client_get_id. --- diff --git a/ChangeLog b/ChangeLog index bc17607..6effd22 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,37 @@ +2005-11-15 Carl Worth + + * src/Makefile.am: + * src/ttt-error.h: + * src/ttt-error.c: (ttt_error_string): Add error values and error + strings from protocol. + + * src/ttt.h: Add typedefs for ttt_client_t and ttt_server_t so + that there's no problem with ttt-client.h and ttt-server.h needing + to mutually include each other. + + * src/ttt-server.h: + * src/ttt-server.c: (ttt_server_register_client), + (ttt_server_unregister_client), (_ttt_server_accept), (main): Push + the client request handling thread back down into ttt-client.c + where it really does belong. The server code no longer ever + reaches down inside the ttt_client_t structure. Export + ttt_server_register_client and ttt_server_unregister_client. Add + documentation for ttt_server_broadcast. Document locking semantics + for all functions which acquire the server lock. + + * src/ttt-client.h: + * src/ttt-client.c: (_ttt_client_create), (_ttt_client_destroy), + (_read_into_request_until), (_read_request), (_execute_request), + (_handle_requests_thread), (ttt_client_new), (ttt_client_get_id): + Pull the ttt_client structure definition back down inside the + ttt-client.c file. Don't export ttt_client_create and + ttt_client_destroy anymore, but instead just use static + _ttt_client_init and _ttt_client_fini. Implement the broadcast by + just passing a client pointer around and using client->request + rather than passing char *request all over. Document new + ttt_client_new and remove ttt_client_read_line. Add locking to + ttt_client_send and document it. Add ttt_client_get_id. + 2005-11-14 Carl Worth Server now acts as a very simple chat server. diff --git a/src/Makefile.am b/src/Makefile.am index 5619e52..4b336a0 100644 --- a/src/Makefile.am +++ b/src/Makefile.am @@ -5,6 +5,8 @@ ttt_common_sources = \ ttt-args.h \ ttt-board.c \ ttt-board.h \ + ttt-error.c \ + ttt-error.h \ ttt-socket.c \ ttt-socket.h \ x.c \ diff --git a/src/ttt-client.c b/src/ttt-client.c index e6c97a1..9d44945 100644 --- a/src/ttt-client.c +++ b/src/ttt-client.c @@ -20,20 +20,39 @@ */ #include "ttt-client.h" + +#include "ttt-server.h" #include "ttt-socket.h" +#include "ttt-error.h" -ttt_client_t * -ttt_client_create (ttt_server_t *server, int socket, int id) -{ - ttt_client_t *client; +struct _ttt_client { + pthread_mutex_t mutex; + pthread_t thread; - client = xmalloc (sizeof (ttt_client_t)); + ttt_server_t *server; + int socket; + + int id; + + char buf[TTT_CLIENT_BUF_SIZE]; + char *buf_head; + char *buf_tail; + + char *request; + int request_size; + int request_len; +}; + +static void +_ttt_client_init (ttt_client_t *client, + ttt_server_t *server, + int socket) +{ + pthread_mutex_init (&client->mutex, NULL); client->server = server; client->socket = socket; - client->id = id; - client->buf_head = client->buf; client->buf_tail = client->buf; @@ -41,17 +60,26 @@ ttt_client_create (ttt_server_t *server, int socket, int id) client->request_size = 0; client->request_len = 0; - return client; + /* XXX: Probably want to register only as the result of the HELO + command. Not only will that match the protocol correctly, but + it will also eliminate a race condition here. */ + client->id = ttt_server_register_client (server, client); } -void -ttt_client_destroy (ttt_client_t *client) +static void +_ttt_client_fini (ttt_client_t *client) { + pthread_mutex_lock (&client->mutex); + + ttt_server_unregister_client (client->server, client); + shutdown (client->socket, SHUT_RDWR); free (client->request); - free (client); + pthread_mutex_unlock (&client->mutex); + + pthread_mutex_destroy (&client->mutex); } static void @@ -79,7 +107,7 @@ _append_to_request (ttt_client_t *client, } static ttt_status_t -ttt_client_read_into_request_until (ttt_client_t *client, char delimeter) +_read_into_request_until (ttt_client_t *client, char delimeter) { ttt_bool_t found_delimeter = FALSE; int bytes_read; @@ -117,27 +145,90 @@ ttt_client_read_into_request_until (ttt_client_t *client, char delimeter) } } -char * -ttt_client_read_line (ttt_client_t *client) +static ttt_status_t +_read_request (ttt_client_t *client) { ttt_status_t status; static const char null_terminator = '\0'; - status = ttt_client_read_into_request_until (client, '\n'); - if (status) { - assert (status == TTT_STATUS_EOF); - return NULL; - } + status = _read_into_request_until (client, '\n'); + if (status) + return status; _append_to_request (client, &null_terminator, 1); - return client->request; + return TTT_STATUS_SUCCESS; +} + +static ttt_error_t +_execute_request (ttt_client_t *client) +{ + ttt_server_broadcast (client->server, client->request); + + return TTT_ERROR_NONE; +} + +static void * +_handle_requests_thread (void *closure) +{ + ttt_status_t status; + ttt_error_t error; + ttt_client_t *client = closure; + + while (1) { + + status = _read_request (client); + if (status == TTT_STATUS_EOF) + break; + if (status) + ASSERT_NOT_REACHED; + + error = _execute_request (client); + if (error) + ttt_client_send (client, ttt_error_string (error)); + } + + _ttt_client_fini (client); + free (client); + + return (void *) 0; +} + +/* Exported: See ttt-client.h for documentation. */ +void +ttt_client_new (void *closure, int client_socket) +{ + ttt_server_t *server = closure; + ttt_client_t *client; + int err; + + client = xmalloc (sizeof (ttt_client_t)); + + _ttt_client_init (client, server, client_socket); + + err = pthread_create (&client->thread, NULL, + _handle_requests_thread, client); + if (err != 0) { + fprintf (stderr, "Error: pthread_create failed: %s. Aborting.\n", + strerror (err)); + exit (1); + } } +/* Exported: See ttt-client.h for documentation. */ void ttt_client_send (ttt_client_t *client, const char *message) { + pthread_mutex_lock (&client->mutex); + ttt_socket_write (client->socket, message, strlen (message)); -} + pthread_mutex_unlock (&client->mutex); +} +/* Exported: See ttt-client.h for documentation. */ +int +ttt_client_get_id (ttt_client_t *client) +{ + return client->id; +} diff --git a/src/ttt-client.h b/src/ttt-client.h index 452b36f..ef74b87 100644 --- a/src/ttt-client.h +++ b/src/ttt-client.h @@ -21,61 +21,28 @@ #include "ttt.h" -#include "ttt-server.h" - #ifndef _TTT_CLIENT_H_ #define _TTT_CLIENT_H_ #define TTT_CLIENT_BUF_SIZE 1024 -typedef struct _ttt_client { - pthread_t thread; - - ttt_server_t *server; - int socket; - - int id; - - char buf[TTT_CLIENT_BUF_SIZE]; - char *buf_head; - char *buf_tail; - - char *request; - int request_size; - int request_len; -} ttt_client_t; - - -/* Create a new ttt_client_t for the given server and given a - * connected socket, and assign it the given id. - * - * Returns: A new ttt_client_t. Call ttt_client_destroy when finished - * with it. - * - * Errors: If any error occurs, (such as out-of-memory), this function - * will not return. - */ -ttt_client_t * -ttt_client_create (ttt_server_t *server, int socket, int id); - -/* Destroy a client. */ +/* Create a new client and start a new thread to handle all requests + * from the client. */ void -ttt_client_destroy (ttt_client_t *client); +ttt_client_new (void *closure, int client_socket); -/* Perform a blocking read until a newline is encountered. +/* Send a message to a client. * - * Returns: A pointer to the string read, or NULL if EOF occurs. This - * string points to data internal to the client and can be overwritten - * by subsequent calls to this function. + * Locking: The client mutex will be acquired and held throughout the + * execution of this function. * - * Errors: If any error (other than reading EOF) occurs, this function - * will not return. + * Errors: If an IO error occurs, this function will not return. */ -char * -ttt_client_read_line (ttt_client_t *client); - -/* Send a message to a client. */ void ttt_client_send (ttt_client_t *client, const char *message); +/* Get a client's unique ID. */ +int +ttt_client_get_id (ttt_client_t *client); + #endif /* _TTT_CLIENT_H_ */ diff --git a/src/ttt-error.c b/src/ttt-error.c new file mode 100644 index 0000000..26ab09d --- /dev/null +++ b/src/ttt-error.c @@ -0,0 +1,54 @@ +/* ttt-error.c - errors returned to clients + * + * Copyright © 2005 Carl Worth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Carl Worth + */ + +#include "ttt-error.h" + +const char * +ttt_error_string (ttt_error_t error) +{ + switch (error) { + case TTT_ERROR_NONE: + return "ERROT NONE\n"; + case TTT_ERROR_NONAMESET: + return "ERROR NONAMESET\n"; + case TTT_ERROR_INVALIDNAME: + return "ERROR INVALIDNAME\n"; + case TTT_ERROR_COMMAND: + return "ERROR COMMAND\n"; + case TTT_ERROR_SYNTAX: + return "ERROR SYNTAX\n"; + case TTT_ERROR_NOTNUMBER: + return "ERROR NOTNUMBER\n"; + case TTT_ERROR_NOTGRID: + return "ERROR NOTGRID\n"; + case TTT_ERROR_NOUSER: + return "ERROR NOUSER\n"; + case TTT_ERROR_NOTINGAME: + return "ERROR_NOTINGAME\n"; + case TTT_ERROR_NOTPLAYING: + return "ERROR_NOTPLAYING\n"; + case TTT_ERROR_NOTYOURTURN: + return "ERROR NOTYOURTURN\n"; + } + + ASSERT_NOT_REACHED; + return NULL; +} diff --git a/src/ttt-error.h b/src/ttt-error.h new file mode 100644 index 0000000..a800ea6 --- /dev/null +++ b/src/ttt-error.h @@ -0,0 +1,44 @@ +/* ttt-error.c - errors returned to clients + * + * Copyright © 2005 Carl Worth + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2, or (at your option) + * any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program; if not, write to the Free Software Foundation, + * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + * + * Author: Carl Worth + */ + +#include "ttt.h" + +#ifndef _TTT_ERROR_H_ +#define _TTT_ERROR_H_ + +typedef enum { + TTT_ERROR_NONE, + TTT_ERROR_NONAMESET, + TTT_ERROR_INVALIDNAME, + TTT_ERROR_COMMAND, + TTT_ERROR_SYNTAX, + TTT_ERROR_NOTNUMBER, + TTT_ERROR_NOTGRID, + TTT_ERROR_NOUSER, + TTT_ERROR_NOTINGAME, + TTT_ERROR_NOTPLAYING, + TTT_ERROR_NOTYOURTURN +} ttt_error_t; + +const char * +ttt_error_string (ttt_error_t error); + +#endif /* _TTT_ERROR_H_ */ diff --git a/src/ttt-server.c b/src/ttt-server.c index 2bc3a52..c50a797 100644 --- a/src/ttt-server.c +++ b/src/ttt-server.c @@ -19,7 +19,8 @@ * Author: Carl Worth */ -#include "ttt.h" +#include "ttt-server.h" + #include "ttt-args.h" #include "ttt-client.h" #include "ttt-socket.h" @@ -46,17 +47,17 @@ ttt_server_init (ttt_server_t *server) server->num_clients = 0; } -static ttt_client_t * -ttt_server_create_client (ttt_server_t *server, int client_socket) +/* Exported: See ttt-server.h for documentation. */ +int +ttt_server_register_client (ttt_server_t *server, ttt_client_t *client) { - ttt_client_t *client; + int id; pthread_mutex_lock (&server->mutex); - client = ttt_client_create (server, client_socket, - server->next_client_id++); + id = server->next_client_id++; - printf ("Client %d has joined.\n", client->id); + printf ("Client %d has joined.\n", id); server->num_clients++; @@ -74,11 +75,12 @@ ttt_server_create_client (ttt_server_t *server, int client_socket) pthread_mutex_unlock (&server->mutex); - return client; + return id; } -static void -ttt_server_destroy_client (ttt_server_t *server, ttt_client_t *client) +/* Exported: See ttt-server.h for documentation. */ +void +ttt_server_unregister_client (ttt_server_t *server, ttt_client_t *client) { int i; @@ -90,57 +92,16 @@ ttt_server_destroy_client (ttt_server_t *server, ttt_client_t *client) assert (i < server->num_clients); - printf ("Client %d has left.\n", client->id); + printf ("Client %d has left.\n", ttt_client_get_id (client)); memmove (&server->clients[i], &server->clients[i+1], (server->num_clients - i - 1) * sizeof (ttt_client_t *)); server->num_clients--; - ttt_client_destroy (client); - pthread_mutex_unlock (&server->mutex); } -static void * -_handle_client_requests_thread (void *closure) -{ - ttt_client_t *client = closure; - ttt_server_t *server = client->server; - char *request; - - while (1) { - - request = ttt_client_read_line (client); - if (request == NULL) - break; - - ttt_server_broadcast (client->server, request); - } - - ttt_server_destroy_client (server, client); - - return (void *) 0; -} - -static void -_accept_client (void *closure, int client_socket) -{ - ttt_server_t *server = closure; - ttt_client_t *client; - int err; - - client = ttt_server_create_client (server, client_socket); - - err = pthread_create (&client->thread, NULL, - _handle_client_requests_thread, client); - if (err != 0) { - fprintf (stderr, "Error: pthread_create failed: %s. Aborting.\n", - strerror (err)); - exit (1); - } -} - void ttt_server_broadcast (ttt_server_t *server, const char *message) { @@ -176,6 +137,14 @@ static const char *WELCOME_MESSAGE = "protocol), but a custom client would still be a fine project for a\n" "motivated beginning programmer.\n\n"; +static void +_ttt_server_accept (void *closure, int client_socket) +{ + ttt_server_t *server = closure; + + ttt_client_new (server, client_socket); +} + int main (int argc, char **argv) { @@ -195,7 +164,7 @@ main (int argc, char **argv) ttt_server_init (&server); while (1) - ttt_socket_accept (socket, _accept_client, &server); + ttt_socket_accept (socket, _ttt_server_accept, &server); /* We only reach here if something bad happened. */ return 1; diff --git a/src/ttt-server.h b/src/ttt-server.h index b8d2496..77fb4bf 100644 --- a/src/ttt-server.h +++ b/src/ttt-server.h @@ -24,9 +24,36 @@ #ifndef _TTT_SERVER_H_ #define _TTT_SERVER_H_ -typedef struct _ttt_server ttt_server_t; +/* Register a new client with the server. + * + * Returns: the unique id of the client. + * + * Locking: The server mutex will be acquired and held throughout the + * execution of this function. + * + * Errors: If an error (such as out-of-memory) occurs, this function + * will not return. + */ +int +ttt_server_register_client (ttt_server_t *server, ttt_client_t *client); -/* Send a message to all connected clients. */ +/* Un-register a client from the server. + * + * Locking: The server mutex will be acquired and held throughout the + * execution of this function. + */ +void +ttt_server_unregister_client (ttt_server_t *server, ttt_client_t *client); + +/* Send a message to all connected clients. + * + * Locking: The server mutex will be acquired and held throughout the + * execution of this function. Each client mutex may also be acquired + * and held by functions called during the execution of this function. + * + * Errors: If an error such as an IO error occurs, this function will + * not return. + */ void ttt_server_broadcast (ttt_server_t *server, const char *message); diff --git a/src/ttt.h b/src/ttt.h index aaa1ed0..c3e8c5c 100644 --- a/src/ttt.h +++ b/src/ttt.h @@ -69,4 +69,7 @@ typedef enum { TTT_STATUS_EOF } ttt_status_t; +typedef struct _ttt_server ttt_server_t; +typedef struct _ttt_client ttt_client_t; + #endif