Skip to content

Instantly share code, notes, and snippets.

@StonedXander
Created April 28, 2012 13:16
Show Gist options
  • Save StonedXander/2519019 to your computer and use it in GitHub Desktop.
Save StonedXander/2519019 to your computer and use it in GitHub Desktop.
An attempt of crappy IM server
// Let's make a server, Posix Style !
#include <iostream>
#include <string.h>
#include <sys/time.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <unistd.h>
#define DEFAULT_PORT "8080"
#define PENDING_QUEUE_SIZE 16
#define TIMEOUT_SEC 2
#define TIMEOUT_USEC 0
#define MAX_MESSAGE_SIZE 512
#define MAX_CLIENT 50
// -- Protocol specific
// The connection is fresh.
#define STATE_INIT 0
// The identity has been partially transmitted.
#define STATE_IDENTIFY 1
// The client can emit new messages.
#define STATE_OPER 2
// The client is currently emitting.
#define STATE_EMITTING 3
// The client is faulted (or fubarded ? ;p)
#define STATE_FAULTED 4
// Server notification of a disconnection.
#define SERVER_OPCODE_DISCONNECT 2
// Server notification about an identity.
#define SERVER_OPCODE_IDENTITY 0
// Server notification about a message.
#define SERVER_OPCODE_MESSAGE 1
// Client setting its identity.
#define CLIENT_OPCODE_IDENTITY 0
// Client send message.
#define CLIENT_OPCODE_MESSAGE 1
// Client saying that it want to disconnect.
#define CLIENT_OPCODE_DISCONNECT 2
// -- End of protocol specific
typedef struct {
// Network related.
int handle;
unsigned char *buffer;
short size;
// Application related.
int state;
char *identifier;
} Client;
/**
* Broadcast message to all client except the one specified by 'who' (if in [0 - count]).
*/
void broadcast(Client *pool, int count, int who, unsigned char *buffer, int size) {
for(int i = 0; i < count; ++i) {
int cursor = 0;
int sent;
int handle = pool[i].handle;
if(i == who) {
continue; // Not you !
}
while(cursor < size) {
sent = send(handle, buffer + cursor, size - cursor, 0);
if(sent < 0) {
// Whoops ... disconnected too ?? c'mon !
break;
}
cursor += sent;
}
if(cursor != size) {
// TODO Error handling.
}
}
}
void broadcastCommand(unsigned char opCode, Client *pool, int count, char *data, int who, unsigned char *buffer) {
unsigned char ucValue = (strlen(data) & 0xFF);
buffer[0] = opCode;
buffer[1] = ucValue;
memcpy(buffer + 2, data, ucValue);
broadcast(pool, count, who, buffer, ucValue + 2);
}
/**
* Broadcast an identity change.
* <SERVER_OPCODE_IDENTITY> <NAME_LEN> <NAME>
*/
void broadcastIdentification(Client *pool, int count, char *identifier, unsigned char *buffer) {
// Build message to broadcast.
broadcastCommand(SERVER_OPCODE_IDENTITY, pool, count, identifier, -1, buffer);
}
/**
* Broadcast a message.
* <SERVER_OPCODE_MESSAGE> <NAME_LEN> <NAME> <MSG_LEN> <MSG>
*/
void broadcastMessage(Client *pool, int count, char *message, unsigned char *buffer) {
// TODO Forgot the name !!
broadcastCommand(SERVER_OPCODE_MESSAGE, pool, count, message, -1, buffer);
}
/**
* Broadcast a disconnection message to all the clients.
* <SERVER_OPCODE_DISCONNECT> <NAME_LEN> <NAME>
*/
void broadcastDisconnection(Client *pool, int count, int who, unsigned char *buffer) {
// Protocol specific code comes here !
Client *subject = pool + who;
if(subject->state > STATE_IDENTIFY) { // Not nice, but effective.
// At least, the guy is identified. We can encoded.
broadcastCommand(SERVER_OPCODE_DISCONNECT, pool, count, subject->identifier, who, buffer);
}
}
void decode(int who, Client *pool, int count, unsigned char *buffer) {
// First, get the client.
Client *subject = pool + who;
// (... and check its sanity)
if(subject->state == STATE_FAULTED) {
subject->size = 0;
return;
}
// Second, get its input.
unsigned char *input = subject->buffer;
int consumed = 0;
while(subject->size > 0) {
// If we're here, it means we got at least one byte, so ...
// Third, get the opcode.
unsigned char opcode = *input;
// Fourth, given the opcode, make stuff.
switch(opcode) {
case CLIENT_OPCODE_IDENTITY:
if(subject->size > 2) {
// At least, we have the size too.
unsigned char idSize = input[1];
if(subject->size >= (idSize + 2)) {
// We can decode identity.
if(subject->identifier != NULL) {
delete [] subject->identifier;
}
subject->identifier = new char[idSize + 1];
memcpy(subject->identifier, subject->buffer + 2, idSize);
subject->identifier[idSize] = '\0';
subject->state = STATE_OPER;
// And now, broadcast the identity.
broadcastIdentification(pool, count, subject->identifier, buffer);
consumed = idSize + 2;
}
}
break;
case CLIENT_OPCODE_MESSAGE:
if(subject->state != STATE_OPER) {
subject->state = STATE_FAULTED;
subject->size = 0;
} else {
if(subject->size > 2) {
unsigned char msgSize = input[1];
if(subject-> size >= (msgSize + 2)) {
memcpy(buffer, input + 2, msgSize);
buffer[msgSize] = '\0';
broadcastMessage(pool, count, buffer, buffer + msgSize + 1);
// FIXME Code redundancy with identifier ?
}
}
}
break;
case CLIENT_OPCODE_DISCONNECT:
default:
subject->state = STATE_FAULTED;
subject->size = 0;
break;
}
if(consumed == 0) {
break;
} else if(consumed < subject->size) {
memmove(subject->buffer, subject->buffer + consumed, subject->size - consumed);
subject->size -= consumed;
} else {
subject->size = 0;
}
}
}
/**
* Manage entering connections.
* @param serverHandle Server Socket.
* @return 0 if everything went well, else -1.
*/
int manageConnections(int serverHandle) {
int returnCode = -1;
int maxHandleValue = serverHandle;
fd_set listeningSet;
fd_set testSet;
Client *pool = new Client[MAX_CLIENT];
int count = 0;
unsigned char *buffer = new unsigned char[MAX_MESSAGE_SIZE * 3];
// Initializing the set.
FD_ZERO(&listeningSet);
// Add the listening port to the set.
FD_SET(serverHandle, &listeningSet);
for(;;) {
testSet = listeningSet;
if(select(maxHandleValue + 1, &testSet, NULL, NULL, NULL) < 0) {
break;
}
// Manage clients.
for(int i = 0; i < count; ++i) {
if(FD_ISSET(pool[i].handle, &testSet)) {
int received = recv(pool[i].handle, pool[i].buffer + pool[i].size, MAX_MESSAGE_SIZE, 0);
if(received < 1) {
// Error or disconnected.
broadcastDisconnection(pool, count, i, buffer);
delete [] pool[i].buffer;
if(pool[i].identifier != NULL) {
delete [] pool[i].identifier;
}
close(pool[i].handle);
FD_CLR(pool[i].handle, &listeningSet);
--count;
if(count > 0) {
memcpy(pool + i, pool + count, sizeof(Client));
}
} else {
pool[i].size += received;
decode(i, pool, count, buffer);
}
}
}
// Manage listening socket.
if(FD_ISSET(serverHandle, &testSet)) {
struct sockaddr_storage connectingAddress;
socklen_t addressSize;
int connectingHandle;
addressSize = sizeof(connectingAddress);
connectingHandle = accept(serverHandle, (struct sockaddr *) &connectingAddress, &addressSize);
if(connectingHandle != -1) {
if(count == MAX_CLIENT) {
// We're full, refuse the connection.
close(connectingHandle);
} else {
pool[count].handle = connectingHandle;
pool[count].state = STATE_INIT;
pool[count].buffer = new unsigned char[MAX_MESSAGE_SIZE * 2];
pool[count].size = 0;
pool[count].identifier = NULL;
++count;
FD_SET(connectingHandle, &listeningSet);
if(connectingHandle > maxHandleValue) {
maxHandleValue = connectingHandle;
}
}
}
}
}
delete []buffer;
delete []pool;
return returnCode;
}
/**
* Launch the server !
* @param port String containing the port number or the service id.
* @return 0 if everything went well !
*/
int launch(char *port) {
// Hints about port resolution/binding procedure.
struct addrinfo hints;
// Result of the port probing.
struct addrinfo *result;
// Server socket.
int serverHandle;
int returnCode = -1;
memset(&hints, 0, sizeof(hints));
hints.ai_family = AF_UNSPEC; // Either IPv4 or IPv6.
hints.ai_socktype = SOCK_STREAM; // TCP Fun time !
hints.ai_flags = AI_PASSIVE; // Passive mode (nice for servers).
// Get local IP on the correct interface.
getaddrinfo(NULL, port, &hints, &result);
serverHandle = -1;
if(result != NULL) {
serverHandle = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
}
if(serverHandle != -1) {
if(bind(serverHandle, result->ai_addr, result->ai_addrlen) != 0) {
close(serverHandle);
serverHandle = -1;
}
}
if(result != NULL) {
freeaddrinfo(result);
}
if(serverHandle != -1) {
if(listen(serverHandle, PENDING_QUEUE_SIZE) != 0) {
close(serverHandle);
serverHandle = -1;
} else {
returnCode = manageConnections(serverHandle);
}
}
return returnCode;
}
int main(int argc, char **argv) {
// Let say the port is defined as the first argument.
char *port = (char *) (DEFAULT_PORT);
if(argc > 1) {
port = argv[1];
}
return launch(port);
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment