成功recv后清空缓冲区

时间:2016-07-20 06:49:40

标签: c++ sockets tcp winsock recv

我正在使用C ++在Windows上编写服务器,我使用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()返回正数,缓冲区仍为空。我错过了什么?

2 个答案:

答案 0 :(得分:2)

我在你的代码中看到了一些问题。

  1. 每次致电readset时,您都没有重置select()变量。 select()修改变量。对于单插槽情况,这不是太糟糕,但你应该养成每次重置变量的习惯。

  2. 您没有检查recv()返回的错误。您认为任何非优雅断开都是成功的,但并非总是如此。

  3. 在返回readN()之前,在true结尾处
  4. ,您正在将buffer参数输出到std::cout,但是buffer将指向数据的 END ,而不是 BEGINNING ,因为它是由读取循环推进的。这可能是你对空白缓冲区&#34;是来自。 readN()本身甚至不应该输出数据,因为您在readN()退出后执行此操作,否则最终会出现冗余输出消息。

  5. 如果readN()返回true,则使用期望空终止buffer字符串的std::cout将最终operator<<传递给char,但是你的缓冲区不能保证以空值终止。

  6. 尝试更像这样的东西:

    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()要求您致电:{/ p>

    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;
    }
    

答案 1 :(得分:0)

readN()的末尾有

std::cout << "\t" << buffer << std::endl;

问题是缓冲区现在指向buffer + size的{​​{1}}的原始值。该值已被

修改
buffer

这应输出缓冲区

buffer += res;

使用以下std::cout << "\t" << (buffer - size) << std::endl; readN()进行试验后,如果套接字不是无效句柄(ncat发送的文本/二进制数据),则main()似乎有效。如果套接字是无效句柄,则函数会快速返回。

readN()