0

I am curently trying to add a multiplayer system to my game engine. To do so i decided to use the TCP approch. Since i am already using the SDL2 library i decided to use the SDL2-net library for networking.

I wish to send data to the server and vice versa. Sending small amounts of data isn't a problem however when i want to send a big chunk of data, a piece of terrain for example, the client will receiver will might get some other data mixed in between the terrain data which is of course not wanted. I am no expert in networking so there might be some basic concepts that i do not know of, and i would be more than happy to know if there is a solution to this "problem".

Thanks for your help!

In the following code a client sends a large array of numbers(in Client constructor) to a server and also a message when the client object gets destroyed(in Client destructor) but these two messages get mixed up.

Client.h

#ifndef CLIENT_H
#define CLIENT_H

#include <SDL2/SDL_net.h>

namespace gt
{
  namespace ns
  {
    namespace networking
    {
      class Client
      {
      private:
        TCPsocket socket;
        SDLNet_SocketSet socketSet;

        void sendData(const char*) const;
      public:
        Client(const char*, int);
        ~Client();

        void checkIncomingData();
      };
    }
  }
}

#endif

Client.cpp

#include "Client.h"

#include <iostream>
#include <cstring>

namespace gt
{
  namespace ns
  {
    namespace networking
    {
      Client::Client(const char* address, int port)
      {
        SDLNet_Init();
        IPaddress ip;
        if(SDLNet_ResolveHost(&ip, address, port) == -1)
        {
          std::cout << "Client connection error!" << std::endl;
        }
        socket = SDLNet_TCP_Open(&ip);
        if(socket == NULL)
        {
          std::cout << "Client connection error! (wrong ip address?)" << std::endl;
        }
        socketSet = SDLNet_AllocSocketSet(1);
        SDLNet_TCP_AddSocket(socketSet, socket);
        sendData("<for the sake of simlicity i removed the numbers that were here>\n");
      }

      Client::~Client()
      {
        sendData("Client disconecting!\n");
        SDLNet_TCP_Close(socket);
        SDLNet_FreeSocketSet(socketSet);
        SDLNet_Quit();
      }

      void Client::sendData(const char* data) const
      {
        int size = strlen(data);
        int sentsize = 0;
        while(sentsize < size)
        {
          sentsize+=SDLNet_TCP_Send(socket, data + sentsize, size - sentsize);
        }
      }

      void Client::checkIncomingData()
      {
        while(SDLNet_CheckSockets(socketSet,0) > 0)
        {
          if(SDLNet_SocketReady(socket))
          {
            char data[1400];
            SDLNet_TCP_Recv(socket, data, 1400);
            std::cout << "data received: " << data << std::endl;
          }
        }
      }
    }
  }
}

Server.h

#ifndef SERVER_H
#define SERVER_H

#include <SDL2/SDL_net.h>

#include "ClientData.h"

#include "../utils/ArrayList.h"

namespace gt
{
  namespace ns
  {
    namespace networking
    {
      class Server
      {
      private:
        int maxClients;

        IPaddress ip;
        TCPsocket socket;
        ArrayList<ClientData> clients;
        SDLNet_SocketSet clientsSet;

        int currentId;

        void removeClient(int);
        void checkNewConnection();
        void checkIncomingData();
        void checkTimeouts(float);
        bool readData(const TCPsocket&);
      public:
        Server(int, int);
        ~Server();

        void update(float);
      };
    }
  }
}

#endif

Server.cpp

#include "Server.h"

#include <iostream>
#include <cstring>

namespace gt
{
  namespace ns
  {
    namespace networking
    {
      Server::Server(int maxClients, int port)
      {
        this->maxClients = maxClients;

        clientsSet = SDLNet_AllocSocketSet(maxClients);

        SDLNet_ResolveHost(&ip, NULL, port);
        socket = SDLNet_TCP_Open(&ip);

        currentId = 1;
      }

      Server::~Server()
      {
        SDLNet_FreeSocketSet(clientsSet);
        SDLNet_TCP_Close(socket);
      }

      void Server::removeClient(int index)
      {
        SDLNet_TCP_DelSocket(clientsSet, clients[index].getSocket());
        clients.remove(index);
      }

      void Server::checkNewConnection()
      {
        TCPsocket clientSocket = SDLNet_TCP_Accept(socket);
        if(clientSocket)
        {
          if(clients.size() < maxClients)
          {
            SDLNet_TCP_AddSocket(clientsSet, clientSocket);
            ClientData data(currentId, clientSocket);
            clients.add(data);
            std::cout << "new connection id: " << currentId << std::endl;
            currentId++;
          }
          else
          {
            //no more space!
          }
        }
      }

      void Server::checkIncomingData()
      {
        while(SDLNet_CheckSockets(clientsSet, 0) > 0)
        {
          for(int i=0;i<clients.size();i++)
          {
            if(SDLNet_SocketReady(clients[i].getSocket()))
            {
              if(readData(clients[i].getSocket()))
              {
                clients[i].timer.reset();
              }
              else
              {
                removeClient(i);
              }
            }
          }
        }
      }

      void Server::checkTimeouts(float timePassed)
      {
        for(int i=0;i<clients.size();i++)
        {
          clients[i].timer.update(timePassed);
          if(clients[i].timer.getTime() > 5.0)
          {
            removeClient(i);
          }
        }
      }

      void Server::update(float timePassed)
      {
        checkNewConnection();
        checkIncomingData();
        checkTimeouts(timePassed);
      }

      bool Server::readData(const TCPsocket& clientSocket)
      {
        char data[1400];
        int size = SDLNet_TCP_Recv(clientSocket, data, 1400);
        if(size <= 0)
        {
          std::cout << "RECEIVING DATA FAILED: " << size << std::endl;
          return false;
        }
        std::cout << data << std::endl;

        while(data[strlen(data) - 1] != '\n')
        {
          size = SDLNet_TCP_Recv(clientSocket, data, 1400);
          if(size <= 0)
          {
            std::cout << "RECEIVING DATA FAILED: " << size << std::endl;
            return false;
          }
          std::cout << data << std::endl;
        }
        return true;
      }
    }
  }
}

what i get in the server terminal when i create the Client object and delete right after(the array in code is bigger i just chose the interesting part of the output):

 0.1519227 -0.002553641 -0.3744464 0.1526676 0 -0.06999236 0.1529192 0.002553701 -0.06999236 0.1526677 0.005009293 -0.06999236 0.1519228 0.007272362 -0.06999236 0.1507131 0.009255945 -0.06999236 0.149085
2 0.01088386 -0.06999236 0.1471016 0.01209348 -0.06999236 0.1448385 0.01283836 -0.06999236 0.142383 0.01308989 -0.06999236 0.1398292 0.01283836 -0.06999236 0.1372755 0.01209348 -0.06999236 0.13482 0.01088386 -0.06999236 0.1325569 0.009255945 -0.06999236 0.1305733 0.007272362 -0.06999236 0.1289454 0.005009293 -0.06999236 0.1277357 0.002553701 -0.06999236 0.1269909 0 -0.06999236 0.1267393 -0.002553701 -0.06999236 0.1269909 -0.005009293 -0.06999236 0.1277357 -0.007272362 -0.06999236 0.1289454 -0.009255945 -0.06999236 0.1305733 -0.01088386 -0.06999236 0.1325569 -0.01209348 -0.06999236 0.13482 -0.01283836 -0.06999236 0.1372755 -0.01308989 -0.06999236 0.1398293 -0.01283836 -0.06999236 0.142383 -0.01209348 -0.06999236 0.1448386 -0.01088386 -0.06999236 0.1471017
.1269907 -0.005009293 -0.3744465 0.1277357 -0.007272362 -0.3744464 0.1289453 -0.009255945 -0.3744465 0.1305732 -0.01088386 -0.3744464 0.1325568 -0.01209348 -0.3744465 0.1348199 -0.01283836 -0.3744465 0.1372755 -0.01308989 -0.3744465 0.1398292 -0.01283836 -0.3744464 0.1423829 -0.01209348 -0.3744464 0.1448385 -0.01088386 -0.3744465 0.1471015 -0.009255945 -0.3744464 0.1490852 -0.007272303 -0.3744464 0.150713 -0.005009233 -0.3744464 0.1519227 -0.002553641 -0.3744464 0.1526676 0 -0.06999236 0.1529192 0.002553701 -0.06999236 0.1526677 0.005009293 -0.06999236 0.1519228 0.007272362 -0.06999236 0.1507131 0.009255945 -0.06999236 0.149085
Client disconecting! <- this message is received in the number array!
236 0.1471016 0.01209348 -0.06999236 0.1448385 0.01283836 -0.06999236 0.142383 0.01308989 -0.06999236 0.1398292 0.01283836 -0.06999236 0.1372755 0.01209348 -0.06999236 0.13482 0.01088386 -0.06999236 0.1325569 0.009255945 -0.06999236 0.1305733 0.007272362 -0.06999236 0.1289454 0.005009293 -0.06999236 0.1277357 0.002553701 -0.06999236 0.1269909 0 -0.06999236 0.1267393 -0.002553701 -0.06999236 0.1269909 -0.005009293 -0.06999236 0.1277357 -0.007272362 -0.06999236 0.1289454 -0.009255945 -0.06999236 0.1305733 -0.01088386 -0.06999236 0.1325569 -0.01209348 -0.06999236 0.13482 -0.01283836 -0.06999236 0.1372755 -0.01308989 -0.06999236 0.1398293 -0.01283836 -0.06999236 0.142383 -0.01209348 -0.06999236 0.1448386 -0.01088386 -0.06999236 0.1471017
.1269907 -0.005009293 -0.3744465 0.1277357 -0.007272362 -0.3744464 0.1289453 -0.009255945 -0.3744465 0.1305732 -0.01088386 -0.3744464 0.1325568 -0.01209348 -0.3744465 0.1348199 -0.01283836 -0.3744465 0.1372755 -0.01308989 -0.3744465 0.1398292 -0.01283836 -0.3744464 0.1423829 -0.01209348 -0.3744464 0.1448385 -0.01088386 -0.3744465 0.1471015 -0.009255945 -0.3744464 0.1490852 -0.007272303 -0.3744464 0.150713 -0.005009233 -0.3744464 0.1519227 -0.002553641 -0.3744464 0.1526676 0 -0.06999236 0.1529192 0.002553701 -0.06999236 0.1526677 0.005009293 -0.06999236 0.1519228 0.007272362 -0.06999236 0.1507131 0.009255945 -0.06999236 0.149085
  • 2
    Why/how is the application layer intermixing messages? TCP/IP just transports your application's messages. You need to look into why your application is trying to send another message when a message (terrain data) is currently in progress. – Richard Critten Nov 27 '19 at 22:56
  • Well i do not know, but the data is sent when i call the Client.sendData() method which send all of the data, i use this function twice so i would expect that it sends the number array and then the "disconect" message. But the number array beeing big i think it is sent in several pieces. And the Server.readData() methode just prints the received data. Sould i wait for a server response after sending the terrain data? – Le Grand Spectacle Nov 27 '19 at 23:05
  • 2
    TCP doesn't send messages, it sends streams of data that it segments to fit the MSS, and reassembles the segments into a stream on the other end. It will simply mash all your messages together, and it has no idea about any message boundary, so it may deliver partial messages to the destination application at any given time, but all data will eventually be sent to the application in order. There are transport protocols designed for messages (UDP, SCTP, etc.), but not TCP. – Ron Maupin Nov 28 '19 at 00:17
  • 1
    You are ignoring the count returned by the read function/method. You can't assume that every read fills the buffer, or doesn't reach end of stream, or doesn't fail. – user207421 Nov 28 '19 at 04:29

1 Answers1

0

The problem was comming from the read function. I thought that if i gave it a NULL char array it would fill it up with the incoming data and then stop. However it fills in the char array using the incoming data and then fills up the remaining space of the char array with the prévious data received. (if the incoming data was shorter then the previous). As pointed out user207421 using the size of received data is needed. The fix was actualy simple, the read function returns the size of the received data and 0 or less if there was a connection error.

The fix:

(Although this code will print the data correcly in order packet seperation might still be needed for data processing)

bool Server::readStream(char* data, int* size, const TCPsocket& clientSocket)
      {
        *size = SDLNet_TCP_Recv(clientSocket, data, 1400);
        return *size > 0;
      }

      bool Server::addStream(Pointer<char>* pointer, const TCPsocket& clientSocket)
      {
        std::cout << "adding stream" << std::endl;
        char stream[1400];
        int streamSize;
        if(!readStream(stream, &streamSize, clientSocket))
        {
          return false;
        }

        int currentSize = 0;
        if(pointer->value() != NULL)
        {
          currentSize = strlen(pointer->value());
        }

        std::cout << "creating ptr: " << currentSize << ":" << streamSize << std::endl;
        Pointer<char> newPointer(new char[currentSize + streamSize + 1]);
        std::cout << "copying old values" << std::endl;
        for(int i=0;i<currentSize;i++)
        {
          *(newPointer.value() + i) = *(pointer->value() + i);
        }

        std::cout << "copying new values" << std::endl;
        for(int i=0;i<streamSize;i++)
        {
          *(newPointer.value() + currentSize + i) = *(stream + i);
        }
        std::cout << "adding end point" << std::endl;
        *(newPointer.value() + currentSize + streamSize) = '\0';
        *pointer = newPointer;
        std::cout << "done stream" << std::endl;
        return true;
      }

      bool Server::readData(const TCPsocket& clientSocket)
      {

        std::cout << "START DATA READING" << std::endl;
        std::cout << "-------------------------------------------" << std::endl;
        Pointer<char> pointer;
        if(!addStream(&pointer, clientSocket))
        {
          std::cout << "RECEIVING DATA FAILED" << std::endl;
          return false;
        }

        while(*(pointer.value() + strlen(pointer.value()) - 1) != '\n')
        {
          if(!addStream(&pointer, clientSocket))
          {
            std::cout << "RECEIVING DATA FAILED" << std::endl;
            return false;
          }
        }
        std::cout << "data received: " << pointer.value() << std::endl;
        std::cout << "-------------------------------------------" << std::endl;
        return true;
      }
  • 1
    'However ... and then fills up the remainimg space of the char array with the previous data received': this is fantasy. It doesn't do any such thing. It doesn't disturb the previous content beyond the count of data actually received, and returns the count, which you are ignoring. Solution: don't. Don't use the byte array beyond the count returned. You don't need the extra copy step. – user207421 Nov 28 '19 at 20:00
  • It is not fantasy i always define a new char (in the code: char stream[1400];) but when i call readStream(char* data, int* size, const TCPsocket& clientSocket) it fills up the char stream with previous data, this is a fact not fantasy, maybee the data in SDLNet_TCP_Recv(clientSocket, data, 1400); is not flushed but this was not what i was refering to. Why do you say i do not need a copy steep? i wish to read a packet which i seperate by '\n'. – Le Grand Spectacle Nov 28 '19 at 21:12
  • You are using the wrong tool for the job. Do not try to drive a nail with a screwdriver. TCP is not made for messages. There are message protocols designed to do what you want. SCTP is message oriented, and it has many TCP features. There was, at one time, a Transactional TCP, but it never took off, and it has been deprecated. – Ron Maupin Nov 29 '19 at 03:50
  • So using TCP for sending information such as the player position/speed/... isn't good? Here i used a big array of numbers but it was just a test. I was just trying to do the same as in this tutorial: https://www.youtube.com/watch?v=iJfC4-yNnzY – Le Grand Spectacle Nov 29 '19 at 16:15