Program Listing for File tcp_server.cpp
↰ Return to documentation for file (src/tap/communication/tcp-server/tcp_server.cpp
)
/*
* Copyright (c) 2020-2021 Advanced Robotics at the University of Washington <robomstr@uw.edu>
*
* This file is part of Taproot.
*
* Taproot is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* Taproot is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with Taproot. If not, see <https://www.gnu.org/licenses/>.
*/
#ifdef PLATFORM_HOSTED
#include "tcp_server.hpp"
#ifdef __linux__
#include <netinet/in.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <unistd.h>
#endif // __linux__
#include <atomic>
#include <iostream>
#include <stdexcept>
#include "json_messages.hpp"
using std::cerr;
namespace tap
{
namespace communication
{
TCPServer::TCPServer(int targetPortNumber)
#ifdef __linux__
: socketOpened(false),
clientConnected(false),
mainClientDescriptor(-1),
serverAddress(),
portNumber(-1)
#endif // __linux__
{
#ifdef __linux__
// Do sockety stuff.
listenFileDescriptor = socket(AF_INET, SOCK_STREAM, 0);
if (listenFileDescriptor < 0)
{
perror("TCPServer failed to open socket");
throw std::runtime_error("SocketError");
}
int yes = 1;
if (setsockopt(listenFileDescriptor, SOL_SOCKET, SO_REUSEADDR, &yes, sizeof(yes)))
{
perror("TCPSever failed to set socket options");
throw std::runtime_error("SocketOptionsError");
}
socketOpened = true;
serverAddress.sin_family = AF_INET;
serverAddress.sin_port = htons(targetPortNumber);
serverAddress.sin_addr.s_addr = INADDR_ANY;
int n = bind(
listenFileDescriptor,
reinterpret_cast<sockaddr*>(&serverAddress),
sizeof(serverAddress));
if (n < 0)
{
perror("TCPServer failed to bind socket");
// Good reference for why this error occurs and what it means:
// https://hea-www.harvard.edu/~fine/Tech/addrinuse.html
std::cerr << "If you're seeing this error it means that the TCPServer "
"has failed to bind to a port too many times. If you're seeing this "
"message, something is probably really messed up."
<< std::endl;
throw std::runtime_error("BindError");
}
portNumber = targetPortNumber;
listen(listenFileDescriptor, LISTEN_QUEUE_SIZE);
std::cout << "TCPServer initialized on port: " << targetPortNumber << std::endl;
std::cout << "call getConnection() to accept client" << std::endl;
#else
UNUSED(targetPortNumber);
#endif // __linux__
}
TCPServer::~TCPServer()
{
#ifdef __linux__
close(listenFileDescriptor);
close(mainClientDescriptor);
#endif // __linux__
}
TCPServer* TCPServer::MainServer()
{
#if defined(ENV_UNIT_TESTS) || !defined(__linux__)
return nullptr;
#else
return &mainServer;
#endif
}
void TCPServer::getConnection()
{
#ifdef __linux__
sockaddr_in clientAddress;
socklen_t clientAddressLength = sizeof(clientAddress);
mainClientDescriptor = accept(
listenFileDescriptor,
reinterpret_cast<sockaddr*>(&clientAddress),
&clientAddressLength);
cerr << "TCPServer: connection accepted" << std::endl;
#endif // __linux__
}
void TCPServer::closeConnection()
{
#ifdef __linux__
close(mainClientDescriptor);
mainClientDescriptor = -1;
std::cout << "TCPServer: closed connection with client, "
"use getConnection() to connect to a new one";
#endif // __linux__
}
uint16_t TCPServer::getPortNumber()
{
#ifdef __linux__
return this->portNumber;
#else
return 0;
#endif // __linux__
}
void TCPServer::writeToClient(const char* message, int32_t messageLength)
{
#ifdef __linux__
if (mainClientDescriptor < 0)
{
// Not necessarily an error if fileDescriptor still hasn't been opened
// so we just don't write to anything and early return.
cerr << "TCPServer: mainClientDescriptor not connected yet" << std::endl;
return;
}
try
{
writeMessage(mainClientDescriptor, message, messageLength);
}
catch (std::runtime_error& e)
{
std::cerr << e.what() << std::endl;
}
#else
UNUSED(message);
UNUSED(messageLength);
#endif // __linux__
}
#ifdef __linux__
void readMessage(int16_t fileDescriptor, char* readBuffer, uint16_t messageLength)
{
readBuffer[messageLength] = '\0'; // Null terminate the message
uint16_t bytesRead = read(fileDescriptor, readBuffer, messageLength);
while (bytesRead < messageLength)
{
int32_t n = read(fileDescriptor, readBuffer + bytesRead, messageLength - bytesRead);
if (n < 0)
{
if (errno == EAGAIN or errno == EINTR)
{
continue;
}
else
{
perror("TCPServer failed to read from client");
throw std::runtime_error("ReadingError");
}
}
bytesRead += n;
}
}
void writeMessage(int16_t fileDescriptor, const char* message, uint16_t bytes)
{
uint32_t bytesWritten = 0;
while (bytesWritten < bytes)
{
int32_t n = write(fileDescriptor, message + bytesWritten, bytes - bytesWritten);
if (n < 0)
{
if (errno == EAGAIN or errno == EINTR)
{
continue;
}
else
{
perror("TCPServer failed to write");
throw std::runtime_error("WriteError");
}
}
bytesWritten += n;
}
}
int32_t readInt32(int16_t fileDescriptor)
{
char buffer[5];
readMessage(fileDescriptor, buffer, 4);
int32_t answer = 0;
for (size_t i = 0; i < 4; i++)
{
answer = answer | buffer[i];
answer <<= 8;
}
return answer;
}
#endif // __linux__
// Only construct static singleton in actual sim, not in unit tests.
#ifndef ENV_UNIT_TESTS
// Definition of static variable. mainServer never created otherwise.
TCPServer TCPServer::mainServer(2001);
#endif
} // namespace communication
} // namespace tap
#endif // PLATFORM_HOSTED