【I/O多路复用】阻塞&非阻塞I/O

IO复用的本质是不在任何地方阻塞!

总结:非阻塞情况下,要配合循环使用。

阻塞&非阻塞IO

  • 阻塞:在进/线程中,发起一个调用时,在调用返回之前,进/线程会被阻塞等待,等待中的进/线让出CPU的使用权。

  • 非阻塞:在进/线程中,发起一个调用时,会立即返回。

  • 会阻塞的四个函数:connect()、accept()、send()、recv()。

connect函数看起好像不会阻塞,但是会经历三次握手是阻塞的,如果connect一个不存在的地址,10s后会返回失败,说明是阻塞的。

阻塞&非阻塞IO的应用场景

  • 在传统的网络服务端程序中(每连接每线/进程),采用阻塞IO。

  • 在I0复用的模型中,事件循环不能被阻塞在任何环节,所以,应该采用非阻塞IO

事件循环是不能阻塞的,如果阻塞了就不能响应其他的客户端请求了!上一节epoll服务端代码的send函数是存在问题的,如果发送数据量足够大,或者网络带宽不够,那么send就会阻塞,无法响应其他客户端的请求。

1.非阻塞lO-connect()

  • 对非阻塞的IO调用connect()函数,不管是否能连接成功,connect()都会立即返回失败,设置错误代码errno==EINPROGRESS。 需要仔细思考...有点绕的,这个特殊,跟后面三个不一致。

    if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)
    {
        if (errno!=EINPROGRESS) // 说明失败
        {
            printf("connect(%s:%s) failed.\n",argv[1],argv[2]); close(sockfd);  return -1;
        }
    }

代码是为了理解。

  • 判断是否连接成功:对非阻塞的IO调用connect()函数后,如果socket的状态是可写的,证明连接是成功的,否则是失败的。

在程序中使用IO复用的函数处理一下,使用poll(因为更简单)监视socket的写事件,如果是可写的说明是成功的,如果是不可写就是失败的。

client1.cpp中部分代码:

//把socket设置成非阻塞。
int setnonblocking(int fd)
{
    int flags;
    if((flags = fcntl(fd,F_GETFL,0))==-1)
        flags=0;
​
    return fcntl(fd,F_SETFL,flags|O_NONBLOCK);    
}
int main(int argc, char *argv[])
{
    int sockfd;
    struct sockaddr_in servaddr;
​
    if ((sockfd=socket(AF_INET,SOCK_STREAM,0))<0) { printf("socket() failed.\n"); return -1; }
    setnonblocking(sockfd);
​
    memset(&servaddr,0,sizeof(servaddr));
    servaddr.sin_family=AF_INET;
    servaddr.sin_port=htons(atoi(argv[2]));
    servaddr.sin_addr.s_addr=inet_addr(argv[1]);
​
    if (connect(sockfd, (struct sockaddr *)&servaddr,sizeof(servaddr)) != 0)
    {
        //非阻塞的socket的connect都会返回失败,errno设置为EINPROGRESS。
        if(errno!=EINPROGRESS)
        {
            //errno不等于EINPROGRESS一定失败,等于不一定成功,也可能会失败。
            printf("connect(%s:%s) failed.\n",argv[1],argv[2]); close(sockfd);  return -1;
        }
    }
​
    pollfd fds;
    fds.fd = sockfd;
    fds.events = POLLOUT;
    poll(&fds,1,-1);
    if(fds.revents==POLLOUT)
        printf("connect ok.\n");
    else
        printf("connect faild.\n");
}

客户端采用,服务端很少使用,知道就行了,面试的时候问到了能回答即可。

2.非阻塞IO-accept()

  • 对非阻塞的IO调用accept(),如果已连接队列中没有socket,函数立即返回失败,errno==EAGAIN;如果已连接队列中有socket,accept返回socket。需要仔细思考...有点绕的。

tcpepoll1.cpp部分代码:

// 初始化服务端的监听端口。
int initserver(int port);
​
//把socket设置成非阻塞。
int setnonblocking(int fd)
{
    int flags;
    if((flags = fcntl(fd,F_GETFL,0))==-1)
        flags=0;
    return fcntl(fd,F_SETFL,flags|O_NONBLOCK);    
}
​
int main(int argc,char *argv[])
{
    if (argc != 2) { printf("usage: ./tcpepoll port\n"); return -1; }
​
    // 初始化服务端用于监听的socket。
    int listensock = initserver(atoi(argv[1]));
    printf("listensock=%d\n",listensock);
​
    setnonblocking(listensock);
​
    while(true)
    {
        if(accept(listensock,0,0)==-1)
        {
            if(errno!=EAGAIN)
            {
                perror("accept:");return -1;
            }
            
        }
        else
            break;
    }
    printf("客户端已连接。\n");
    return 0;
}

真实项目中不会这么写,以上代码为了测试,看一下就行,理解非阻塞。

3.非阻塞IO-recv()

对非阻塞的IO调用recv(),如果没数据可读函数(接收缓冲区为空)立即返回失败,errno==EAGAIN。

🔴重点:读取不到数据的时候返回-1;客户端断开的时候读取到0!别搞反了。

4.非阻塞lO-send()

对非阻塞的I0调用send(),如果socket不可写(发送缓冲区已满),函数立即返回失败,errno==EAGAIN。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值