当前位置: 代码迷 >> 综合 >> 非阻塞IO,多路复用
  详细解决方案

非阻塞IO,多路复用

热度:62   发布时间:2024-02-27 11:53:10.0

一、网络编程四种IO模型。
1、什么是IO模型?
网络编程对于数据输出输入方法: 阻塞IO,非阻塞IO,多路复用,信号驱动。

2、这几种IO模型原理是如何?
1)阻塞IO: 一直阻塞等待某一个套接字的数据到达,如果有数据,则读取并返回,如果无数据,就会一直等待。
read(fd);
recv(fd);
accept(sockfd);
以上的例子,在过去,我们都会觉得是函数阻塞,例如read函数,accept函数阻塞,但是这样想是错误,因为真正阻塞的是套接字/文件描述符,并不是函数本身具有阻塞的属性。

因为fd是文件描述符  -> fd默认就是阻塞属性  -> read(fd)  -> 阻塞

2)非阻塞IO:读取一个套接字数据,如果有数据,就会读取返回,如果没有数据,直接返回。
因为fd是文件描述符  -> fd默认就是阻塞属性  -> 添加非阻塞属性给fd  -> read(fd)  -> 非阻塞读取。

3)

4)

二、非阻塞IO模型。
1、如果要使用非阻塞IO,思路是如何?
1)先创建一个文件描述符/套接字   -> 默认都是阻塞属性。
2)设置非阻塞的属性给文件描述符  -> 这时候,文件描述符就是非阻塞的。
3)再调用read()/accept()/recv()去处理这个文件描述符时,就会非阻塞。

2、如何设置非阻塞属性给文件描述符?  ->  fcntl()  -> man 2 fcntl

头文件:
#include <unistd.h>
#include <fcntl.h>

原型:
    int fcntl(int fd, int cmd, ... /* arg */ );

参数:
    fd: 需要设置属性的文件描述符
    cmd:
         F_GETFL (void)  -> void代表第三个参数不需要填
         Get the file access mode and the file status flags; arg is ignored.
         //获取当前文件描述符的属性


         F_SETFL (int)   -> int代表后面那个参数要填
         Set the file status flags to the value specified by arg.

         O_NONBLOCK   -> 非阻塞属性

返回值:
    成功:
        F_GETFL    返回文件的属性
        F_SETFL    返回0
            
    失败:
        -1

   例题1: 设置非阻塞属性给监听套接字,看看这个套接字还会不会阻塞等待客户端连接?

   listen(sockfd);
   accept(sockfd);  -> 阻塞   -> 有人连接,accept()返回
                 没有人连接,就会一直等待

   listen(sockfd);  
   设置非阻塞属性给sockfd  
   accept(sockfd);  -> 非阻塞  -> 有人连接,accept()返回成功
                   -> 没有人连接,就会返回失败        

#include "head.h"

int main(int argc,char *argv[])
{
    //1. 创建一个TCP套接字
    int sockfd;
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    
    //2. 绑定IP地址,端口号
    struct sockaddr_in srvaddr;
    socklen_t len = sizeof(srvaddr);
    bzero(&srvaddr,len);
    
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(atoi(argv[1]));
    srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    bind(sockfd,(struct sockaddr *)&srvaddr,len);
    
    //3. 设置监听套接字
    listen(sockfd,5);
    
    //sockfd  -> 默认是阻塞属性
    
    //4. 添加非阻塞属性给sockfd
    int state;
    state = fcntl(sockfd,F_GETFL); //state就是sockfd当前的属性。
    state |= O_NONBLOCK;           //state就是新属性了
    fcntl(sockfd,F_SETFL,state);
    
    //sockfd  -> 具有非阻塞属性
    
    //5. 等待客户端连接
    struct sockaddr_in cliaddr;
    bzero(&cliaddr,len);
    int connfd;
    
    while(1)
    {
        connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);
        if(connfd > 0) //有人连接,就会返回成功
        {
            printf("connfd = %d\n",connfd);
            printf("new connection:%s\n",inet_ntoa(cliaddr.sin_addr));
            break;
        }
        else{ //没有人连接,就会返回失败
            printf("connect error!\n");
        }
    }
        
    close(sockfd);
    close(connfd);

    return 0;
}

   练习1: 使用非阻塞IO读取TCP套接字上数据。
      -> 有数据就打印出来,没有返回失败。

参考p1目录。

三、多路复用。
1、什么是多路复用?
就是先将需要监听的文件描述符加入到一个集合中,然后在规定的时间内/无限等待去监听这个集合,如果规定的时间内/无限等待过程中有数据到达,则其他没有数据到达的文件描述符就会被自动剔除到集合之外,我们用户只需要观察集合中有哪个文件描述符存在就可以。

2、如何实现多路复用?
1)定义一个集合。  ->  man 2 select
   fd_set set;

2)删除、添加、清空,判断集合?
   void FD_CLR(int fd, fd_set *set);    -> 把集合中的fd删除掉
   int  FD_ISSET(int fd, fd_set *set);  -> 判断fd是不是在集合中
   void FD_SET(int fd, fd_set *set);    -> 将fd添加到集合中
   void FD_ZERO(fd_set *set);           -> 清空集合中所有的文件描述符

  fd: 需要处理的文件描述符。
  set:集合的地址

返回值:
    在集合中:  1
    不在集合中:0

3)如何监听一个集合中?  -> select()  -> man 2 select

头文件:
       #include <sys/select.h>
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
    
参数:
nfds:所有正在监测的套接字的最大值加1
readfds:读就绪文件描述符集合   -> 把所有需要监听的文件描述符加入到这个集合中即可。
writefds:写就绪文件描述符集合  -> NULL
exceptfds:异常就绪文件描述符集合  -> NULL
timeout:超时控制                 -> NULL (代表无限等待)

返回值:
    成功:就绪文件描述符总数
    失败:-1


   例题2: 实现客户端与服务器互相收发,不能实现线程,要求使用多路复用。
   
服务器端:
#include "head.h"

int main(int argc,char *argv[])
{
    //1. 创建一个TCP套接字
    int sockfd;
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    
    //2. 绑定IP地址,端口号
    struct sockaddr_in srvaddr;
    socklen_t len = sizeof(srvaddr);
    bzero(&srvaddr,len);
    
    srvaddr.sin_family = AF_INET;
    srvaddr.sin_port = htons(atoi(argv[1]));
    srvaddr.sin_addr.s_addr = htonl(INADDR_ANY);
    
    bind(sockfd,(struct sockaddr *)&srvaddr,len);
    
    //3. 设置监听套接字
    listen(sockfd,5);
    
    //4. 等待对方的连接
    int connfd;
    struct sockaddr_in cliaddr;
    bzero(&cliaddr,len);
    
    connfd = accept(sockfd,(struct sockaddr *)&cliaddr,&len);
    if(connfd > 0)
    {
        printf("connfd = %d\n",connfd);
        printf("new connection:%s\n",inet_ntoa(cliaddr.sin_addr));
    }
    
    //5. 定义一个集合
    fd_set set;
    char buf[100];
    int max_fd = connfd > STDIN_FILENO ? connfd : STDIN_FILENO;
    
    //6. 不断监听这个集合,看看谁留下来了。
    while(1)
    {
        FD_ZERO(&set);
        FD_SET(connfd,&set);
        FD_SET(STDIN_FILENO,&set);
        
        //7. 一直阻塞等待
        select(max_fd+1,&set,NULL,NULL,NULL);
        
        //8. 只要集合中任意一个文件描述符有数据到达,那么select就会返回。
        if(FD_ISSET(connfd,&set))  //connfd还在,说明我要收数据
        {
            bzero(buf,sizeof(buf));
            recv(connfd,buf,sizeof(buf),0);
            printf("from client:%s",buf);
            
            if(strncmp(buf,"quit",4) == 0)
            {
                break;
            }
        }
        
        if(FD_ISSET(STDIN_FILENO,&set))  //STDIN_FILENO还在,说明要发送数据给对方。
        {
            bzero(buf,sizeof(buf));
            fgets(buf,sizeof(buf),stdin);
            send(connfd,buf,strlen(buf),0);
            if(strncmp(buf,"quit",4) == 0)
            {
                break;
            }
        }
    }
    
    close(connfd);
    close(sockfd);
    
    return 0;
}

客户端:
#include "head.h"

int main(int argc,char *argv[])  // ./jack 192.168.14.4 50000
{
    //1. 创建TCP套接字
    int sockfd;
    sockfd = socket(AF_INET,SOCK_STREAM,0);
    //printf("sockfd = %d\n",sockfd);
    
    //2. 准备rose的IP地址。
    struct sockaddr_in srvaddr;
    socklen_t len = sizeof(srvaddr);
    bzero(&srvaddr,len);
    
    srvaddr.sin_family = AF_INET;  //地址族
    srvaddr.sin_port = htons(atoi(argv[2]));  //端口号
    inet_pton(AF_INET,argv[1],&srvaddr.sin_addr); //设置好Rose的IP地址
    
    
    //3. 发起连接
    //connect调用之前: sockfd  -> 待连接套接字
    int ret = connect(sockfd,(struct sockaddr *)&srvaddr,len);
    if(ret == 0)
    {
        printf("connect success!\n");
    }
    //connect调用之后: sockfd  -> 已连接套接字
    
    //5. 定义一个集合
    fd_set set;
    char buf[100];
    int max_fd = sockfd > STDIN_FILENO ? sockfd : STDIN_FILENO;
    
    //6. 不断监听这个集合,看看谁留下来了。
    while(1)
    {
        FD_ZERO(&set);
        FD_SET(sockfd,&set);
        FD_SET(STDIN_FILENO,&set);
        
        //7. 一直阻塞等待
        select(max_fd+1,&set,NULL,NULL,NULL);
        
        //8. 只要集合中任意一个文件描述符有数据到达,那么select就会返回。
        if(FD_ISSET(sockfd,&set))  //sockfd还在,说明我要收数据
        {
            bzero(buf,sizeof(buf));
            recv(sockfd,buf,sizeof(buf),0);
            printf("from client:%s",buf);
            
            if(strncmp(buf,"quit",4) == 0)
            {
                break;
            }
        }
        
        if(FD_ISSET(STDIN_FILENO,&set))  //STDIN_FILENO还在,说明要发送数据给对方。
        {
            bzero(buf,sizeof(buf));
            fgets(buf,sizeof(buf),stdin);
            send(sockfd,buf,strlen(buf),0);
            if(strncmp(buf,"quit",4) == 0)
            {
                break;
            }
        }
    }

    //5. 回收TCP套接字的资源
    close(sockfd);
    
    return 0;
}