Created
March 4, 2018 20:37
-
-
Save Gydo194/c105d2b5f0af5c9807166023f694a2df to your computer and use it in GitHub Desktop.
C99 compliant chat server (2.0)
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 <arpa/inet.h> | |
#include <netdb.h> | |
#define INPUT_BUFFER_SIZE 2048 //2k buffer | |
#define PORT "9034" // port we're listening on | |
/////global variables | |
fd_set master_fds; // master file descriptor list | |
fd_set read_fds; // temp file descriptor list for select() | |
int fdmax; // maximum file descriptor number | |
int masterSocket; // listening socket descriptor | |
int newfd; // newly accept()ed socket descriptor | |
struct sockaddr_storage remoteaddr; // client address | |
socklen_t addrlen; | |
char buf[INPUT_BUFFER_SIZE]; // buffer for client data | |
int nbytes; | |
char remoteIP[INET6_ADDRSTRLEN]; | |
int opt = 1; // for setsockopt() SO_REUSEADDR, below | |
int i, j, rv, select_ret; | |
struct addrinfo server_addr, *ai, *p; | |
//end global variables | |
void safeShutdown(int code) { | |
close(masterSocket); | |
printf("[INFO] [SHUTDOWN] safely shutting down server..."); | |
exit(code); | |
} | |
void setup() { | |
printf("[SERVER] starting server\n"); | |
FD_ZERO(&master_fds); // clear the master and temp sets | |
FD_ZERO(&read_fds); | |
// get us a socket and bind it | |
memset(&server_addr, 0, sizeof server_addr); //bzero? | |
server_addr.ai_family = AF_INET; //AF_UNSPEC | |
server_addr.ai_socktype = SOCK_STREAM; | |
server_addr.ai_flags = AI_PASSIVE; | |
rv = getaddrinfo(NULL, PORT, &server_addr, &ai); | |
if (0 != rv) { | |
fprintf(stderr, "[ERROR]: %s\n", gai_strerror(rv)); | |
safeShutdown(EXIT_FAILURE); | |
} | |
for (p = ai; p != NULL; p = p->ai_next) { | |
masterSocket = socket(p->ai_family, p->ai_socktype, p->ai_protocol); | |
if (masterSocket < 0) { | |
printf("[DEBUG] socket creation failed; continue\n"); | |
continue; | |
} | |
//address already in use? | |
setsockopt(masterSocket, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof (int)); | |
if (bind(masterSocket, p->ai_addr, p->ai_addrlen) < 0) { | |
close(masterSocket); | |
continue; | |
} | |
break; | |
} | |
// if we got here, it means we didn't get bound | |
if (p == NULL) { | |
fprintf(stderr, "[ERROR] [FATAL]: failed to bind\n"); | |
safeShutdown(EXIT_FAILURE); | |
} | |
freeaddrinfo(ai); // all done with this | |
// listen | |
if (listen(masterSocket, 10) == -1) { | |
perror("listen"); | |
safeShutdown(EXIT_FAILURE); | |
} | |
// add the main socket to the master set | |
FD_SET(masterSocket, &master_fds); | |
// keep track of the biggest file descriptor | |
fdmax = masterSocket; // so far, it's this one | |
printf("[SERVER] setupinit completed.\n"); | |
} | |
// get sockaddr, IPv4 or IPv6: | |
void *get_in_addr(struct sockaddr *sa) { | |
if (sa->sa_family == AF_INET) { | |
return &(((struct sockaddr_in*) sa)->sin_addr); | |
} | |
return &(((struct sockaddr_in6*) sa)->sin6_addr); | |
} | |
void handleConnection() { | |
// handle new connections | |
addrlen = sizeof remoteaddr; | |
newfd = accept(masterSocket, | |
(struct sockaddr *) &remoteaddr, | |
&addrlen); | |
if (newfd < 0) { | |
perror("accept"); | |
} else { | |
FD_SET(newfd, &master_fds); // add to master set | |
if (newfd > fdmax) { // keep track of the max | |
fdmax = newfd; | |
} | |
printf("[INFO]: new connection from '%s'; on socket '%d'\n", inet_ntop(remoteaddr.ss_family, get_in_addr((struct sockaddr*) &remoteaddr), remoteIP, INET6_ADDRSTRLEN), newfd); | |
} | |
} | |
void handleInput() { | |
// handle data from a client | |
nbytes = recv(i, buf, sizeof buf, 0); | |
if (nbytes <= 0) { | |
// got error or connection closed by client | |
if (nbytes == 0) { | |
// connection closed | |
printf("[INFO]: socket '%d' hung up (closed connection)\n", i); | |
} else { | |
perror("recv failed"); | |
} | |
close(i); // bye! | |
FD_CLR(i, &master_fds); // remove from master set | |
} else { | |
// we got some data from a client | |
//send the data we've recv()'d to all clients | |
//loop the fd_set socket fd's | |
for (j = 0; j <= fdmax; j++) { | |
// send to everyone! | |
if (FD_ISSET(j, &master_fds)) { //check for socket integrity | |
// except the listener and the socket the data came from | |
if (j != masterSocket && j != i) { | |
if (send(j, buf, nbytes, 0) == -1) { | |
perror("send failed"); | |
} | |
} | |
} | |
} | |
} | |
} | |
int main(void) { | |
//run server setup once | |
setup(); | |
while (1) { | |
read_fds = master_fds; // copy it | |
select_ret = select(fdmax + 1, &read_fds, NULL, NULL, NULL); //blocking call, waits for activity on one of the sockets in the fd_set passed to it (read_fds) | |
if (select_ret < 0) { | |
perror("select failed"); | |
exit(EXIT_FAILURE); | |
} | |
//loop fd_set looking for new data (FD_ISSET) | |
for (i = 0; i <= fdmax; i++) { | |
if (FD_ISSET(i, &read_fds)) { //got activity on one of the sockets | |
if (i == masterSocket) { | |
//new connection, master socket was select()'ed | |
handleConnection(); | |
} else { | |
//existing connection sending new data | |
handleInput(); | |
} | |
} | |
} | |
} | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment