From: Carl Worth Date: Mon, 14 Nov 2005 21:14:52 +0000 (+0000) Subject: 2005-11-14 Carl Worth X-Git-Url: https://git.cworth.org/git?p=ttt;a=commitdiff_plain;h=15672ac8305a1c5ba0d9bf6edabb0a194c30628e;ds=sidebyside 2005-11-14 Carl Worth Server now acts as a very simple chat server. * src/ttt-client.h: * src/ttt-client.c: (ttt_client_create), (ttt_client_destroy), (_append_to_request), (ttt_client_read_into_request_until), (ttt_client_read_line), (ttt_client_send): Add support to do a buffering read within the client and to reuse the buffer (which grows by doubling) from one request to the next. Add function to send a message to a client. * src/ttt-server.c: (ttt_server_init), (ttt_server_create_client): Add support for a client idea. Resize the server's client list by doubling. * src/ttt-server.c: (ttt_server_destroy_client): Add support to destroy a client. * src/ttt-server.c: (_handle_client_requests_thread), (_accept_client): Move handling of client requests up to ttt-server.c from ttt-client.c. Use threads rather than a fork per client. * src/ttt-server.h: * src/ttt-server.h: (ttt_server_broadcast): Add function to send a message to all connected clients. * src/ttt-socket.h: * src/ttt-socket.c: (ttt_socket_read), (ttt_socket_write): Add functions to do blocking read or write of the specified number of bytes. * src/ttt-socket.c: (ttt_socket_accept): Remove fork, so that the accept function call can take care of anything like that itself. * src/x.h: * src/x.c: (xread), (xwrite): New wrappers for read and write. * src/ttt.h: Add TTT_STATUS_EOF, TRUE, and FALSE. * src/ttt.c: Need to include ttt-args.h explicitly now. --- diff --git a/ChangeLog b/ChangeLog index 85d24c6..bc17607 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,43 @@ +2005-11-14 Carl Worth + + Server now acts as a very simple chat server. + + * src/ttt-client.h: + * src/ttt-client.c: (ttt_client_create), (ttt_client_destroy), + (_append_to_request), (ttt_client_read_into_request_until), + (ttt_client_read_line), (ttt_client_send): Add support to do a + buffering read within the client and to reuse the buffer (which + grows by doubling) from one request to the next. Add function to + send a message to a client. + + * src/ttt-server.c: (ttt_server_init), (ttt_server_create_client): + Add support for a client idea. Resize the server's client list by + doubling. + * src/ttt-server.c: (ttt_server_destroy_client): Add support to + destroy a client. + * src/ttt-server.c: (_handle_client_requests_thread), + (_accept_client): Move handling of client requests up to + ttt-server.c from ttt-client.c. Use threads rather than a fork per + client. + * src/ttt-server.h: + * src/ttt-server.h: (ttt_server_broadcast): Add function to send a + message to all connected clients. + + * src/ttt-socket.h: + * src/ttt-socket.c: (ttt_socket_read), + (ttt_socket_write): Add functions to do blocking read or write of + the specified number of bytes. + + * src/ttt-socket.c: (ttt_socket_accept): Remove fork, so that the + accept function call can take care of anything like that itself. + + * src/x.h: + * src/x.c: (xread), (xwrite): New wrappers for read and write. + + * src/ttt.h: Add TTT_STATUS_EOF, TRUE, and FALSE. + + * src/ttt.c: Need to include ttt-args.h explicitly now. + 2005-11-11 Carl Worth * src/ttt-server.h: diff --git a/src/ttt-client.c b/src/ttt-client.c index 80fca84..e6c97a1 100644 --- a/src/ttt-client.c +++ b/src/ttt-client.c @@ -20,14 +20,10 @@ */ #include "ttt-client.h" - -struct _ttt_client { - ttt_server_t *server; - int socket; -}; +#include "ttt-socket.h" ttt_client_t * -ttt_client_create (ttt_server_t *server, int socket) +ttt_client_create (ttt_server_t *server, int socket, int id) { ttt_client_t *client; @@ -36,31 +32,112 @@ ttt_client_create (ttt_server_t *server, int socket) client->server = server; client->socket = socket; + client->id = id; + + client->buf_head = client->buf; + client->buf_tail = client->buf; + + client->request = NULL; + client->request_size = 0; + client->request_len = 0; + return client; } void ttt_client_destroy (ttt_client_t *client) { - close (client->socket); + shutdown (client->socket, SHUT_RDWR); + + free (client->request); free (client); } -void -ttt_client_handle_requests (ttt_client_t *client) +static void +_append_to_request (ttt_client_t *client, + const char *buf, + int size) { -#define BUF_SIZE 1024 + int size_needed = client->request_len + size; + + if (size_needed > client->request_size) { + if (client->request_size == 0) { + client->request_size = size_needed; + } else { + while (size_needed > client->request_size) + client->request_size *= 2; + } + + client->request = xrealloc (client->request, client->request_size); + } + + memcpy (client->request + client->request_len, + buf, size); + + client->request_len += size; +} + +static ttt_status_t +ttt_client_read_into_request_until (ttt_client_t *client, char delimeter) +{ + ttt_bool_t found_delimeter = FALSE; + int bytes_read; + char *s; + + client->request_len = 0; while (1) { - char buf[BUF_SIZE]; - int cnt; - cnt = read (client->socket, buf, BUF_SIZE); - if (cnt == 0) - break; - write (0, buf, cnt); - write (client->socket, buf, cnt); + + if (client->buf_tail >= client->buf_head) { + bytes_read = xread (client->socket, + client->buf, + TTT_CLIENT_BUF_SIZE); + if (bytes_read == 0) + return TTT_STATUS_EOF; + client->buf_head = client->buf; + client->buf_tail = client->buf_head + bytes_read; + } + + for (s = client->buf_head; s < client->buf_tail; s++) { + if (*s == delimeter) { + found_delimeter = TRUE; + s++; + break; + } + } + + _append_to_request (client, + client->buf_head, + s - client->buf_head); + client->buf_head = s; + + if (found_delimeter) + return TTT_STATUS_SUCCESS; } } +char * +ttt_client_read_line (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; + } + + _append_to_request (client, &null_terminator, 1); + + return client->request; +} + +void +ttt_client_send (ttt_client_t *client, const char *message) +{ + ttt_socket_write (client->socket, message, strlen (message)); +} + diff --git a/src/ttt-client.h b/src/ttt-client.h index b067674..452b36f 100644 --- a/src/ttt-client.h +++ b/src/ttt-client.h @@ -26,10 +26,28 @@ #ifndef _TTT_CLIENT_H_ #define _TTT_CLIENT_H_ -typedef struct _ttt_client ttt_client_t; +#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. + * connected socket, and assign it the given id. * * Returns: A new ttt_client_t. Call ttt_client_destroy when finished * with it. @@ -38,13 +56,26 @@ typedef struct _ttt_client ttt_client_t; * will not return. */ ttt_client_t * -ttt_client_create (ttt_server_t *server, int socket); +ttt_client_create (ttt_server_t *server, int socket, int id); +/* Destroy a client. */ void ttt_client_destroy (ttt_client_t *client); -/* Loop forever handling client requests. Never returns. */ +/* Perform a blocking read until a newline is encountered. + * + * 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. + * + * Errors: If any error (other than reading EOF) occurs, this function + * will not return. + */ +char * +ttt_client_read_line (ttt_client_t *client); + +/* Send a message to a client. */ void -ttt_client_handle_requests (ttt_client_t *client); +ttt_client_send (ttt_client_t *client, const char *message); #endif /* _TTT_CLIENT_H_ */ diff --git a/src/ttt-server.c b/src/ttt-server.c index 316a270..2bc3a52 100644 --- a/src/ttt-server.c +++ b/src/ttt-server.c @@ -20,13 +20,17 @@ */ #include "ttt.h" +#include "ttt-args.h" #include "ttt-client.h" #include "ttt-socket.h" struct _ttt_server { pthread_mutex_t mutex; + int next_client_id; + ttt_client_t **clients; + int clients_size; int num_clients; }; @@ -35,22 +39,88 @@ ttt_server_init (ttt_server_t *server) { pthread_mutex_init (&server->mutex, NULL); + server->next_client_id = 0; + server->clients = NULL; + server->clients_size = 0; server->num_clients = 0; } -static void -ttt_server_add_client (ttt_server_t *server, ttt_client_t *client) +static ttt_client_t * +ttt_server_create_client (ttt_server_t *server, int client_socket) { + ttt_client_t *client; + pthread_mutex_lock (&server->mutex); + client = ttt_client_create (server, client_socket, + server->next_client_id++); + + printf ("Client %d has joined.\n", client->id); + server->num_clients++; - server->clients = xrealloc (server->clients, - server->num_clients * sizeof (ttt_client_t *)); + + if (server->num_clients > server->clients_size) { + if (server->clients_size == 0) + server->clients_size = 1; + else + server->clients_size *= 2; + + server->clients = xrealloc (server->clients, + server->clients_size * sizeof (ttt_client_t *)); + } server->clients [server->num_clients - 1] = client; pthread_mutex_unlock (&server->mutex); + + return client; +} + +static void +ttt_server_destroy_client (ttt_server_t *server, ttt_client_t *client) +{ + int i; + + pthread_mutex_lock (&server->mutex); + + for (i = 0; i < server->num_clients; i++) + if (server->clients[i] == client) + break; + + assert (i < server->num_clients); + + printf ("Client %d has left.\n", client->id); + + 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 @@ -58,32 +128,53 @@ _accept_client (void *closure, int client_socket) { ttt_server_t *server = closure; ttt_client_t *client; + int err; - client = ttt_client_create (server, client_socket); + 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); + } +} - ttt_server_add_client (server, client); +void +ttt_server_broadcast (ttt_server_t *server, const char *message) +{ + int i; - ttt_client_handle_requests (client); + pthread_mutex_lock (&server->mutex); + + for (i = 0; i < server->num_clients; i++) + ttt_client_send (server->clients[i], message); + + pthread_mutex_unlock (&server->mutex); } static const char *WELCOME_MESSAGE = -"Welcome to ttt-server. So far, this program is simply a demonstration\n" -"of a TCP/IP server capable of handling multiple simultaneous clients.\n" +"Welcome to ttt-server. So far, this program is still a demonstration\n" +"TCP/IP server, acting something like a rather braindead chat server.\n" "The server is currently listening on:\n" "\n %s:%s\n" -"\nTo test this program, simply connect a client to that host and port.\n" +"\nTo test this, simply connect one or more clients to that host and port.\n" "For example:\n" "\n telnet %s %s\n" -"\nOnce you have connected a client, the server will echo any characters\n" -"it receives back to the client as well as to stdout.\n" +"\nOnce you have connected a client, the server will send each line of text\n" +"it receives to all connected clients. The server reports client joins and\n" +"departures on stdout.\n" "\nNote that to terminate the telnet client you type Control-], then\n" ", then \"close\" (and ) at the \"telnet> \" prompt.\n" "\nHave fun!\n" "-Carl\n" -"\nPS. The server does support multiple clients, but there is not yet any\n" -"interaction between the clients. The next step is probably to turn the\n" -"server into a simple chat server. After that, we should have the necessary\n" -"structure in place to start implementing the real tic-tac-toe protocol.\n\n"; +"\nPS. At this point we're ready to leave the demonstration phase and to\n" +"begin implementing TTTP (tic-tac-toe protocol) as well as fixing the\n" +"protocol specifcation. We don't need a custom client to move forward on\n" +"the server (that is one of the ideas behind using a telnet-compatible\n" +"protocol), but a custom client would still be a fine project for a\n" +"motivated beginning programmer.\n\n"; int main (int argc, char **argv) diff --git a/src/ttt-server.h b/src/ttt-server.h index 773b034..b8d2496 100644 --- a/src/ttt-server.h +++ b/src/ttt-server.h @@ -26,4 +26,8 @@ typedef struct _ttt_server ttt_server_t; +/* Send a message to all connected clients. */ +void +ttt_server_broadcast (ttt_server_t *server, const char *message); + #endif /* _TTT_SERVER_H_ */ diff --git a/src/ttt-socket.c b/src/ttt-socket.c index f185d36..199535f 100644 --- a/src/ttt-socket.c +++ b/src/ttt-socket.c @@ -150,16 +150,41 @@ ttt_socket_accept (int listen_socket, ttt_socket_accept_func_t accept, void *closure) { - pid_t pid; int connected_socket; connected_socket = _wait_for_connection (listen_socket); - pid = xfork(); + (accept) (closure, connected_socket); +} + +/* Exported: see ttt-socket.h for documentation. */ +void +ttt_socket_read (int socket, + void *buf, + size_t count) +{ + int bytes_read; + char *cbuf = buf; + + while (count) { + bytes_read = xread (socket, cbuf, count); + cbuf += bytes_read; + count -= bytes_read; + } +} + +/* Exported: see ttt-socket.h for documentation. */ +void +ttt_socket_write (int socket, + const void *buf, + size_t count) +{ + int written; + const char *cbuf = buf; - if (pid == 0) { - /* Child process. */ - (accept) (closure, connected_socket); - return; + while (count) { + written = xwrite (socket, cbuf, count); + cbuf += written; + count -= written; } } diff --git a/src/ttt-socket.h b/src/ttt-socket.h index 909501e..6b35ef2 100644 --- a/src/ttt-socket.h +++ b/src/ttt-socket.h @@ -55,4 +55,24 @@ ttt_socket_accept (int listen_socket, ttt_socket_accept_func_t accept, void *closure); +/* Performa a blocking read, until all count bytes are read from the + * socket to buf, which must be of size count or larger. + * + * Errors: If any errors occur, this function does not return. + */ +void +ttt_socket_read (int socket, + void *buf, + size_t count); + +/* Perform a blocking write, until all count bytes are written from + * buf to the socket. + * + * Errors: If any errors occur, this function does not return. + */ +void +ttt_socket_write (int socket, + const void *buf, + size_t count); + #endif diff --git a/src/ttt.c b/src/ttt.c index f211ae6..fba2dde 100644 --- a/src/ttt.c +++ b/src/ttt.c @@ -20,6 +20,7 @@ */ #include "ttt.h" +#include "ttt-args.h" int main (int argc, char **argv) diff --git a/src/ttt.h b/src/ttt.h index 316d744..aaa1ed0 100644 --- a/src/ttt.h +++ b/src/ttt.h @@ -56,14 +56,17 @@ do { \ #define TTT_PRINTF_FORMAT(fmt_index, va_index) #endif -#include "ttt-args.h" #include "x.h" typedef int ttt_bool_t; +#define FALSE 0 +#define TRUE 1 + typedef enum { TTT_STATUS_SUCCESS = 0, - TTT_STATUS_FAILURE + TTT_STATUS_FAILURE, + TTT_STATUS_EOF } ttt_status_t; #endif diff --git a/src/x.c b/src/x.c index 18c69a4..8da355e 100644 --- a/src/x.c +++ b/src/x.c @@ -248,3 +248,33 @@ xselect (int n, return ret; } + +ssize_t +xread (int fd, void *buf, size_t count) +{ + int ret; + + ret = read (fd, buf, count); + if (ret == -1) { + fprintf (stderr, "Error: read failed: %s. Aborting.\n", + strerror (errno)); + exit (1); + } + + return ret; +} + +ssize_t +xwrite (int fd, const void *buf, size_t count) +{ + int ret; + + ret = write (fd, buf, count); + if (ret == -1) { + fprintf (stderr, "Error: write failed: %s. Aborting.\n", + strerror (errno)); + exit (1); + } + + return ret; +} diff --git a/src/x.h b/src/x.h index 868a567..b3384af 100644 --- a/src/x.h +++ b/src/x.h @@ -76,4 +76,10 @@ xselect (int n, fd_set *exceptfds, struct timeval *timeout); +ssize_t +xread (int fd, void *buf, size_t count); + +ssize_t +xwrite (int fd, const void *buf, size_t count); + #endif /* _X_H_ */