Skip to content

Instantly share code, notes, and snippets.

@angleton
Forked from Gydo194/Server.cpp
Created January 4, 2023 00:46
Show Gist options
  • Save angleton/902e80753e494745a9206c5f2a334389 to your computer and use it in GitHub Desktop.
Save angleton/902e80753e494745a9206c5f2a334389 to your computer and use it in GitHub Desktop.
C++ Event driven TCP socket server (multi client, single threaded)
/*
* 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);
}
/*
* 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