首页 > 编程知识 正文

睡眠面膜的正确用法,阿达帕林凝胶正确用法

时间:2023-05-06 15:33:35 阅读:221253 作者:1546

socket 非阻塞模式下的 recv 行为

非阻塞模式下如果当前无数据可读,recv 函数将立即返回,返回值为 -1,错误码为 EWOULDBLOCK。将客户端代码修成一下:

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990 /** * 验证阻塞模式下recv函数的行为,client端,blocking_client_recv.cpp * zhangyl 2018.12.17 */#include <sys/types.h> #include <sys/socket.h>#include <arpa/inet.h>#include <unistd.h>#include <iostream>#include <string.h>#include <stdio.h>#include <fcntl.h>#include <errno.h>#define SERVER_ADDRESS "127.0.0.1"#define SERVER_PORT 3000#define SEND_DATA "helloworld"int main(int argc, char* argv[]){ //1.创建一个socket int clientfd = socket(AF_INET, SOCK_STREAM, 0); if (clientfd == -1) { std::cout << "create client socket error." << std::endl; return -1; } //2.连接服务器 struct sockaddr_in serveraddr; serveraddr.sin_family = AF_INET; serveraddr.sin_addr.s_addr = inet_addr(SERVER_ADDRESS); serveraddr.sin_port = htons(SERVER_PORT); if (connect(clientfd, (struct sockaddr *)&serveraddr, sizeof(serveraddr)) == -1) { std::cout << "connect socket error." << std::endl;close(clientfd); return -1; }//连接成功以后,我们再将 clientfd 设置成非阻塞模式,//不能在创建时就设置,这样会影响到 connect 函数的行为int oldSocketFlag = fcntl(clientfd, F_GETFL, 0);int newSocketFlag = oldSocketFlag | O_NONBLOCK;if (fcntl(clientfd, F_SETFL, newSocketFlag) == -1){close(clientfd);std::cout << "set socket to nonblock error." << std::endl;return -1;}//直接调用recv函数,程序会阻塞在recv函数调用处while (true){char recvbuf[32] = {0};int ret = recv(clientfd, recvbuf, 32, 0);if (ret > 0) {//收到了数据std::cout << "recv successfully." << std::endl;} else if (ret == 0){//对端关闭了连接std::cout << "peer close the socket." << std::endl;break;} else if (ret == -1) {if (errno == EWOULDBLOCK){std::cout << "There is no data available now." << std::endl;} else if (errno == EINTR) {//如果被信号中断了,则继续重试recv函数std::cout << "recv data interrupted by signal." << std::endl;} else{//真的出错了break;}}}//5. 关闭socketclose(clientfd); return 0;}

执行结果与我们预期的一模一样, recv 函数在无数据可读的情况下并不会阻塞情绪,所以程序会一直有“There is no data available now.”相关的输出。

 

recv 指定长度


// recieve buffer
int RecvBuffer(int nSockfd, char *pBuffer, size_t nLength, size_t &nReceived)
{
    LOG_ENTER;
    size_t nReceive = 0;
    int nRetry = 5;
    nReceived = 0;
    while (true && nRetry > 0)
    {
        // receive buffer
        nReceive = recv(nSockfd, pBuffer+nReceived, nLength-nReceived, 0);
        if (nReceive == -1)
        {
            LOG_ERROR("recv failed!errno=" << errno << "sockfd=" << nSockfd << ",buffer=" << pBuffer);
            switch (errno)
            {
                case EAGAIN:
                case EINTR:
                {
                    nRetry--;
                    usleep(10);
                    continue;
                }

                case EBADF:
                {
                    return 0;
                }

                default:
                {
                    return -1;
                }
            }
        }
        else if (nReceive == 0)
        {
            LOG_ERROR("recv failed!errno=" << errno << "sockfd=" << nSockfd << ",buffer=" << pBuffer);
            return 0;
        }
        else
        {
            // increase received bytes
            nReceived += nReceive;
            // test if be finished or not
            if (nReceived >= nLength)
            {
                return nReceived;
            }
        }
    }
    return -1;
}
 

非阻塞模式下 SEND 和 RECV 函数的返回值总结

我们来根据前面的讨论来总结一下 send 和 recv 函数的各种返回值意义:

返回值 n返回值含义大于 0成功发送 n 个字节0对端关闭连接小于 0( -1)出错或者被信号中断或者对端 TCP 窗口太小数据发不出去(send)或者当前网卡缓冲区已无数据可收(recv)

我们来逐一介绍下这三种情况:

返回值大于 0

对于 send 和 recv 函数返回值大于 0,表示发送或接收多少字节,需要注意的是,在这种情形下,我们一定要判断下 send 函数的返回值是不是我们期望发送的缓冲区长度,而不是简单判断其返回值大于 0。举个例子:

12345 int n = send(socket, buf, buf_length, 0);if (n > 0){ printf("send data successfullyn");}

很多新手会写出上述代码,虽然返回值 n 大于 0,但是实际情形下,由于对端的 TCP 窗口可能因为缺少一部分字节就满了,所以返回值 n 的值可能在 (0, buf_length] 之间,当 0 < n < buf_length 时,虽然此时 send 函数是调用成功了,但是业务上并不算正确,因为有部分数据并没发出去。你可能在一次测试中测不出 n 不等于 buf_length 的情况,但是不代表实际中不存在。所以,建议要么认为返回值 n 等于 buf_length 才认为正确,要么在一个循环中调用 send 函数,如果数据一次性发不完,记录偏移量,下一次从偏移量处接着发,直到全部发送完为止。

123456 //不推荐的方式一int n = send(socket, buf, buf_length, 0);if (n == buf_length){ printf("send data successfullyn");} 123456789101112131415161718192021222324252627282930313233343536 //推荐的方式二:在一个循环里面根据偏移量发送数据bool SendData(const char* buf, int buf_length){ //已发送的字节数目 int sent_bytes = 0; int ret = 0; while (true) { ret = send(m_hSocket, buf + sent_bytes, buf_length - sent_bytes, 0); if (ret == -1) { if (errno == EWOULDBLOCK) { //严谨的做法,这里如果发不出去,应该缓存尚未发出去的数据,后面介绍 break; } else if (errno == EINTR) continue; else return false; } else if (ret == 0) { return false; } sent_bytes += ret; if (sent_bytes == buf_length) break; //稍稍降低 CPU 的使用率 usleep(1); } return true;}

返回值等于 0

通常情况下,如果 send 或者 recv 函数返回 0,我们就认为对端关闭了连接,我们这端也关闭连接即可,这是实际开发时最常见的处理逻辑。

返回值小于 0

对于 send 或者 recv 函数返回值小于 0 的情况(即返回 -1),根据前文的讨论,此时并不表示 send 或者 recv 函数一定调用出错。这里列一个表格说明:

 返回值和错误码send 函数recv 函数操作系统说明1返回 -1,错误码 EWOUDBLOCK 或 EAGAINTCP 窗口太小,数据暂时发不出去当前内核缓冲区中无可读数据Linux2返回 -1,错误码 EINTR被信号中断,需要重试被信号中断,需要重试Linux3返回 -1,错误码不是上述 1 和 2出错出错Linux4返回 -1,错误码 WSAEWOUDBLOCKTCP 窗口太小,数据暂时发不出去当前内核缓冲区中无可读数据Windows5返回 -1,错误码不是上述 4出错出错Windows

注意:这里是针对非阻塞模式下 socket 的 send 和 recv 返回值,如果是阻塞模式下 socket,如果返回值是 -1(Windows 上即 SOCKET_ERROR),则一定表示出错。

阻塞与非阻塞的 SOCKET 的各自适用场景

阻塞的 socket 函数在调用 sendrecvconnectaccept 等函数时,如果特定的条件不满足,就会阻塞其调用线程直至超时,非阻塞的 socket 恰恰相反。这并不意味着非阻塞模式的 socket 模式比阻塞模式的 socket 模式好,二者各有优缺点。

非阻塞模式的 socket,一般用于需要支持高并发多 QPS 的场景下(如服务器程序),但是正如前文所述,这种模式让程序执行流和控制逻辑变复杂;相反,阻塞模式逻辑简单,程序结构简单明了,常用于一些特殊的场景。这里举两个应用的场景:

示例一

程序需要临时发送一个文件,文件分段发送,每段对端都会的给与一个应答,程序可以单独开一个任务线程,在这个任务线程函数里面,使用先 send 后 recv 再 send 再 recv 的模式,每次 send 和 recv 都是阻塞式的。

示例二

A 端与 B 端之间只有问答模式,即 A 发送给 B 一个请求,B 必定会应答给 A 一个响应,除此以外,B 不会给 A 推送任何数据,也可以采取阻塞模式,每次 send 完请求后,就可以直接使用阻塞式的 recv 去接受一定要有的应答包。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。