2005-11-15 Carl Worth <cworth@cworth.org>
authorCarl Worth <carl@theworths.org>
Tue, 15 Nov 2005 17:56:28 +0000 (17:56 +0000)
committerCarl Worth <carl@theworths.org>
Tue, 15 Nov 2005 17:56:28 +0000 (17:56 +0000)
        * 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.

ChangeLog
src/Makefile.am
src/ttt-client.c
src/ttt-client.h
src/ttt-error.c [new file with mode: 0644]
src/ttt-error.h [new file with mode: 0644]
src/ttt-server.c
src/ttt-server.h
src/ttt.h

index bc176073fc7e0241c19ef2bfce625caaa48faf62..6effd22d2c61d7053c09e339f2ed855a39969f76 100644 (file)
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,37 @@
+2005-11-15  Carl Worth  <cworth@cworth.org>
+
+       * 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  <cworth@cworth.org>
 
        Server now acts as a very simple chat server.
index 5619e522b5bd9ff35598c95fcb2c9ca0447a9402..4b336a05d2d9ff0c6bf8d55df1e89abfb4a83b14 100644 (file)
@@ -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                     \
index e6c97a1c6ad9c2f16ba1f07d9bbc0336e68daa93..9d44945e506664678854fab51c41fa3fd04e870d 100644 (file)
  */
 
 #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;
+}
index 452b36fa0d73ec492a506f2a2fcac5b071e96b27..ef74b87760d893acfaaf671dd6470a3b4c9baf2d 100644 (file)
 
 #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 (file)
index 0000000..26ab09d
--- /dev/null
@@ -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 <cworth@cworth.org>
+ */
+
+#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 (file)
index 0000000..a800ea6
--- /dev/null
@@ -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 <cworth@cworth.org>
+ */
+
+#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_ */
index 2bc3a529b51efca60af87679488e248facd84b46..c50a797b259ec8bf1dbb6b74886bba2468ca4131 100644 (file)
@@ -19,7 +19,8 @@
  * Author: Carl Worth <cworth@cworth.org>
  */
 
-#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;
index b8d2496e2149d44285b302a14d6fb28da13714a7..77fb4bf79df7c023a2f971b27015131b8e7e09e9 100644 (file)
 #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);
 
index aaa1ed0ab4d524bd4ec7468916e315a0173acc20..c3e8c5c25fa88abb9c1b91df910ebc889bc7f536 100644 (file)
--- 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