Skip to content
Snippets Groups Projects
server.cpp 5.03 KiB
Newer Older
#include "server.hpp"
#include "proto.hpp"
#include <cstring>
#include <definitions.hpp>
#include <iostream>
#include <sys/socket.h>

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @brief Creates a Server instace (singleton)
 *
 */
void Server::createInstance() {
  if (instance == nullptr) {
    instance = new Server(DEFAULT_PORT);
  }
}

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @brief Creates a Server instance with custom port
 *
 * @param port
 */
void Server::createInstance(const int &port) {
  if (instance == nullptr) {
    instance = new Server(port);
  }
}
Lukas Güldenstein's avatar
Lukas Güldenstein committed

/**
 * @brief Construct a new Server object with default port
 *
 */
Server::Server() { setup(DEFAULT_PORT); }

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @brief Construct a new Server object with custom port
 *
 * @param port
 */
Server::Server(const int &port) { setup(port); }

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @brief Destroy the Server object and close the master socket
 *
 */
Server::~Server() { close(mastersocket_fd); }

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @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
}

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @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();
  }
}

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @brief Binds to socket
 *
 */
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;
}

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @brief Starts listening on mastersocket
 *
 */
void Server::startListen() {
  int rc = listen(mastersocket_fd, 3);
  if (rc < 0) {
    perror("listen() failed");
  }
}

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @brief Stops the server
 *
 */
void Server::stop() { close(mastersocket_fd); }

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @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);
}

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @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);
}

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @brief Starts the server
 *
 * @details Loops over set of sockets and handles new connections or available
 * data on existing connections
 *
 */
  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);
      }
    }
  }
}

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @brief Wrapper function for initialization
 *
 */
void Server::init() {
  initializeSocket();
  bindSocket();
  startListen();
}

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @brief Sets the callback for reveiving data
 *
 * @param cb
 */
void Server::onInput(receiveCallbackType cb) { receiveCallback = cb; }

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @brief Sets the callback for handling new connections
 *
 * @param cb
 */
void Server::onConnect(newConnectionCallbackType cb) {
  newConnectionCallback = cb;
}

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @brief Sets the callback for handling disconnects
 *
 * @param cb
 */
void Server::onDisconnect(disconnectCallbackType cb) {
  disconnectCallback = cb;
}

Lukas Güldenstein's avatar
Lukas Güldenstein committed
/**
 * @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);
Lukas Güldenstein's avatar
Lukas Güldenstein committed
}

Server *Server::instance;