c++ - 成功接收后清空缓冲区

标签 c++ sockets tcp winsock recv

我正在 Windows 上用 C++ 编写服务器,但使用 recv() 时遇到了奇怪的行为。

我写了这个函数:

bool readN(SOCKET s, int size, char* buffer){
    fd_set readset;
    struct timeval tv;
    int left, res;
    FD_ZERO(&readset);
    FD_SET(s, &readset);
    left = size;
    std::cout << "-----called readN to read " << size << " byte" << std::endl;
    while (left > 0) {
        tv.tv_sec = MAXWAIT;
        tv.tv_usec = 0;
        res = select(0, &readset, NULL, NULL, &tv);
        if (res > 0) {
            res = recv(s, buffer, left, 0);
            if (res == 0) {//connection closed by client
                return false;
            }

            left -= res;
            std::cout << "\treceived " << res << " left " << left << std::endl;
            if (left != 0) {
                buffer += res;
            }

        }
        else if (res == 0) { //timer expired
            return false;
        }
        else { //socket error
            return false;
        }
    }
    std::cout << "\t" << buffer << std::endl;
    return true;
}

我这样调用它:

std::unique_ptr<char[]> buffer = std::make_unique<char[]>(size_);
if (readN(sck, size_, buffer.get())) {
    std::cout << "----read message----" << std::endl;
    std::cout <<"\t"<< buffer.get()<< std::endl;
}

问题在于,即使recv()返回正数,缓冲区仍然是空的。我错过了什么?

最佳答案

我在您的代码中发现了一些问题。

  1. 您没有重置 readset每次调用 select() 时都有变量。 select()修改变量。对于单套接字情况,这还不错,但您应该养成每次重置变量的习惯。

  2. 您没有检查 recv() 返回的错误。您认为任何非正常断开连接都是成功的,但这并不总是正确的。

  3. readN() 末尾返回之前true ,您将输出 buffer参数std::cout ,但是buffer将指向数据的END,而不是BEGINNING,因为它是由读取循环推进的。这可能是您对“空缓冲区”感到困惑的原因。 readN()它本身甚至根本不应该输出数据,因为您在 readN() 之后执行此操作退出,否则您最终会得到冗余的输出消息。

  4. 如果 readN()返回 true,您正在通过最终的 bufferstd::cout使用operator<<期望以 null 结尾 char字符串,但不保证您的缓冲区以 null 结尾。

尝试更多类似这样的事情:

bool readN(SOCKET s, int size, char* buffer){
    fd_set readset;
    struct timeval tv;
    int res;
    std::cout << "-----called readN to read " << size << " byte(s)" << std::endl;
    while (size > 0) {
        FD_ZERO(&readset);
        FD_SET(s, &readset);
        tv.tv_sec = MAXWAIT;
        tv.tv_usec = 0;

        res = select(0, &readset, NULL, NULL, &tv);
        if (res > 0) {
            res = recv(s, buffer, size, 0);
            if (res == SOCKET_ERROR) {
                res = WSAGetLastError();
                if (res == WSAEWOULDBLOCK) {
                    continue; //call select() again
                }
                return false; //socket error
            }

            if (res == 0) {
                return false;  //connection closed by client
            }

            buffer += res;
            size -= res;

            std::cout << "\treceived " << res << " byte(s), " << size << " left" << std::endl;
        }

        /*
        else if (res == 0) {
            return false; //timer expired
        }
        else {
            return false; //socket error
        }
        */

        else {
            return false; //timer expired or socket error
        }
    }

    return true;
}

std::unique_ptr<char[]> buffer = std::make_unique<char[]>(size_);
if (readN(sck, size_, buffer.get())) {
    std::cout << "----read message----" << std::endl;
    std::cout << "\t";
    std::cout.write(buffer.get(), size_);
    std::cout << std::endl;
}

话虽如此,我建议采用 readN() 的替代实现,具体取决于您使用的是阻塞套接字还是非阻塞套接字。

如果阻止,请使用 setsockopt(SO_RCVTIMEO)而不是select() 。如果recv()因超时而失败,WSAGetLastError()将报告WSAETIMEDOUT :

sck = socket(...);

DWORD timeout = MAXWAIT * 1000;
setsockopt(sck, SOL_SOCKET, SO_RCVTIMEO, (char*)&timeout, sizeof(timeout));

bool readN(SOCKET s, int size, char* buffer){
    int res;
    std::cout << "-----called readN to read " << size << " byte(s)" << std::endl;
    while (size > 0) {
        res = recv(s, buffer, size, 0);
        if (res == SOCKET_ERROR) {
            /*
            res = WSAGetLastError();
            if (res == WSAETIMEDOUT) {
                return false; //timer expired
            }
            else {
                return false; //socket error
            }
            */
            return false; //timer expired or socket error
        }

        if (res == 0) {
            return false; //connection closed by client
        }

        buffer += res;
        size -= res;

        std::cout << "\treceived " << res << " byte(s), " << size << " left" << std::endl;
    }

    return true;
}

如果是非阻塞,则不要调用select()除非recv()要求您调用它:

bool readN(SOCKET s, int size, char* buffer){
    fd_set readset;
    struct timeval tv;
    int res;
    std::cout << "-----called readN to read " << size << " byte(s)" << std::endl;
    while (size > 0) {
        res = recv(s, buffer, size, 0);
        if (res == SOCKET_ERROR) {
            res = WSAGetLastError();
            if (res != WSAEWOULDBLOCK) {
                return false; //socket error
            }

            FD_ZERO(&readset);
            FD_SET(s, &readset);
            tv.tv_sec = MAXWAIT;
            tv.tv_usec = 0;

            res = select(0, &readset, NULL, NULL, &tv);
            if (res > 0) {
                continue; //call recv() again
            }

            /*
            else if (res == 0) {
                return false; //timer expired
            }
            else {
                return false; //socket error
            }
            */

            return false; //timer expired or socket error
        }

        if (res == 0) {
            return false; //connection closed by client
        }

        buffer += res;
        size -= res;

        std::cout << "\treceived " << res << " byte(s), " << size << " left" << std::endl;
    }

    return true;
}

关于c++ - 成功接收后清空缓冲区,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/38474190/

相关文章:

linux - 两个具有虚拟接口(interface) ip 的 TCP 客户端应用程序

C++ 套接字只发送前 4 个字节的数据

c++ - 乘以 1.0 和 int 到浮点转换的精度

python - 在 Python 中通过套接字在两个进程之间传递共享内存对象

javascript - 如何通过离开页面处理客户端断开的socket.io?

sockets - 数据在 tcp 套接字中保留多长时间和位置

c - TCP C 程序自动重新连接到客户端

C++使用之后声明的类的对象

java - 在模板类中使用 <limits.h>> 中的最大值

python - Google App Engine 套接字权限被拒绝