为啥在 TCP 服务器/客户端模型中,服务器会复制客户端发送的每个数据包并同时发送它?
Posted
技术标签:
【中文标题】为啥在 TCP 服务器/客户端模型中,服务器会复制客户端发送的每个数据包并同时发送它?【英文标题】:Why in a TCP server/client model, the server copies every packet the client sends and sends it as well?为什么在 TCP 服务器/客户端模型中,服务器会复制客户端发送的每个数据包并同时发送它? 【发布时间】:2021-10-23 19:11:36 【问题描述】:所以我想了解更多关于 TCP 协议是如何工作的并开始搞乱 Windows 套接字 (WinSocks2),我设法从 Microsoft documentation 编写了一个基本的服务器和客户端。
我希望在 Wireshark 中看到 3 次握手,但我做到了,但不是我想象的那样,它实际上是重复的(服务器将与客户端相同的数据包发送回客户端)。
所以我的问题是,这正常吗,还是我做错了什么?
这是我的服务器源代码:
// This should be placed before #include <Windows.h> if the windows include is used
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <cstdio>
#include <Windows.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
/// TEST CODE /// REMOVE AFTER TESTING WAS DONE ///
int main()
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0)
printf("WSAStartup failed: %d\n", iResult);
return 1;
struct addrinfo* result = NULL, * ptr = NULL, hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// Resolve the local address and port to be used by the server
iResult = getaddrinfo("0.0.0.0", "1337", &hints, &result);
if (iResult != 0)
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
SOCKET ListenSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ListenSocket == INVALID_SOCKET)
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR)
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
freeaddrinfo(result);
if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR)
printf("Listen failed with error: %ld\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
// Handling pending connections
puts("Waiting for a connection..");
SOCKET ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET)
printf("accept failed: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
#define DEFAULT_BUFLEN 512
char recvbuf[DEFAULT_BUFLEN];
int iSendResult;
int recvbuflen = DEFAULT_BUFLEN;
int size =0 ;
// Receive until the peer shuts down the connection
do
iResult = recv(ClientSocket, recvbuf, recvbuflen, 0);
if (iResult > 0)
printf("Bytes received: %d\n", iResult);
size += iResult;
// I thought this was the problem but after I removed it from the code it still was the same.
//
// Echo the buffer back to the sender
//iSendResult = send(ClientSocket, recvbuf, iResult, 0);
/*if (iSendResult == SOCKET_ERROR)
printf("send failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
*/
//printf("Bytes sent: %d\n", iSendResult);
else if (iResult == 0)
printf("Connection closing...\n");
else
printf("recv failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
while (iResult > 0);
recvbuf[size] = 0;
printf("Data received: %s\n", recvbuf);
// Stops responding to requests, still can accept tho
iResult = shutdown(ClientSocket, SD_SEND);
if (iResult == SOCKET_ERROR)
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ClientSocket);
WSACleanup();
return 1;
// Close socket, no receiving/sending
closesocket(ClientSocket);
WSACleanup();
这里是客户端:
// This should be placed before #include <Windows.h> if the windows include is used
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <cstdio>
#include <Windows.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
/// TEST CODE /// REMOVE AFTER TESTING WAS DONE ///
int main()
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0)
printf("WSAStartup failed: %d\n", iResult);
return 1;
struct addrinfo* result = NULL, * ptr = NULL, hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// Resolve the local address and port to be used by the server
iResult = getaddrinfo("109.xxx.xxx.xxx", "1337", &hints, &result);
if (iResult != 0)
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
SOCKET ConnectSocket = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
if (ConnectSocket == INVALID_SOCKET)
printf("Error at socket(): %ld\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
iResult = connect(ConnectSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR)
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
// Should really try the next address returned by getaddrinfo
// if the connect call failed
// But for this simple example we just free the resources
// returned by getaddrinfo and print an error message
freeaddrinfo(result);
if (ConnectSocket == INVALID_SOCKET)
printf("Unable to connect to server!\n");
WSACleanup();
return 1;
#define DEFAULT_BUFLEN 512
int recvbuflen = DEFAULT_BUFLEN;
const char* sendbuf = "this is a test";
char recvbuf[DEFAULT_BUFLEN];
// Send an initial buffer
iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR)
printf("send failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
printf("Bytes Sent: %ld\n", iResult);
// shutdown the connection for sending since no more data will be sent
// the client can still use the ConnectSocket for receiving data
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR)
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
// Receive data until the server closes the connection
do
iResult = recv(ConnectSocket, recvbuf, recvbuflen, 0);
if (iResult > 0)
printf("Bytes received: %d\n", iResult);
else if (iResult == 0)
printf("Connection closed\n");
else
printf("recv failed: %d\n", WSAGetLastError());
while (iResult > 0);
closesocket(ConnectSocket);
WSACleanup();
这里是从Wireshark抓到的包,其中以109.xxx.xxx.xxx
开头的IP地址就是服务器:
【问题讨论】:
请在问题中包含您的代码。 我把它贴在了pastebin上,否则问题会变得更长,不过我会这样做 在 imgur 上读取数据包捕获很烦人。如果您复制几个有意义的数据包并突出显示您认为您看到的内容不正确的地方,您将获得更好的响应。例如:在这个数据包中,我们看到 [blah],我期待 [foo]。 在 Windows 中使用 WSAConnect 更容易。 【参考方案1】:不,重复的数据包不正常。但这与您的代码无关。这更有可能是您的网络设置的问题,或者只是您捕获数据包的方式的副作用。
与重复数据包无关,显示的代码还有其他问题。
在您的服务器中:
您的if (ListenSocket == INVALID_SOCKET)
块缺少return
,因此如果socket()
失败,那么您仍然使用无效的SOCKET
调用bind()
并取消引用无效的result
指针。
一旦到达recv()
循环,就永远不会在调用WSACleanup()
之前关闭ListenSocket
。
recvbuf
中的 printf()
需要在 iResult > 0
时位于循环内。如果你要空终止recvbuf
,那么你需要在调用recv()
时使用recvbuflen-1
,否则你会丢失最后收到的字节。否则,将iResult
作为参数传递给printf()
,这样您就根本不需要空终止recvbuf
。
在您的客户中:
在调用getaddrinfo()
时不要使用AI_PASSIVE
标志。该标志用于侦听服务器,而不是出站客户端。
您的if (ConnectSocket == INVALID_SOCKET)
块缺少return
,因此如果socket()
失败,那么您仍然使用无效的SOCKET
调用connect()
并取消引用无效的result
指针。
试试这个代码:
服务器:
// This should be placed before #include <Windows.h> if the windows include is used
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <cstdio>
#include <Windows.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
/// TEST CODE /// REMOVE AFTER TESTING WAS DONE ///
int main()
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0)
printf("WSAStartup failed: %d\n", iResult);
return 1;
struct addrinfo* result = NULL, hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
hints.ai_flags = AI_PASSIVE;
// Resolve the local address and port to be used by the server
iResult = getaddrinfo(NULL, "1337", &hints, &result);
if (iResult != 0)
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
SOCKET ListenSocket = socket(result->ai_family, result->ai_socktype, result->ai_protocol);
if (ListenSocket == INVALID_SOCKET)
printf("Error at socket(): %d\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
iResult = bind(ListenSocket, result->ai_addr, (int)result->ai_addrlen);
if (iResult == SOCKET_ERROR)
printf("bind failed with error: %d\n", WSAGetLastError());
freeaddrinfo(result);
closesocket(ListenSocket);
WSACleanup();
return 1;
freeaddrinfo(result);
if (listen(ListenSocket, SOMAXCONN) == SOCKET_ERROR)
printf("Listen failed with error: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
// Handling pending connections
puts("Waiting for a connection..");
SOCKET ClientSocket = accept(ListenSocket, NULL, NULL);
if (ClientSocket == INVALID_SOCKET)
printf("accept failed: %d\n", WSAGetLastError());
closesocket(ListenSocket);
WSACleanup();
return 1;
#define DEFAULT_BUFLEN 512
char recvbuf[DEFAULT_BUFLEN];
int iSendResult;
// Receive until the peer shuts down the connection
do
iResult = recv(ClientSocket, recvbuf, sizeof(recvbuf), 0);
if (iResult > 0)
printf("Bytes received: %d\n", iResult);
printf("Data received: %.*s\n", iResult, recvbuf);
// Echo the buffer back to the sender
/*iSendResult = send(ClientSocket, recvbuf, iResult, 0);
if (iSendResult == SOCKET_ERROR)
printf("send failed: %d\n", WSAGetLastError());
break;
printf("Bytes sent: %d\n", iSendResult);*/
else if (iResult == 0)
printf("Connection closing...\n");
else
printf("recv failed: %d\n", WSAGetLastError());
while (iResult > 0);
// Close socket, no receiving/sending
shutdown(ClientSocket, SD_BOTH);
closesocket(ClientSocket);
WSACleanup();
return 0;
客户:
// This should be placed before #include <Windows.h> if the windows include is used
#ifndef WIN32_LEAN_AND_MEAN
#define WIN32_LEAN_AND_MEAN
#endif
#include <cstdio>
#include <Windows.h>
#include <WinSock2.h>
#include <WS2tcpip.h>
/// TEST CODE /// REMOVE AFTER TESTING WAS DONE ///
int main()
WSADATA wsaData;
int iResult;
iResult = WSAStartup(MAKEWORD(2, 2), &wsaData);
if (iResult != 0)
printf("WSAStartup failed: %d\n", iResult);
return 1;
struct addrinfo* result = NULL, hints;
ZeroMemory(&hints, sizeof(hints));
hints.ai_family = AF_INET;
hints.ai_socktype = SOCK_STREAM;
hints.ai_protocol = IPPROTO_TCP;
// Resolve the local address and port to be used by the server
iResult = getaddrinfo("109.xxx.xxx.xxx", "1337", &hints, &result);
if (iResult != 0)
printf("getaddrinfo failed: %d\n", iResult);
WSACleanup();
return 1;
SOCKET ConnectSocket = INVALID_SOCKET;
for(struct addrinfo* ptr = result; ptr != NULL; ptr = ptr->ai_next)
ConnectSocket = socket(ptr->ai_family, ptr->ai_socktype, ptr->ai_protocol);
if (ConnectSocket == INVALID_SOCKET)
printf("Error at socket(): %d\n", WSAGetLastError());
freeaddrinfo(result);
WSACleanup();
return 1;
iResult = connect(ConnectSocket, result->ai_addr, (int)result->ai_addrlen);
freeaddrinfo(result);
if (iResult != SOCKET_ERROR)
break;
closesocket(ConnectSocket);
ConnectSocket = INVALID_SOCKET;
if (ConnectSocket == INVALID_SOCKET)
printf("Unable to connect to server!\n");
WSACleanup();
return 1;
#define DEFAULT_BUFLEN 512
const char* sendbuf = "this is a test";
char recvbuf[DEFAULT_BUFLEN];
// Send an initial buffer
iResult = send(ConnectSocket, sendbuf, (int)strlen(sendbuf), 0);
if (iResult == SOCKET_ERROR)
printf("send failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
printf("Bytes Sent: %ld\n", iResult);
// shutdown the connection for sending since no more data will be sent
// the client can still use the ConnectSocket for receiving data
iResult = shutdown(ConnectSocket, SD_SEND);
if (iResult == SOCKET_ERROR)
printf("shutdown failed: %d\n", WSAGetLastError());
closesocket(ConnectSocket);
WSACleanup();
return 1;
// Receive data until the server closes the connection
do
iResult = recv(ConnectSocket, recvbuf, sizeof(recvbuf), 0);
if (iResult > 0)
printf("Bytes received: %d\n", iResult);
else if (iResult == 0)
printf("Connection closed\n");
else
printf("recv failed: %d\n", WSAGetLastError());
while (iResult > 0);
shutdown(ConnectSocket, SD_RECEIVE);
closesocket(ConnectSocket);
WSACleanup();
return 0;
【讨论】:
以上是关于为啥在 TCP 服务器/客户端模型中,服务器会复制客户端发送的每个数据包并同时发送它?的主要内容,如果未能解决你的问题,请参考以下文章
自己用C语言构造数据包,实现TCP三次握手过程,为啥中间会产生一个RST信号?
为啥在 TCP 中使用 bind()?为啥它只用在服务器端而不用在客户端?