为何客户端首次send后,服务器recv返回0判定连接已关闭?
TCP环回Socket连接异常问题
我正在构建一个简单的TCP环回Socket示例:客户端连接服务器后,从标准输入读取数据并发送至服务器。但遇到异常:服务器首次调用recv能正常接收数据,循环中的第二次recv却始终返回0(程序判定为客户端关闭连接),但客户端仍在循环提示输入并调用send,无法理解该现象。
代码文件
client.cpp
#include <sys/socket.h> #include <sys/types.h> #include <netdb.h> #include <unistd.h> #include <iostream> int main(int argc, char* argv[]) { if (argc != 2) { std::cerr << "Usage: " << argv[0] << " <port number>" << std::endl; return 1; } addrinfo hints{.ai_protocol = IPPROTO_TCP, .ai_socktype = SOCK_STREAM, .ai_family = PF_INET}; addrinfo* result; int getaddrresult = getaddrinfo("localhost", argv[1], &hints, &result); if (getaddrresult != 0) { std::cerr << "getaddrinfo failed: " << gai_strerror(getaddrresult) << std::endl; return 1; } if (result == nullptr) { std::cerr << "getaddrinfo returned nullptr" << std::endl; return 1; } int sockFd = socket(result->ai_family, result->ai_socktype, result->ai_protocol); if (sockFd == -1) { std::cerr << "socket failed" << std::endl; return 1; } int connectResult = connect(sockFd, result->ai_addr, result->ai_addrlen); if (connectResult == -1) { std::cerr << "connect failed" << std::endl; return 1; } std::cout << "Connected to server" << std::endl; while (true) { std::string message; std::cout << "Please enter message: "; std::getline(std::cin, message); if (message == "exit") { break; } int sendResult = send(sockFd, message.c_str(), message.size(), 0); if (sendResult == -1) { perror("send failed"); return 1; } std::cout << "Sent " << sendResult << " bytes" << std::endl; } std::cout << "Closing connection" << std::endl; close(sockFd); freeaddrinfo(result); return 0; }
server.cpp
#include <netdb.h> #include <sys/socket.h> #include <sys/types.h> #include <unistd.h> #include <iostream> #include <stdio.h> #include <vector> using namespace std; pair<string, string> getHostAndPort(const sockaddr *addr) { char host[NI_MAXHOST]; char port[NI_MAXSERV]; int getnameinforesult = getnameinfo(addr, sizeof(addr->sa_len), host, NI_MAXHOST, port, NI_MAXSERV, 0); if (getnameinforesult != 0) { return {"<unknown>", "<unknown>"}; } return {host, port}; } int main() { addrinfo hints{.ai_protocol = IPPROTO_TCP, .ai_socktype = SOCK_STREAM, .ai_family = PF_INET}; addrinfo *result; const char *port = "50069"; int getaddrresult = getaddrinfo("localhost", port, &hints, &result); if (getaddrresult != 0) { cout << "getaddrinfo failed: " << gai_strerror(getaddrresult) << endl; return -1; } if (result == nullptr) { cout << "getaddrinfo returned nullptr" << endl; return -1; } const auto [host, portString] = getHostAndPort(result->ai_addr); int sockFd = socket(result->ai_family, result->ai_socktype, result->ai_protocol); if (sockFd == -1) { perror("socket failed"); return -1; } if (bind(sockFd, result->ai_addr, result->ai_addrlen) == -1) { perror("bind failed"); return -1; } cout << "bound to port " << portString << endl; if (listen(sockFd, 10) == -1) { perror("listen failed"); return -1; } cout << "listening for incoming connections on " << host << ":" << portString << endl; sockaddr_storage clientAddr; socklen_t clientAddrLen = sizeof(clientAddr); while (true) { int clientFd = accept(sockFd, (sockaddr *)&clientAddr, &clientAddrLen); if (clientFd == -1) { perror("accept failed"); return -1; } const auto [clientHost, clientPort] = getHostAndPort((sockaddr *)&clientAddr); cout << "accepted connection from " << clientHost << ":" << clientPort << endl; std::vector<char> buffer(1024); int bytesReceived; while (true) { bytesReceived = recv(clientFd, buffer.data(), buffer.size(), 0); if (bytesReceived == -1) { perror("recv failed"); return -1; } else if (bytesReceived > 0) { std::string receivedData(buffer.data(), bytesReceived); cout << "received " << bytesReceived << " bytes: <" << receivedData << ">" << endl; } else if (bytesReceived == 0) { cout << "client closed connection" << endl; break; } buffer.clear(); } close(clientFd); cout << "closed connection to " << clientHost << ":" << clientPort << endl; } close(sockFd); freeaddrinfo(result); return 0; }
CMakeLists.txt
cmake_minimum_required(VERSION 3.10) set(CMAKE_CXX_STANDARD 17) project(server_client) add_compile_options(-Wall -Wextra -Wpedantic -Werror) add_executable(server src/server.cpp) add_executable(client src/client.cpp)
预期行为
循环中第二次recv调用会阻塞,直到再次收到客户端发送的数据。
实际输出
服务器输出:
./server bound to port 50069 listening for incoming connections on localhost:50069 accepted connection from localhost:65036 received 15 bytes: <fshdjkfhkdjsfsd> client closed connection closed connection to localhost:65036 ^C
客户端输出:
./client 50069 Connected to server Please enter message: fshdjkfhkdjsfsd Sent 15 bytes Please enter message: fhdjskfhksd Sent 11 bytes Please enter message: ^C
问题原因与修复
问题出在服务器代码的recv循环中:每次调用recv后执行了buffer.clear()。std::vector::clear()会清空容器内的元素,将其大小(size())置为0,但不会释放内存。这导致下一次调用recv(clientFd, buffer.data(), buffer.size(), 0)时,第三个参数(接收缓冲区大小)为0。根据recv的行为,当请求接收0字节时,它会直接返回0,被程序误判为客户端主动关闭了连接。
修复方法
删除循环中的buffer.clear()语句即可。因为缓冲区已经初始化为1024字节的大小,后续recv会直接覆盖缓冲区中的旧数据,不影响新数据的接收。修改后的服务器循环代码如下:
std::vector<char> buffer(1024); int bytesReceived; while (true) { bytesReceived = recv(clientFd, buffer.data(), buffer.size(), 0); if (bytesReceived == -1) { perror("recv failed"); return -1; } else if (bytesReceived > 0) { std::string receivedData(buffer.data(), bytesReceived); cout << "received " << bytesReceived << " bytes: <" << receivedData << ">" << endl; } else if (bytesReceived == 0) { cout << "client closed connection" << endl; break; } // 移除buffer.clear() }
如果确实需要清空缓冲区内容(比如避免残留数据干扰),可以用memset(buffer.data(), 0, buffer.size())代替buffer.clear(),这样既能清空数据,又能保持缓冲区的大小不变。
内容的提问来源于stack exchange,提问作者Picard
相关产品推荐
相关产品推荐

