Created
March 4, 2018 17:46
-
-
Save Gydo194/1b442c029e79809257592c890e4a9c23 to your computer and use it in GitHub Desktop.
C99 compliant TCP multi-client single threaded chat server (again, not my work)
This file contains hidden or 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
#include <stdio.h> | |
#include <stdlib.h> | |
#include <string.h> | |
#include <unistd.h> | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <netinet/in.h> | |
#include <netdb.h> | |
#include <sys/time.h> //FD_ macros, for me only necessary in netbeans gcc for some reason | |
#define MSG_SIZE 1024 | |
#define NAME_SIZE 24 | |
#define MAX_CONNECTIONS 100 | |
#define PORT 3500 | |
// User list (Simple-linked list!!1) | |
struct chat_user { | |
int socket_fd; | |
char nickname[NAME_SIZE]; | |
struct sockaddr addr; | |
struct chat_user* next; | |
}; | |
// Messages to all connected clients. | |
void messageToAll(struct chat_user* firstUser, char* message, char closed) | |
{ | |
struct chat_user *ptrUser; | |
if(firstUser == NULL) | |
return; | |
for(ptrUser = firstUser; ptrUser != NULL; ptrUser = ptrUser->next) | |
{ | |
if( ptrUser->socket_fd == -1 ) | |
continue; | |
send(ptrUser->socket_fd, message, strlen(message), 0); | |
if( closed == 1 ) | |
close(ptrUser->socket_fd); | |
} | |
} | |
// Find user by socket id | |
struct chat_user* findUserBySocketId(struct chat_user* firstUser, int socket_id) | |
{ | |
struct chat_user *ptrUser; | |
if(firstUser == NULL) | |
return NULL; | |
for(ptrUser = firstUser; ptrUser != NULL; ptrUser = ptrUser->next) | |
{ | |
if( ptrUser->socket_fd == socket_id ) | |
return ptrUser; | |
} | |
return NULL; | |
} | |
int main(int argc, char *argv[]) | |
{ | |
// file descriptors | |
fd_set master_set, current_set; | |
// user structures pointers (first, last, and two to temporary manipulate) | |
struct chat_user *firstUser, *lastUser, *tempUser, *ptrUser; | |
// socket structures | |
struct sockaddr_in server_address; | |
struct sockaddr_storage addr_storage; | |
// sockets ids (server, max?, new?) | |
int server_socketfd, max_socketfd, new_socketfd; | |
// other | |
int i; | |
char msg[MSG_SIZE]; | |
/* | |
* Reference: | |
* http://www.mkssoftware.com/docs/man3/select.3.asp | |
*/ | |
FD_ZERO(&master_set); | |
FD_ZERO(¤t_set); | |
// Creating the socket itself. | |
server_socketfd = socket(AF_INET, SOCK_STREAM, 0); | |
// Describes type of connection family (?! better explain...) | |
server_address.sin_family = AF_INET; | |
server_address.sin_addr.s_addr = htonl(INADDR_ANY); | |
server_address.sin_port = htons(PORT); | |
// Bind created socket with it family | |
bind(server_socketfd, (struct sockaddr *)&server_address, sizeof(struct sockaddr_in)); | |
// Add socket identifier to master set (file descriptors) | |
FD_SET(server_socketfd, &master_set); | |
FD_SET(0, &master_set); | |
// Initialize max fd | |
max_socketfd = server_socketfd; | |
if( listen(server_socketfd, MAX_CONNECTIONS) == -1) | |
{ | |
perror("* Error with listen()"); | |
exit(1); | |
} | |
printf("*** Chat Service listening on port %d. ***\n", PORT); | |
// Loop | |
while(1) | |
{ | |
// Sets the current set to server itself | |
current_set = master_set; | |
// Select current sockets to parse | |
if( select(max_socketfd+1, ¤t_set, NULL, NULL, NULL) == -1 ) | |
{ | |
perror("* Error with select()"); | |
exit(1); | |
} | |
// Socket interaction | |
for(i = 0; i < FD_SETSIZE; i++) | |
{ | |
// Test if current set is really connected. | |
if(!FD_ISSET(i, ¤t_set)) | |
continue; | |
// New connection | |
if( i == server_socketfd ) | |
{ | |
// Try to accept the connection [TODO: replace NULLs with correctly info catchers) | |
if( (new_socketfd = accept(server_socketfd, NULL, NULL)) == -1 ) | |
{ | |
perror("* Error with accept()"); | |
exit(1); | |
} | |
printf("* Client Connected\n"); | |
// Add this socket to master set | |
FD_SET(new_socketfd, &master_set); | |
printf("New Socket ID: %d\n", new_socketfd); | |
// Create a userList member | |
if( firstUser == NULL ) | |
{ | |
// Allocate chat_user struct into userList pointer | |
firstUser = (struct chat_user*)malloc(sizeof(struct chat_user)); | |
firstUser->next = NULL; | |
firstUser->socket_fd = new_socketfd; | |
firstUser->addr = *((struct sockaddr *)&addr_storage); | |
lastUser = firstUser; | |
sprintf(firstUser->nickname, "Unknown %d", new_socketfd); | |
} | |
else | |
{ | |
tempUser = (struct chat_user*)malloc(sizeof(struct chat_user)); | |
tempUser->next = NULL; | |
tempUser->socket_fd = new_socketfd; | |
tempUser->addr = *((struct sockaddr *)&addr_storage); | |
sprintf(tempUser->nickname, "Unknown %d", new_socketfd); | |
// Update the pointer to next element | |
lastUser->next = tempUser; | |
// Update last element to current pointer | |
lastUser = tempUser; | |
} | |
// New element is the highest? Update variable | |
if( new_socketfd > max_socketfd ) | |
{ | |
max_socketfd = new_socketfd; | |
} | |
} // if( i == server_socketfd ) | |
else if( i == 0 ) | |
{ | |
// Keyboard interaction | |
char keyboard_msg[MSG_SIZE]; | |
fgets(keyboard_msg, MSG_SIZE + 1, stdin); | |
if( keyboard_msg[0] == '!') | |
{ | |
// Console Commands | |
if( strcmp(keyboard_msg+1, "quit\n") == 0 ) | |
{ | |
sprintf(msg, "[Server] Chat is closing down...\n"); | |
messageToAll(firstUser, msg, 1); | |
} | |
} | |
else | |
{ | |
sprintf(msg, "[Server] %s", keyboard_msg); | |
messageToAll(firstUser, msg, 1); | |
} | |
} // else if( i == 0 ) | |
else if( i ) | |
{ | |
// Client saying anything | |
char message[MSG_SIZE]; | |
int result; | |
if( (result = recv(i, message, MSG_SIZE, 0)) > 0 ) | |
{ | |
message[result]='\0'; | |
ptrUser = findUserBySocketId(firstUser, i); | |
if( ptrUser == NULL ) | |
{ | |
printf("* Problem searching user id %d\n", i); | |
continue; | |
} | |
if( message[0] == '$' ) | |
{ | |
// System Message | |
if( strcmp(message+1, "nick\n") == 0 ) | |
{ | |
if( strlen(ptrUser->nickname) > 0 ) | |
{ | |
sprintf(msg, "[Server] %s is now %s.\n", ptrUser->nickname, "Testing"); | |
messageToAll(firstUser, msg, 0); | |
} | |
sprintf(ptrUser->nickname, "%s", "Testing"); | |
} | |
} | |
else if( message[0] == '!' ) | |
{ | |
// User Command | |
if( strcmp(message+1, "quit\n") == 0 ) | |
{ | |
sprintf(msg, "[Server] %s is leaving...\n", ptrUser->nickname); | |
messageToAll(firstUser, msg, 0); | |
close(ptrUser->socket_fd); | |
} | |
} | |
else | |
{ | |
// Chat Message | |
sprintf(msg, "[%s] %s", ptrUser->nickname, message); | |
messageToAll(firstUser, msg, 0); | |
} | |
} | |
} // else if( i ) | |
else | |
{ | |
// Client disconnected | |
printf("*** CLIENT DISCONNECTED ***\n"); | |
} // else | |
} // for(i = 0; i < max_socketfd; i++) | |
} | |
return EXIT_SUCCESS; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment