[IO复用] recv()和send()的阻塞和非阻塞、返回值、超时

前言

记录一下recv和send函数的相关信息。

阻塞

头文件

#include <sys/socket.h> //socket()
#include <unistd.h>  //close()
#include<netinet/in.h> //sockaddr_in

#include <stdio.h> //perror()
#include <errno.h>

#include <string.h> //memset
#include <iostream>

#include <netinet/in.h> //inet_ntoa
#include <arpa/inet.h>

阻塞模式server代码,进行recv

#if 1 //阻塞模式
int main()
{
    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(listen_fd == -1) {
        perror("socket");
        return -1;
    }

    timeval timev;
    timev.tv_sec = 3;
    timev.tv_usec = 0;
    int ret = setsockopt(listen_fd, SOL_SOCKET, SO_RCVTIMEO, (void*)&timev, sizeof(timev));
    if(ret != 0)
    {
        perror("setsockopt SO_RCVTIMEO");
        close(listen_fd);
        return -1;
    }

    sockaddr_in local_addr;
    local_addr.sin_family = AF_INET;
    local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    local_addr.sin_port = htons(9100);
    ret = bind(listen_fd, (sockaddr*)&local_addr, sizeof(local_addr));
    if(ret == -1)
    {
        perror("bind");
        close(listen_fd);
        return -1;
    }

    ret = listen(listen_fd,5);
    if (ret == -1)
    {
        perror("listen");
        close(listen_fd);
        return -1;
    }

    sockaddr_in cnn_addr;
    memset(&cnn_addr, 0x00, sizeof(cnn_addr));
    socklen_t len = sizeof(cnn_addr);
    int conn_fd = accept(listen_fd, (sockaddr*)&cnn_addr, &len);
    if(conn_fd == -1)
    {
        perror("accept");
        close(listen_fd);
        return -1;
    }
    else
    {
        std::cout << "accept addr:" << inet_ntoa(cnn_addr.sin_addr)
                  << " port:" << ntohs(cnn_addr.sin_port) << std::endl;
        int loop_count = 0;

        char buff[512] = {0x00};
        while (1)
        {
            std::cout << "loop:" << ++loop_count << " times" << std::endl;
            int count = recv(conn_fd, buff, 512, 0);
            if(count == 0) 
            {
                //无论阻塞模式还是非阻塞模式,
                //recv 返回0,都表示链接断开
                std::cout << "close end" << std::endl;
                break;
            }
            if(count == -1)
            {
                 //无论阻塞模式还是非阻塞模式,
                //recv 返回-1,都表示错误,返回一下三个错误,被视为正常
                if (errno == EINTR || 
                    errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    std::cout << "EWOULDBLOCK.continue" << std::endl;
                    continue;
                }
                else
                {
                    std::cout << "ERROR.break" << errno <<std::endl;
                    break;
                }
            }
            else 
                std::cout << "recv (" << count << " bytes):" << buff << std::endl;
        }
    }

    std::cout << "close connection" << std::endl;
    close(listen_fd);
    return 0;
}
#endif //阻塞模式

阻塞模式client代码,进行send

#if 1 //阻塞模式
int main()
{
    int local_fd = socket(AF_INET, SOCK_STREAM, 0);
    if(local_fd == -1) {
        perror("socket");
        return -1;
    }

    timeval timev;
    timev.tv_sec = 3;
    timev.tv_usec = 0;
    int ret = setsockopt(local_fd, SOL_SOCKET, SO_SNDTIMEO, (void*)&timev, sizeof(timev));

    sockaddr_in local_addr;
    local_addr.sin_family = AF_INET;
    local_addr.sin_addr.s_addr = htonl(INADDR_ANY);
    local_addr.sin_port = htons(9100);
    ret = connect(local_fd, (sockaddr*)&local_addr, sizeof(local_addr));
    if(ret == -1)
    {
        perror("accept");
        close(local_fd);
        return -1;
    }
    else
    {
        std::cout << "connect addr:" << inet_ntoa(local_addr.sin_addr)
                  << " port:" << ntohs(local_addr.sin_port) << std::endl;
        int loop_count = 0;

        char buff[512] = {0x00};
        sprintf(buff, "%s", "hello");
        while (1)
        {
            std::cout << "loop:" << ++loop_count << " times" << std::endl;

            getchar();
            int count = send(local_fd, buff, strlen(buff), 0);
            if(count == 0) 
            {
                //无论阻塞模式还是非阻塞模式,
                //send 返回0,都表示链接断开
                std::cout << "close end" << std::endl;
                break;
            }
            if(count == -1)
            {
                 //无论阻塞模式还是非阻塞模式,
                //send 返回-1,都表示错误,返回一下三个错误,被视为正常
                if (errno == EINTR || 
                    errno == EAGAIN || errno == EWOULDBLOCK)
                {
                    std::cout << "EWOULDBLOCK.continue" << std::endl;
                    continue;
                }
                else
                {
                    std::cout << "ERROR.break" << errno <<std::endl;
                    break;
                }
            }
            else
                std::cout << "send (" << count << " bytes):" << buff << std::endl;
        }
    }

    std::cout << "close connection" << std::endl;
    close(local_fd);
    return 0;
}
#endif //阻塞模式

超时设置

上面代码中均用setsockopt进行了recv和send的超时设置。

int ret = setsockopt(listen_fd, SOL_SOCKET, SO_RCVTIMEO, (void*)&timev, sizeof(timev));
 int ret = setsockopt(local_fd, SOL_SOCKET, SO_SNDTIMEO, (void*)&timev, sizeof(timev));

这个超时设置不但包括recv和send,还包括accept。
accept
当accept超时的时候,会返回-1,errno==11(“Resource temporarily unavailable”)。

recv
当recv超时的时候,会返回-1, errno==EAGAIN(11)。
EWOULDBLOCK 就是EAGAIN

#define	EWOULDBLOCK	EAGAIN	/* Operation would block */

非阻塞

创建socket时,直接用SOCK_NOBLOCK指定为非阻塞

server部分

设置如下,因为socket()默认是阻塞的,所以设置了SOCK_NONBLOCK就会是非阻塞socket了。

- int listen_fd = socket(AF_INET, SOCK_STREAM, 0); //block socket
+ int listen_fd = socket(AF_INET, SOCK_STREAM | SOCK_NONBLOCK, 0); //non-block socket

和上面的超时设置一样,如果在sock()函数中,设置了SOCK_NONBLOCK参数,accept会变成非阻塞,
如果执行时没有连接可以返回,accept就会直接返回-1,errno:11。
需要用一个while循环, 当accept返回 -1 并且errno 为EAGAIN 时,重复accept。

但是此时recv和send还是阻塞的
这是因为,上面的diff代码,创建的是用于listen的socket,它被设置为非阻塞后,会影响跟他有关的accept。
但是recv和send,关联的socket是accept返回的连接的socket,跟上面listen的fd的设置无关了。

 int conn_fd = accept(listen_fd, (sockaddr*)&cnn_addr, &len);
 ...
 int count = recv(conn_fd, buff, 512, 0);

此时可以用accept4()把accept返回的socket,设置为非阻塞的:

- int conn_fd = accept(listen_fd, (sockaddr *)&cnn_addr, &len); //return block socket
+ int conn_fd = accept4(listen_fd, (sockaddr *)&cnn_addr, &len, SOCK_NONBLOCK); //return non-block socket

这样后续的recv 就是非阻塞了。

client 部分

向上面一样,在socket()中添加SOCK_NONBLOCK参数,可以把socket设置为非阻塞。
和socket关联的connect也是非阻塞的了。当无法立即建立连接,connect会返回EINPROGRESS。
可以直接不启动server,直接启动client进行connect来测试:
阻塞connect的执行结果:

~/share/myproj/2.1.1-multiIO/20240121$ ./client
connect: Connection refused ← perror输出

非阻塞connect的执行结果:

~/share/myproj/2.1.1-multiIO/20240121$ ./client
connect: Operation now in progress ← perror输出
connect EINPROGRESS continue ← 循环处的log,证明continue了
connect: Connection refused ← perror输出

跟上面同理,connect后面的send,也变成非阻塞的了。
此时让server不调用recv(), client的send会一直发送,直到server的接收缓冲区满了,send将会立刻返回一个EWOULDBLOCK错误。
而如果是阻塞的socket,send将会阻塞住。

使用fcntl()把socket设置为非阻塞

这里只是设置方式的差别。
实际效果是和上面一样的。
上面是在socket()函数中,设置SOCK_NONBLOCK来进行非阻塞设置。
还可以用以下方法:
代码来自于:使用fcntl()函数设置socket为阻塞态或非阻塞态
设置为非阻塞

#include <fcntl.h> //fcntl
int func()
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    int flag = fcntl(fd,F_GETFL,0);//获取文件fd当前的状态
    //int flag = fcntl(fd,F_GETFL);//不用第3个参数也可以
    if(flag<0)
    {
        perror("fcntl F_GETFL fail"); 
        close(fd); 
        return -1; 
    }
    if (fcntl(fd, F_SETFL, flag | O_NONBLOCK) < 0)//设置为非阻塞态
     {  
        perror("fcntl F_SETFL fail"); 
        close(fd);
        return -1; 
    }
}

恢复阻塞

int func()
{
    int fd = socket(AF_INET, SOCK_STREAM, 0);
    int flag = fcntl(fd,F_GETFL,0);//获取文件fd当前的状态
    if(flag<0)
    {
        perror("fcntl F_GETFL fail"); 
        close(fd); 
        return -1; 
    }
    
    flag = flag&~O_NONBLOCK;
    //flag &= ~O_NONBLOCK;//等同于上行
    if (fcntl(fd, F_SETFL, flag) < 0)//设置为阻塞态
     {  
        perror("fcntl F_SETFL fail"); 
        close(fd);
        return -1; 
    }
}

socket阻塞,单独把recv或者send设置为非阻塞

也可以单独把recv和send函数设置为非阻塞。
设置方法如下:

- int count = recv(conn_fd, buff, 512, 0);
+  count = recv(conn_fd, buff, 512, MSG_DONTWAIT);

这样就是socket是阻塞的,但是设置了MSG_DONTWAIT的recv是非阻塞的(除了这个recv函数,别的accept等函数都还是阻塞的)。

recv和send的返回值

不论阻塞还是非阻塞,
recv的返回值,都是
大于0,表示接受的字节数
等于0,表示断开连接
等于-1,如果是errno == (EAGAIN || EWOULDBLOCK || EINTR) ,则表示正常,继续recv;
其余errno 表示发生错误。

send也是如此。

参考文章

socket阻塞与非阻塞情况下的recv、send、read、write返回值

使用fcntl()函数设置socket为阻塞态或非阻塞态

send与recv函数详解

  • 71
    点赞
  • 70
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
要实现非阻塞recvsend,你可以使用以下方法: 1. 设置套接字为非阻塞模式:使用fcntl函数将套接字的文件描述符设置为非阻塞模式。例如: ```cpp #include <fcntl.h> int setNonBlocking(int sockfd) { int flags = fcntl(sockfd, F_GETFL, 0); if (flags == -1) { return -1; } if (fcntl(sockfd, F_SETFL, flags | O_NONBLOCK) == -1) { return -1; } return 0; } ``` 你可以在创建套接字后立即调用`setNonBlocking(sockfd)`将其设置为非阻塞模式。 2. 使用select或poll函数进行IO多路复用:使用select或poll函数可以同时监视多个套接字的可读或可写状态,以避免阻塞。例如,在非阻塞模式下使用select函数: ```cpp #include <sys/select.h> int nonBlockingRecv(int sockfd, char* buffer, int bufferSize) { fd_set readfds; FD_ZERO(&readfds); FD_SET(sockfd, &readfds); struct timeval timeout; timeout.tv_sec = 0; timeout.tv_usec = 0; int selectResult = select(sockfd + 1, &readfds, NULL, NULL, &timeout); if (selectResult == -1) { return -1; } if (selectResult == 0) { // 没有数据可读 return 0; } if (FD_ISSET(sockfd, &readfds)) { // 有数据可读 int recvResult = recv(sockfd, buffer, bufferSize, 0); if (recvResult == -1) { // 接收错误 return -1; } return recvResult; } return 0; } ``` 类似地,你可以实现非阻塞send函数。 这些方法可以在C++中实现非阻塞recvsend操作。请注意,非阻塞IO需要适当的处理,因为它们可能不会立即返回所需的数据量。你需要根据实际情况调整代码以处理部分读取/写入的情况。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值