TCP 缓冲区中的多条消息

Multiple messages in a tcp buffer

本文关键字:消息 缓冲区 TCP      更新时间:2023-10-16

我实际上是在编写一个简单的服务器,它是使用 tcp 协议的 c++ 客户端。由于这是要集成到多人游戏中,因此每个客户端都必须非常快速地发送数据。

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

我尝试了各种方法,例如推迟 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 Mapn" + 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();
}`

来扩展Barmar的评论

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

您可能send1 个 100 字节的块并接收 100 个 1 字节recv秒,或者您可能会收到 20 个 5 字节recv

您可以发送 100 个 1 字节块并接收 4 个 25 字节的消息

您必须自己处理消息边界。要么有一个哨兵值来标记开始和结束,要么在前面加上一个本身就是固定大小的长度(所以你知道你已经阅读了整个长度(。然后循环recv直到您收到整条消息