You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

为何客户端首次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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.26 09:21:01