+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.
ttt-args.h \
ttt-board.c \
ttt-board.h \
+ ttt-error.c \
+ ttt-error.h \
ttt-socket.c \
ttt-socket.h \
x.c \
*/
#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;
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
}
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;
}
}
-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;
+}
#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_ */
--- /dev/null
+/* 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;
+}
--- /dev/null
+/* 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_ */
* 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"
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++;
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;
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)
{
"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)
{
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;
#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);
TTT_STATUS_EOF
} ttt_status_t;
+typedef struct _ttt_server ttt_server_t;
+typedef struct _ttt_client ttt_client_t;
+
#endif