Last active
May 21, 2024 08:18
-
-
Save Gydo194/c14e52701289354ab66359c2a75706f8 to your computer and use it in GitHub Desktop.
C++ Event driven TCP socket server (multi client, single threaded)
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
/* | |
* Server.cpp | |
* | |
* EventServer is a simple C++ TCP socket server implementation, | |
* to serve as an example to anyone who wants to learn it. | |
* It can interface with the rest of your program using three callback functions. | |
* - onConnect, which fires when a new client connects. the client's fd is passed. | |
* - onDisconnect, which fires when a client disconnects. passes fd. | |
* - onInput, fires when input is received from a client. passes fd and char* | |
* | |
* define SERVER_DEBUG to spew out some juicy debug data! | |
* | |
* Original work by Gydo194, BSD 3-clause license below applies. | |
* | |
* For an usage example, see the following project: https://github.com/Gydo194/server | |
* | |
* Use with neccesary caution, this code has not been audited or tested for performance problems, security holes, memory leaks etc. | |
* | |
*/ | |
/* | |
Copyright 2018-2021 Gydo194 | |
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
#include "Server.hpp" | |
Server::Server() | |
{ | |
setup(DEFAULT_PORT); | |
} | |
Server::Server(int port) | |
{ | |
setup(port); | |
} | |
Server::Server(const Server& orig) | |
{ | |
} | |
Server::~Server() | |
{ | |
#ifdef SERVER_DEBUG | |
std::cout << "[SERVER] [DESTRUCTOR] Destroying Server...\n"; | |
#endif | |
close(mastersocket_fd); | |
} | |
void Server::setup(int port) | |
{ | |
mastersocket_fd = socket(AF_INET, SOCK_STREAM, 0); | |
if (mastersocket_fd < 0) { | |
perror("Socket creation failed"); | |
} | |
FD_ZERO(&masterfds); | |
FD_ZERO(&tempfds); | |
memset(&servaddr, 0, sizeof (servaddr)); //bzero | |
servaddr.sin_family = AF_INET; | |
servaddr.sin_addr.s_addr = htons(INADDR_ANY); | |
servaddr.sin_port = htons(port); | |
bzero(input_buffer,INPUT_BUFFER_SIZE); //zero the input buffer before use to avoid random data appearing in first receives | |
} | |
void Server::initializeSocket() | |
{ | |
#ifdef SERVER_DEBUG | |
std::cout << "[SERVER] initializing socket\n"; | |
#endif | |
int opt_value = 1; | |
int ret_test = setsockopt(mastersocket_fd, SOL_SOCKET, SO_REUSEADDR, (char *) &opt_value, sizeof (int)); | |
#ifdef SERVER_DEBUG | |
printf("[SERVER] setsockopt() ret %d\n", ret_test); | |
#endif | |
if (ret_test < 0) { | |
perror("[SERVER] [ERROR] setsockopt() failed"); | |
shutdown(); | |
} | |
} | |
void Server::bindSocket() | |
{ | |
#ifdef SERVER_DEBUG | |
std::cout << "[SERVER] binding...\n"; | |
#endif | |
int bind_ret = bind(mastersocket_fd, (struct sockaddr*) &servaddr, sizeof (servaddr)); | |
#ifdef SERVER_DEBUG | |
printf("[SERVER] bind() ret %d\n", bind_ret); | |
#endif | |
if (bind_ret < 0) { | |
perror("[SERVER] [ERROR] bind() failed"); | |
} | |
FD_SET(mastersocket_fd, &masterfds); //insert the master socket file-descriptor into the master fd-set | |
maxfd = mastersocket_fd; //set the current known maximum file descriptor count | |
} | |
void Server::startListen() | |
{ | |
#ifdef SERVER_DEBUG | |
std::cout << "[SERVER] listen starting...\n"; | |
#endif | |
int listen_ret = listen(mastersocket_fd, 3); | |
#ifdef SERVER_DEBUG | |
printf("[SERVER] listen() ret %d\n", listen_ret); | |
#endif | |
if (listen_ret < 0) { | |
perror("[SERVER] [ERROR] listen() failed"); | |
} | |
} | |
void Server::shutdown() | |
{ | |
int close_ret = close(mastersocket_fd); | |
#ifdef SERVER_DEBUG | |
printf("[SERVER] [DEBUG] [SHUTDOWN] closing master fd.. ret '%d'.\n",close_ret); | |
#endif | |
} | |
void Server::handleNewConnection() | |
{ | |
#ifdef SERVER_DEBUG | |
std::cout << "[SERVER] [CONNECTION] handling new connection\n"; | |
#endif | |
socklen_t addrlen = sizeof (client_addr); | |
tempsocket_fd = accept(mastersocket_fd, (struct sockaddr*) &client_addr, &addrlen); | |
if (tempsocket_fd < 0) { | |
perror("[SERVER] [ERROR] accept() failed"); | |
} else { | |
FD_SET(tempsocket_fd, &masterfds); | |
//increment the maximum known file descriptor (select() needs it) | |
if (tempsocket_fd > maxfd) { | |
maxfd = tempsocket_fd; | |
#ifdef SERVER_DEBUG | |
std::cout << "[SERVER] incrementing maxfd to " << maxfd << std::endl; | |
#endif | |
} | |
#ifdef SERVER_DEBUG | |
printf("[SERVER] [CONNECTION] New connection on socket fd '%d'.\n",tempsocket_fd); | |
#endif | |
} | |
newConnectionCallback(tempsocket_fd); //call the callback | |
} | |
void Server::recvInputFromExisting(int fd) | |
{ | |
int nbytesrecv = recv(fd, input_buffer, INPUT_BUFFER_SIZE, 0); | |
if (nbytesrecv <= 0) | |
{ | |
//problem | |
if (0 == nbytesrecv) | |
{ | |
disconnectCallback((uint16_t)fd); | |
close(fd); //well then, bye bye. | |
FD_CLR(fd, &masterfds); | |
return; | |
} else | |
{ | |
perror("[SERVER] [ERROR] recv() failed"); | |
} | |
close(fd); //close connection to client | |
FD_CLR(fd, &masterfds); //clear the client fd from fd set | |
return; | |
} | |
#ifdef SERVER_DEBUG | |
printf("[SERVER] [RECV] Received '%s' from client!\n", input_buffer); | |
#endif | |
receiveCallback(fd,input_buffer); | |
//memset(&input_buffer, 0, INPUT_BUFFER_SIZE); //zero buffer //bzero | |
bzero(&input_buffer,INPUT_BUFFER_SIZE); //clear input buffer | |
} | |
void Server::loop() | |
{ | |
tempfds = masterfds; //copy fd_set for select() | |
#ifdef SERVER_DEBUG | |
printf("[SERVER] [MISC] max fd = '%hu' \n", maxfd); | |
std::cout << "[SERVER] [MISC] calling select()\n"; | |
#endif | |
int sel = select(maxfd + 1, &tempfds, NULL, NULL, NULL); //blocks until activity | |
//printf("[SERVER] [MISC] select() ret %d, processing...\n", sel); | |
if (sel < 0) { | |
perror("[SERVER] [ERROR] select() failed"); | |
shutdown(); | |
} | |
//no problems, we're all set | |
//loop the fd_set and check which socket has interactions available | |
for (int i = 0; i <= maxfd; i++) { | |
if (FD_ISSET(i, &tempfds)) { //if the socket has activity pending | |
if (mastersocket_fd == i) { | |
//new connection on master socket | |
handleNewConnection(); | |
} else { | |
//exisiting connection has new data | |
recvInputFromExisting(i); | |
} | |
} //loop on to see if there is more | |
} | |
} | |
void Server::init() | |
{ | |
initializeSocket(); | |
bindSocket(); | |
startListen(); | |
} | |
void Server::onInput(void (*rc)(uint16_t fd, char *buffer)) | |
{ | |
receiveCallback = rc; | |
} | |
void Server::onConnect(void(*ncc)(uint16_t)) | |
{ | |
newConnectionCallback = ncc; | |
} | |
void Server::onDisconnect(void(*dc)(uint16_t)) | |
{ | |
disconnectCallback = dc; | |
} | |
uint16_t Server::sendMessage(Connector conn, char *messageBuffer) { | |
return send(conn.source_fd,messageBuffer,strlen(messageBuffer),0); | |
} | |
uint16_t Server::sendMessage(Connector conn, const char *messageBuffer) { | |
return send(conn.source_fd,messageBuffer,strlen(messageBuffer),0); | |
} |
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
/* | |
* Server.hpp | |
* | |
* EventServer is a simple C++ TCP socket server implementation, | |
* to serve as an example to anyone who wants to learn it. | |
* It can interface with the rest of your program using three callback functions. | |
* - onConnect, which fires when a new client connects. the client's fd is passed. | |
* - onDisconnect, which fires when a client disconnects. passes fd. | |
* - onInput, fires when input is received from a client. passes fd and char* | |
* | |
* define SERVER_DEBUG to spew out some juicy debug data! | |
* | |
* Original work by Gydo194, BSD 3-clause license below applies. | |
* | |
* For an usage example, see the following project: https://github.com/Gydo194/server | |
* | |
* Use with neccesary caution, this code has not been audited or tested for performance problems, security holes, memory leaks etc. | |
* | |
*/ | |
/* | |
Copyright 2018-2021 Gydo194 | |
Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: | |
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. | |
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. | |
3. Neither the name of the copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. | |
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. | |
*/ | |
#ifndef SERVER_HPP | |
#define SERVER_HPP | |
#include <cstdio> | |
#include <cstdlib> | |
#include <cstring> | |
#include <unistd.h> | |
#include <sys/types.h> | |
#include <sys/socket.h> | |
#include <netinet/in.h> //sockaddr, socklen_t | |
#include <arpa/inet.h> | |
#include <netdb.h> | |
#include <iostream> | |
#define INPUT_BUFFER_SIZE 100 //test: 100 bytes of buffer | |
#define DEFAULT_PORT 9034 | |
class Server | |
{ | |
public: | |
Server(); | |
Server(int port); | |
Server(const Server& orig); | |
virtual ~Server(); | |
struct Connector { | |
uint16_t source_fd; | |
}; | |
void shutdown(); | |
void init(); | |
void loop(); | |
//callback setters | |
void onConnect(void (*ncc)(uint16_t fd)); | |
void onInput(void (*rc)(uint16_t fd, char *buffer)); | |
void onDisconnect(void (*dc)(uint16_t fd)); | |
uint16_t sendMessage(Connector conn, const char *messageBuffer); | |
uint16_t sendMessage(Connector conn, char *messageBuffer); | |
private: | |
//fd_set file descriptor sets for use with FD_ macros | |
fd_set masterfds; | |
fd_set tempfds; | |
//unsigned integer to keep track of maximum fd value, required for select() | |
uint16_t maxfd; | |
//socket file descriptors | |
int mastersocket_fd; //master socket which receives new connections | |
int tempsocket_fd; //temporary socket file descriptor which holds new clients | |
//client connection data | |
struct sockaddr_storage client_addr; | |
//server socket details | |
struct sockaddr_in servaddr; | |
//input buffer | |
char input_buffer[INPUT_BUFFER_SIZE]; | |
char remote_ip[INET6_ADDRSTRLEN]; | |
//int numbytes; | |
void (*newConnectionCallback) (uint16_t fd); | |
void (*receiveCallback) (uint16_t fd, char *buffer); | |
void (*disconnectCallback) (uint16_t fd); | |
//function prototypes | |
void setup(int port); | |
void initializeSocket(); | |
void bindSocket(); | |
void startListen(); | |
void handleNewConnection(); | |
void recvInputFromExisting(int fd); | |
//void *getInetAddr(struct sockaddr *saddr); | |
}; | |
#endif /* SERVER_HPP */ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Thanks a lot for your implementation!