2017-08-15 105 views
-1

我实际上是编程一个简单的服务器,它是使用tcp协议的C++中的客户端。由于这将被集成到多人游戏中,所以每个客户端都必须非常快速地发送数据。tcp缓冲区中的多条消息

问题:服务器的缓冲区有时会在其中获取多条消息。

我尝试了各种东西,如推迟nagle的算法,但我没有设法解决这个问题。这里是服务器的代码:

#ifdef __linux__ 
    #include <sys/types.h> 
    #include <sys/socket.h> 
    #include <netinet/in.h> 
    #include <netinet/ip.h> 
    #include <netinet/tcp.h> 
    #include <unistd.h> 
    #include <stdlib.h> 
    #include <string.h> 
    #include <fcntl.h> 
    #include <errno.h> 
    #include <arpa/inet.h> 
    #define SOCKET int 
    #define SOCKADDR_IN struct sockaddr_in 
#endif 
#ifdef _WIN32 
    #include <winsock2.h> 
#endif 
#include <cstdio> 
#include <iostream> 
#include <thread> 
#include <vector> 
#include <string> 
#include "server.h" 
#include "../../Logger/logger.h" 
#include "../../AltisCraft.fr/Map/map.h" 
#include "../../StringPlus/string_plus.h" 
#include "../../AltisCraft.fr/Map/User/User.h" 
void connectEvent(), receive(), sendAllUsers(string), closeConnectio(),manageMsg(); 
vector<SOCKET> clients; 
vector<thread> clientsThreads; 
vector<string> msg; 
SOCKET socketId, newSocketId; 
SOCKADDR_IN source; 
thread connection; 
char buffer[65535] = {0}; 
int position; 

// TODO: crypt every data sendLog/receive 
// TODO: whitelist ip serv 
// TODO: Auth system 
// TODO: timer with packet ? (double receive...) 

int sendLog(SOCKET s, const char* c, int i0, int i1) 
{ 
    log("Send:"); 
    log(c); 
    send(s, c, i0, i1); 
} 

void initializeNetwork() 
{ 
    #ifdef _WIN32 
     WSADATA initWin32; 
     WSAStartup(MAKEWORD(2, 2),&initWin32); 
    #endif 
    socketId = socket(AF_INET, SOCK_STREAM, 0); 
    source.sin_family = AF_INET; 
    source.sin_addr.s_addr = INADDR_ANY; 
    source.sin_port = htons(33333); 
    bind(socketId, (struct sockaddr*)&source, sizeof(source)); 
    connection = thread(&connectEvent); 
    connection.join(); 
    closeConnection(); 
} 

void connectEvent() 
{ 
    int error; 
    while(1) 
    { 
     error = 99; 
     while(error != 0) 
     { 
      error = listen(socketId, 1); 
     } 
     #ifdef _WIN32 
      int tempo = sizeof(source); 
      newSocketId = accept(socketId, (struct sockaddr*)&source, &tempo); 
      clients.push_back(newSocketId); 
     #endif 
     #ifdef __linux__ 
      socklen_t tempo; 
      newSocketId = accept(socketId, (struct sockaddr *)&source, &tempo); 
      clients.push_back(newSocketId); 
     #endif 
     clientsThreads.push_back(thread(&receive)); 
    } 
} 

void receive() 
{ 
    int val = 1; 
    position = clients.size() - 1; 
    bool connected = 1; 
    while(connected) 
    { 
     buffer[65535] = {0}; 
     if(recv(clients[position], buffer, 1515, 0) > 0) 
     { 
      string msg = buffer; 
      bool isEmpty = false; 
      log(string(inet_ntoa(source.sin_addr)) + ": " + msg); 
      if(startsWith(msg, "Connect ")) 
       addUser(replace(msg, "Connect ", "")); 
      else if(msg == "MAJ Map") 
      { 
       log(elements); 
       string toSend = "MAJ Map\n" + elements; 
       sendLog(clients[position], toSend.c_str(), strlen(toSend.c_str()), 0); 
      } 
      else if(startsWith(msg, "MAJ User ")) /// optimize: don't sendLog pos to player who sendLog 
      { 
       msg = replace(msg, "MAJ User ", ""); 
       if(startsWith(msg, "Pos ")) 
       { 
        msg = replace(msg, "Pos ", ""); 
        vector<string> elements = split(msg, " "); 
        User user = *getUserByName(elements[0] + " " + elements[1]); 
        user.updateView(user.getView().updatePosition(Position(convertStrToDouble(elements[2]), convertStrToDouble(elements[3]), convertStrToDouble(elements[4])))); 
       } 
       else if(startsWith(msg, "ViewAngle ")) 
       { 
        msg = replace(msg, "ViewAngle ", ""); 
        vector<string> elements = split(msg, " "); 
        User user = *getUserByName(elements[0] + " " + elements[1]); 
        user.updateView(user.getView().updateViewAngle(ViewAngle(convertStrToDouble(elements[2]), convertStrToDouble(elements[3])))); 
       } 
      } 
      else 
       sendAllUsers(string(string(inet_ntoa(source.sin_addr)) + ": " + msg).c_str()); 
     } 
     else 
      connected = 0; 
    } 
    shutdown(clients[position], 2); 
    for(int i=0;i<msg.size();i++) 
     cout << msg[i] << endl; 
    #ifdef _WIN32 
     closesocket(clients[position]); 
    #endif 
    #ifdef __linux__ 
     close(clients[position]); 
    #endif 
    clients.erase(clients.begin() + position); 

} 


void sendAllUsersWithoutOne(string msg, string name) 
{ 
    for(int j = 0; j < (int)clients.size(); j++) 
    { 
     // only linux here (MSG_DONTWAIT) 
     #ifdef __linux__ 
     if(recv(clients[j], NULL, 1, MSG_PEEK | MSG_DONTWAIT) == 0) 
     { 
      clients.erase(clients.begin() + j); 
      continue; 
     } 
     #endif 
     sendLog(clients[j], msg.c_str(), strlen(msg.c_str()), 0); 
    } 
} 

void sendAllUsers(string msg) 
{ 
    for(int j = 0; j < (int)clients.size(); j++) 
    { 
     // only linux here (MSG_DONTWAIT) 
     #ifdef __linux__ 
     if(recv(clients[j], NULL, 1, MSG_PEEK | MSG_DONTWAIT) == 0) 
     { 
      clients.erase(clients.begin() + j); 
      continue; 
     } 
     #endif 
     sendLog(clients[j], msg.c_str(), strlen(msg.c_str()), 0); 
    } 
} 

void closeConnection() 
{ 
    for(int i = 0; i < (int)clients.size(); i++) 
    { 
     shutdown(clients[i], 2); 
     #ifdef _WIN32 
      closesocket(clients[i]); 
     #endif 
     #ifdef __linux__ 
      close(clients[i]); 
     #endif 
    } 
    #ifdef _WIN32 
     closesocket(socketId); 
     WSACleanup(); 
    #endif 
    #ifdef __linux__ 
     close(socketId); 
    #endif 
} 

void freeNetwork() 
{ 
    closeConnection(); 
}` 
+0

嗯......你忘了提问题了。 –

+0

@Error - 句法懊悔我把它放在粗体中。 –

+0

首先,如果您需要非常快速地发送/接收数据,您不应该使用TCP - 尝试使用UDP。如果缓冲区中有多条消息,则应该尝试从缓冲区读取,直到它为空 - 另一件事是,您还应该准备好缓冲区可能只包含消息的一部分,这种情况下您将无法读取直到收到其余的消息。您还应该考虑使用非阻塞套接字和/或微线程协程。 – olgierdh

回答

2

对Barmar的评论扩大

TCP是流协议,而不是一个消息协议。只保证发送n个字节,您将以相同的顺序接收n个字节。

你可能send 1块为100个字节和接收100 1字节recv S,或者您可能会收到20 5个字节recv小号

您可以发送1001个字节的块和接收4点25字节的信息

你必须自己处理消息边界。要么有一个标记值来标记开始和结束,或者预先确定一个本身是固定大小的长度(所以你知道你已经读了整个长度)。然后在recv上循环,直到收到全部消息