Created
April 28, 2012 13:16
-
-
Save StonedXander/2519019 to your computer and use it in GitHub Desktop.
An attempt of crappy IM server
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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