Skip to content

Commit

Permalink
Basic core of functions. Still no server logic.
Browse files Browse the repository at this point in the history
  • Loading branch information
antirez committed Oct 27, 2023
1 parent d4348ee commit 1061212
Showing 1 changed file with 163 additions and 14 deletions.
177 changes: 163 additions & 14 deletions smallchat.c
Original file line number Diff line number Diff line change
Expand Up @@ -37,21 +37,45 @@
#include <fcntl.h>
#include <errno.h>
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <assert.h>

/* Set the specified socket in non-blocking mode, with no delay flag. */
int socketSetNonBlockNoDelay(int fd) {
int flags, yes = 1;
/* ============================ Data structures =================================
* The minimal stuff we can afford to have. This example must be simple
* even for people that don't know a lot of C.
* =========================================================================== */

/* Set the socket nonblocking.
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
* interrupted by a signal. */
if ((flags = fcntl(fd, F_GETFL)) == -1) return -1;
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) return -1;
#define MAX_CLIENTS 1000 // This is actually the higher file descriptor.
#define MAX_NICK_LEN 32
#define SERVER_PORT 7711

/* This is best-effort. No need to check for errors. */
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes));
return 0;
}
/* This structure represents a connected client. There is very little
* info about it: the socket descriptor and the nick name, if set, otherwise
* the first byte of the nickname is set to 0 if not set.
* The client can set its nickname with /nick <nickname> command. */
struct client {
int fd; // Client socket.
char *nick; // Nickname set by client or NULL.
};

/* This global structure encasulates the global state of the chat. */
struct chatState {
int serversock; // Listening server socket.
int numclients; // Number of connected clients right now.
int maxclient; // The greatest 'clients' slot populated.
struct client *clients[MAX_CLIENTS]; // Clients are set in the corresponding
// slot of their socket descriptor.
};

struct chatState *Chat; // Initialized at startup.

/* ======================== Low level networking stuff ==========================
* Here you will find basic socket stuff that should be part of
* a decent standard C library, but you know... there are other
* crazy goals for the future of C: like to make the whole language an
* Undefined Behavior.
* =========================================================================== */

/* Create a TCP socket lisetning to 'port' ready to accept connections. */
int createTCPServer(int port) {
Expand All @@ -65,7 +89,7 @@ int createTCPServer(int port) {
sa.sin_port = htons(port);
sa.sin_addr.s_addr = htonl(INADDR_ANY);

if (bind(s,&sa,sizeof(sa)) == -1 ||
if (bind(s,(struct sockaddr*)&sa,sizeof(sa)) == -1 ||
listen(s, 511) == -1)
{
close(s);
Expand All @@ -74,6 +98,21 @@ int createTCPServer(int port) {
return s;
}

/* Set the specified socket in non-blocking mode, with no delay flag. */
int socketSetNonBlockNoDelay(int fd) {
int flags, yes = 1;

/* Set the socket nonblocking.
* Note that fcntl(2) for F_GETFL and F_SETFL can't be
* interrupted by a signal. */
if ((flags = fcntl(fd, F_GETFL)) == -1) return -1;
if (fcntl(fd, F_SETFL, flags | O_NONBLOCK) == -1) return -1;

/* This is best-effort. No need to check for errors. */
setsockopt(fd, IPPROTO_TCP, TCP_NODELAY, &yes, sizeof(yes));
return 0;
}

/* If the listening socket signaled there is a new connection ready to
* be accepted, we accept(2) it and return -1 on error or the new client
* socket on success. */
Expand All @@ -82,7 +121,8 @@ int acceptClient(int server_socket) {

while(1) {
struct sockaddr_in sa;
s = accept(server_socket,&sa,sizeof(sa));
socklen_t slen = sizeof(sa);
s = accept(server_socket,(struct sockaddr*)&sa,&slen);
if (s == -1) {
if (errno == EINTR)
continue; /* Try again. */
Expand All @@ -93,3 +133,112 @@ int acceptClient(int server_socket) {
}
return s;
}

/* We also define an allocator that always crashes on out of memory: you
* will discover that in most programs designed to run for a long time, that
* are not libraries, trying to recover from out of memory is often futile
* and at the same time makes the whole program terrible. */
void *chatMalloc(size_t size) {
void *ptr = malloc(size);
if (ptr == NULL) {
perror("Out of memory");
exit(1);
}
return ptr;
}

/* Also aborting realloc(). */
void *chatRealloc(void *ptr, size_t size) {
ptr = realloc(ptr,size);
if (ptr == NULL) {
perror("Out of memory");
exit(1);
}
return ptr;
}

/* ====================== Small chat core implementation ========================
* Here the idea is very simple: we accept new connections, read what clients
* write us and fan-out (that is, send-to-all) the message to everybody
* with the exception of the sender. And that is, of course, the most
* simple chat system ever possible.
* =========================================================================== */

/* Create a new client bound to 'fd'. This is called when a new client
* connects. As a side effect updates the global Chat state. */
struct client *createClient(int fd) {
struct client *c = chatMalloc(sizeof(*c));
socketSetNonBlockNoDelay(fd); // Pretend this will not fail.
c->fd = fd;
c->nick = NULL;
assert(Chat->clients[c->fd] == NULL); // This should be available.
Chat->clients[c->fd] = c;
/* We need to update the max client set if needed. */
if (c->fd > Chat->maxclient) Chat->maxclient = c->fd;
Chat->numclients++;
return c;
}

/* Free a client, associated resources, and unbind it from the global
* state in Chat. */
void freeClient(struct client *c) {
free(c->nick);
close(c->fd);
Chat->clients[c->fd] = NULL;
Chat->numclients--;
if (Chat->maxclient == c->fd) {
/* Ooops, this was the max client set. Let's find what is
* the new highest slot used. */
int j;
for (j = Chat->maxclient-1; j >= 0; j--) {
if (Chat->clients[j] != NULL) Chat->maxclient = j;
break;
}
if (j == -1) Chat->maxclient = -1; // We no longer have clients.
}
free(c);
}

/* Allocate and init the global stuff. */
void initChat(void) {
Chat = chatMalloc(sizeof(*Chat));
memset(Chat,0,sizeof(*Chat));
/* No clients at startup, of course. */
Chat->maxclient = -1;
Chat->numclients = 0;

/* Create our listening socket, bound to the given port. This
* is where our clients will connect. */
Chat->serversock = createTCPServer(SERVER_PORT);
if (Chat->serversock == -1) {
perror("Creating listening socket");
exit(1);
}
}

/* Send the specified string to all connected clients but the one
* having as socket descriptor 'excluded'. If you want to send something
* to every client just set excluded to an impossible socket: -1. */
void sendMsgToAllClientsBut(int excluded, char *s, size_t len) {
for (int j = 0; j < Chat->maxclient; j++) {
if (Chat->clients[j] == NULL ||
Chat->clients[j]->fd == excluded) continue;

/* Important: we don't do ANY BUFFERING. We just use the kernel
* socket buffers. If the content does not fit, we don't care.
* This is needed in order to keep this program simple. */
write(Chat->clients[j]->fd,s,len);
}
}

int main(void) {
initChat();

while(1) {
int c = acceptClient(Chat->serversock);
if (c == -1) continue;
printf("Client accepted FD %d\n", c);
sleep(1);
}
return 0;
}

0 comments on commit 1061212

Please sign in to comment.