【问题标题】:How to send large chunks of data using a tcp connection如何使用 tcp 连接发送大块数据
【发布时间】:2019-11-27 22:46:00
【问题描述】:

我目前正在尝试将多人游戏系统添加到我的游戏引擎中。为此,我决定使用 TCP 方法。由于我已经在使用 SDL2 库,因此我决定使用 SDL2-net 库进行网络连接。

我希望向服务器发送数据,反之亦然。发送少量数据不是问题,但是当我想发送大量数据时,例如一块地形,客户端将接收器可能会在地形数据之间混合一些其他数据,这当然不是通缉。我不是网络专家,所以可能有一些我不知道的基本概念,如果有解决这个“问题”的方法,我会非常高兴。

感谢您的帮助!

在以下代码中,客户端向服务器发送大量数字(在客户端构造函数中),并在客户端对象被销毁时(在客户端析构函数中)发送一条消息,但这两条消息混淆了。

客户端.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

客户端.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;
          }
        }
      }
    }
  }
}

服务器.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

服务器.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;
      }
    }
  }
}

当我创建 Client 对象并在之后立即删除时,我在服务器终端中得到了什么(代码中的数组更大,我只是选择了输出中有趣的部分):

 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

【问题讨论】:

  • 应用层为什么/如何混合消息? TCP/IP 只是传输应用程序的消息。您需要了解为什么您的应用程序在消息(地形数据)当前正在进行时尝试发送另一条消息。
  • 嗯,我不知道,但是当我调用发送所有数据的 Client.sendData() 方法时发送数据,我使用这个函数两次,所以我希望它发送数字数组,然后是“断开连接”消息。但是我认为数字数组很大,我认为它是分几块发送的。 Server.readData() 方法只是打印接收到的数据。发送地形数据后是否需要等待服务器响应?
  • TCP 不发送消息,它发送分段以适应 MSS 的数据流,并在另一端将分段重新组合成一个流。它将简单地将所有消息混合在一起,并且不知道任何消息边界,因此它可能会在任何给定时间将部分消息传递到目标应用程序,但所有数据最终都会按顺序发送到应用程序。有为消息设计的传输协议(UDP、SCTP 等),但不是 TCP。
  • 您忽略了读取函数/方法返回的计数。你不能假设每次读取都会填满缓冲区,或者没有到达流的末尾,或者没有失败。

标签: c++ networking tcp sdl-net


【解决方案1】:

问题来自读取功能。我想如果我给它一个 NULL char 数组,它会用传入的数据填充它,然后停止。然而,它使用传入的数据填充 char 数组,然后用接收到的先前数据填充 char 数组的剩余空间。 (如果传入的数据比前一个短)。正如所指出的,user207421 需要使用接收数据的大小。修复其实很简单,读取函数返回接收数据的大小,如果连接错误,返回 0 或更小。

修复:

(尽管此代码将正确打印数据,以便数据处理仍需要数据包分离)

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;
      }

【讨论】:

  • '然而......然后用之前收到的数据填充char数组的remainimg空间':这是幻想。它不会做任何这样的事情。它不会干扰超出实际接收的数据计数的先前内容,并返回您忽略的计数。解决方法:不要。不要使用超出返回计数的字节数组。您不需要额外的复制步骤。
  • 这不是幻想我总是定义一个新的字符(在代码中:char stream[1400];)但是当我调用 readStream(char* data, int* size, const TCPsocket& clientSocket) 它填充用以前的数据向上传输字符流,这不是幻想,也许是 SDLNet_TCP_Recv(clientSocket, data, 1400) 中的数据;没有冲洗,但这不是我所指的。为什么你说我不需要副本陡峭?我想读取一个我用'\n'分隔的数据包。
  • 您使用了错误的工具来完成这项工作。不要试图用螺丝刀打钉子。 TCP 不是为消息而生的。有一些消息协议旨在满足您的需求。 SCTP 是面向消息的,它有很多 TCP 特性。曾经有一个事务性 TCP,但它从未流行起来,并且已被弃用。
  • 那么使用TCP发送玩家位置/速度/...等信息不好吗?在这里,我使用了一大堆数字,但这只是一个测试。我只是想在本教程中做同样的事情:youtube.com/watch?v=iJfC4-yNnzY
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-06-27
  • 2014-07-01
  • 1970-01-01
  • 1970-01-01
  • 2011-02-18
  • 1970-01-01
相关资源
最近更新 更多