Newer
Older
#include <definitions.hpp>
/**
* @brief Creates a Server instace (singleton)
*
*/
void Server::createInstance() {
if (instance == nullptr) {
instance = new Server(DEFAULT_PORT);
}
}
/**
* @brief Creates a Server instance with custom port
*
* @param port
*/
void Server::createInstance(const int &port) {
if (instance == nullptr) {
instance = new Server(port);
}
}
/**
* @brief Construct a new Server object with default port
*
*/
Server::Server() { setup(DEFAULT_PORT); }
/**
* @brief Construct a new Server object with custom port
*
* @param port
*/
Server::Server(const int &port) { setup(port); }
/**
* @brief Destroy the Server object and close the master socket
*
*/
Server::~Server() { close(mastersocket_fd); }
/**
* @brief Setup server with socket and port
*
* @param port
*/
void Server::setup(int port) {
mastersocket_fd = socket(AF_INET, SOCK_STREAM, 0);
if (mastersocket_fd < 0) {
perror("Error creating socket");
}
FD_ZERO(&masterfds);
FD_ZERO(&tmpfds);
memset(&server_addr, 0, sizeof(server_addr));
server_addr.sin_family = AF_INET;
server_addr.sin_addr.s_addr = htons(INADDR_ANY);
server_addr.sin_port = htons(port);
bzero(input_buffer, INPUT_BUFFER_SIZE); // no random data in input buffer
}
/**
* @brief Initializes the master socket
*
*/
void Server::initializeSocket() {
int opt_value = 1;
int rc = setsockopt(mastersocket_fd, SOL_SOCKET, SO_REUSEADDR,
(char *)&opt_value, sizeof(int));
if (rc < 0) {
perror("setsocketopt() failed");
stop();
}
}
void Server::bindSocket() {
int rc = bind(mastersocket_fd, (struct sockaddr *)&server_addr,
sizeof(server_addr));
if (rc < 0) {
perror("bind() failed");
}
FD_SET(mastersocket_fd, &masterfds);
maxfd = mastersocket_fd;
}
/**
* @brief Starts listening on mastersocket
*
*/
void Server::startListen() {
int rc = listen(mastersocket_fd, 3);
if (rc < 0) {
perror("listen() failed");
}
}
void Server::stop() { close(mastersocket_fd); }
/**
* @brief Handles new connection
*
* @details Calls the given callback function if client connects
*
*/
void Server::handleNewConnection() {
socklen_t addrLen = sizeof(client_addr);
tempsocket_fd =
accept(mastersocket_fd, (struct sockaddr *)&client_addr, &addrLen);
if (tempsocket_fd < 0) {
perror("accept() failed");
} else {
FD_SET(tempsocket_fd, &masterfds);
if (tempsocket_fd > maxfd) {
maxfd = tempsocket_fd;
}
}
newConnectionCallback(tempsocket_fd);
}
/**
* @brief Receives from existing socket
*
* @details Calls the given callback function if message has been received
*
* @param fd
*/
void Server::recvInputFromExisting(int fd) {
int nbytesrecv = recv(fd, input_buffer, INPUT_BUFFER_SIZE, 0);
if (nbytesrecv <= 0) {
if (nbytesrecv == 0) {
disconnectCallback((uint16_t)fd);
close(fd);
FD_CLR(fd, &masterfds);
return;
} else {
perror("recv() failed");
}
close(fd);
FD_CLR(fd, &masterfds);
return;
}
auto msg = proto::Message();
msg.ParseFromArray(input_buffer, nbytesrecv);
receiveCallback(fd, msg);
// clear input buffer
bzero(&input_buffer, INPUT_BUFFER_SIZE);
}
/**
* @brief Starts the server
*
* @details Loops over set of sockets and handles new connections or available
* data on existing connections
*
*/
void Server::run() {
tmpfds = masterfds;
int sel = select(maxfd + 1, &tmpfds, NULL, NULL, NULL);
if (sel < 0) {
perror("select() failed");
stop();
}
// loop over the set of file descriptors and see if we can act
for (int i = 0; i <= maxfd; i++) {
if (FD_ISSET(i, &tmpfds)) {
// there is something to be done
if (mastersocket_fd == i) {
// we have a new connection
handleNewConnection();
} else {
// new data on existing connection
recvInputFromExisting(i);
}
}
}
}
/**
* @brief Wrapper function for initialization
*
*/
void Server::init() {
initializeSocket();
bindSocket();
startListen();
}
/**
* @brief Sets the callback for reveiving data
*
* @param cb
*/
void Server::onInput(receiveCallbackType cb) { receiveCallback = cb; }
/**
* @brief Sets the callback for handling new connections
*
* @param cb
*/
void Server::onConnect(newConnectionCallbackType cb) {
newConnectionCallback = cb;
}
/**
* @brief Sets the callback for handling disconnects
*
* @param cb
*/
void Server::onDisconnect(disconnectCallbackType cb) {
disconnectCallback = cb;
}
/**
* @brief Sends protobuf message on socket
*
* @details Serializes protobuf message as byte data
*
* @param source_fd
* @param msg
* @return uint16_t
*/
uint16_t Server::sendMessage(int source_fd, const proto::Message &msg) {
uint8_t buf[msg.ByteSizeLong()];
msg.SerializeToArray(buf, msg.ByteSizeLong());
return send(source_fd, buf, msg.ByteSizeLong(), 0);