千家信息网

Socket-IO复用技术

发表于:2024-11-13 作者:千家信息网编辑
千家信息网最后更新 2024年11月13日,(上一篇地址)前面使用socket完成一个服务器对应多个客户端的小实验的时候,针对TCP连接,我们必须得创建新的进程来与新的客户端通信。那么,就意味着,1000个客户端就有有1000个server进程
千家信息网最后更新 2024年11月13日Socket-IO复用技术

(上一篇地址)前面使用socket完成一个服务器对应多个客户端的小实验的时候,针对TCP连接,我们必须得创建新的进程来与新的客户端通信。那么,就意味着,1000个客户端就有有1000个server进程,这显然是不实际的。如果,我们可以提前把要监听的文件描述符放到一个集合里,一旦其中一个发生事件(不管是连上,还是通信),就去处理。这样,会方便很多。所以,今天学习一下IO复用。

1 五个I/O模型

  • 阻塞I/O
  • 非阻塞I/O
  • I/O复用(select和poll)
  • 信号驱动I/O
  • 异步I/O

    阻塞IO

    最流行的I/O模型是阻塞I/O模型,缺省时,所有的套接口都是阻塞的。

    非阻塞IO

    IO复用

    信号驱动IO

    异步IO

    2 I/O复用

    如果一个或多个I/O条件满足(例如:输入已准备好被读,或者描述字可以承接更多输出的时候)我们就能够被通知到,这样的能力被称为I/O复用,是由函数selectpoll支持的。

    I/O复用网络应用场合

  • 当客户处理多个描述字
  • 一个客户同时处理多个套接口
  • 如果一个tcp服务器既要处理监听套接口,又要处理连接套接口
  • 如果一个服务器既要处理TCP,又要处理UDP

select

         /* According to POSIX.1-2001 */       #include        /* According to earlier standards */       #include        #include        #include        int select(int nfds, fd_set *readfds, fd_set *writefds,                  fd_set *exceptfds, struct timeval *timeout);       void FD_CLR(int fd, fd_set *set);//从集合中删除一个描述字       int  FD_ISSET(int fd, fd_set *set);//描述字是否在该集合中       void FD_SET(int fd, fd_set *set);//添加一个描述字到集合中       void FD_ZERO(fd_set *set);//清空描述字集合
  • 作用:函数允许进程指示内核等待多个事件中的任一个发生,并仅在一个或多个事件发生或经过某指定的时间后才唤醒进程
    提供了即时响应多个套接的读写事件
  • 参数:
  • nfds:集合中最大的文件描述符 + 1 (指定被测试的描述字个数,它的值是要被测试的最大描述字加1,描述字0、1、2…….一直到nfds均被测试)
  • readfds:要检查读事件的容器
  • writefds:要检查写事件的容器
  • timeout:超时时间
  • 返回值:返回触发套接字的个数
    中间的三个参数readset、writeset和exceptset指定我们要让内核测试读、写和异常条件所需的描述字
    如果我们对某个条件不感兴趣,这三个参数中相应的参数就可以设为空指针

timeout参数

时间的结构体如下:

            struct timeval(                long tv_sec;  //秒                long tv_usec;//微秒            );

timeout参数有三种可能

  • 永远等待下去:仅在有一个描述字准备好I/O时才返回,为此,我们将timeout设置为空指针
  • 等待固定时间:在有一个描述字准备好I/O是返回,但不超过由timeout参数所指timeval结构中指定的秒数和微秒数
  • 根本不等待:检查描述字后立即返回,这称为轮询。定时器的值必须为0

    fd_set参数

    select使用描述字集,它一般是一个整数数组,每个数中的每一位对应一个描述字。

    使用流程

    使用select完成之前socket的测试,流程如下:

    客户端代码不变。

    #include < sys/types.h>     #include < sys/socket.h>#include < netinet/in.h>    //sockaddr_in#include < stdio.h>#include < string.h>//TCPint main(){    int fd;    int ret;    int addrLen;    char acbuf[20] = "";    struct sockaddr_in serAddr = {0};    struct sockaddr_in myAddr = {0};    //1.socket();    fd = socket(PF_INET,SOCK_STREAM,0);    if(fd == -1)    {        perror("socket");        return -1;    }    //2.连接connect() 服务器的地址    serAddr.sin_family = AF_INET;    serAddr.sin_port = htons(1234);    serAddr.sin_addr.s_addr = inet_addr("192.168.159.5");    ret = connect(fd,(struct sockaddr *)&serAddr,sizeof(struct sockaddr_in));    if(ret == -1)    {        perror("connect");        return -1;    }    //获取自己的地址    addrLen = sizeof(struct sockaddr_in);    ret = getsockname(fd,(struct sockaddr *)&myAddr,&addrLen);    if(ret == -1)    {        perror("getsockname");        return -1;    }    printf("client---ip: %s , port: %d\n",\                inet_ntoa(myAddr.sin_addr),ntohs(myAddr.sin_port));    //3.通信    while(1)    {        printf("send: ");        fflush(stdout);        scanf("%s",acbuf);        if(strcmp(acbuf,"exit") == 0)        {            break;        }        write(fd,acbuf,strlen(acbuf));    }    //4.close()    close(fd);    return 0;}

    服务器端:
    select.c

        #include < sys/types.h>         #include < sys/socket.h>    #include < netinet/in.h>    //sockaddr_in    #include < stdio.h>    #include < string.h>    #include < signal.h>    #include < sys/select.h>    #include < unistd.h>    #include < sys/time.h>    //TCP    int main()    {        int fd;        int clientfd;        int ret;        pid_t pid;        int i;        int maxfd;          //当前最大套接字        int nEvent;        fd_set set = {0};   //监听集合        fd_set oldset = {0};    //存放所有要监听的文件描述符        struct timeval time = {0};        int reuse = 0;        char acbuf[20] = "";        char client_addr[100] = "";        struct sockaddr_in addr = {0};  //自己的地址        struct sockaddr_in clientAddr = {0};    //连上的客户端的地址        int addrLen = sizeof(struct sockaddr_in);        signal(SIGCHLD,SIG_IGN);        //1.socket()        fd = socket(PF_INET,SOCK_STREAM,0);        if(fd == -1)        {            perror("socket");            return -1;        }        //会出现没有活动的套接字仍然存在,会禁止绑定端口,出现错误:address already in use .        //由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,该状态会保留2-4分钟        if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)            {            perror("setsockopet error\n");            return -1;            }        //2.bind()        addr.sin_family = AF_INET;        addr.sin_port = htons(1234);        addr.sin_addr.s_addr = inet_addr("192.168.159.5");        ret = bind(fd,(struct sockaddr *)&addr,addrLen);        if(ret == -1)        {            perror("bind");            return -1;        }        //3.listen()        ret = listen(fd,10);        if(ret == -1)        {            perror("listen");            return -1;        }        //创建监听集合        FD_ZERO(&oldset);        FD_SET(fd,&oldset);        //maxfdp1:当前等待的最大套接字。比如:当前fd的值=3,则最大的套接字就是3        //所以每当有客户端连接进来,就比较一下文件描述符        maxfd = fd;        //select        //select之前,set放的是所有要监听的文件描述符;{3,4,5}        //select之后,set只剩下有发生事件的文件描述符。{3}        while(1)        {            set = oldset;            printf("before accept.\n");            time.tv_sec = 5;            nEvent = select(maxfd + 1,&set,NULL,NULL,&time);    //返回文件描述符的个数(即事件的个数)            printf("after accept.%d\n",nEvent);            if(nEvent == -1)            {                perror("select");                return -1;            }            else if(nEvent == 0)    //超时            {                printf("time out");                return 1;            }            else            {                           //有事件发生                //判断是否是客户端产生的事件                for(i = 0 ; i <= maxfd ; i++)                {                    if(FD_ISSET(i,&set))                    {                        if(i == fd)                        {                            clientfd = accept(fd,(struct sockaddr *)&clientAddr,&addrLen);                            FD_SET(clientfd,&oldset);                            printf("client ip:%s ,port:%u\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));                            if(clientfd > maxfd)                            {                                maxfd = clientfd;                            }                        }                        else                        {                            memset(acbuf,0,20);                            if(read(i,acbuf,20) == 0) //客户端退出                            {                                close(i);                                //还要从集合里删除                                FD_CLR(i,&oldset);                            }                            else                                printf("receive: %s\n",acbuf);                        }                    }                }            }        }        return 0;    }

    epoll

    epoll用到的函数有以下几个:

                #include        int epoll_create(int size);//创建epoll             int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);//操作函数             int epoll_wait(int epfd, struct epoll_event *events,                      int maxevents, int timeout);   

事件集合的结构体:

(这里 ,还要注意,epoll的超时参数是int,单位是us)

使用流程

        #include              #include         #include  //sockaddr_in        #include         #include         #include         #include         //epoll        //epoll_wait() epoll_creat() epoll_ctl()        //TCP        int main()        {            int fd;            int clientfd;            int ret;            pid_t pid;            int i;            int epfd;            int nEvent;            struct epoll_event event = {0};            struct epoll_event rtl_events[20] = {0};    //事件结果集            int reuse = 0;            char acbuf[20] = "";            char client_addr[100] = "";            struct sockaddr_in addr = {0};  //自己的地址            struct sockaddr_in clientAddr = {0};    //连上的客户端的地址            int addrLen = sizeof(struct sockaddr_in);            signal(SIGCHLD,SIG_IGN);            //1.socket()            fd = socket(PF_INET,SOCK_STREAM,0);            if(fd == -1)            {                perror("socket");                return -1;            }            //会出现没有活动的套接字仍然存在,会禁止绑定端口,出现错误:address already in use .            //由TCP套接字TIME_WAIT引起,bind 返回 EADDRINUSE,该状态会保留2-4分钟            if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse)) < 0)                {                perror("setsockopet error\n");                return -1;                }            //2.bind()            addr.sin_family = AF_INET;            addr.sin_port = htons(1234);            addr.sin_addr.s_addr = inet_addr("192.168.159.5");            ret = bind(fd,(struct sockaddr *)&addr,addrLen);            if(ret == -1)            {                perror("bind");                return -1;            }            //3.listen()            ret = listen(fd,10);            if(ret == -1)            {                perror("listen");                return -1;            }            epfd = epoll_create(1000);  //同时监听的文件描述符            event.data.fd = fd;            event.events = EPOLLIN;  //读            epoll_ctl(epfd,EPOLL_CTL_ADD,fd, &event);            while(1)            {        //      nEvent = epoll_wait(epfd,rtl_events,20,-1);  //-1:阻塞    0:非阻塞                nEvent = epoll_wait(epfd,rtl_events,20,5000);                if(nEvent == -1)                {                    perror("epoll_wait");                    return -1;                }                else if(nEvent == 0)                {                    printf("time out.");                }                else                {                    //有事件发生,立即处理                    for(i = 0; i < nEvent;i++)                    {                        //如果是 服务器fd                        if( rtl_events[i].data.fd == fd )                        {                            clientfd = accept(fd,(struct sockaddr *)&clientAddr,&addrLen);                            //添加                            event.data.fd = clientfd;                            event.events = EPOLLIN;  //读                            epoll_ctl(epfd,EPOLL_CTL_ADD,clientfd,&event);                            printf("client ip:%s ,port:%u\n",inet_ntoa(clientAddr.sin_addr),ntohs(clientAddr.sin_port));                        }                        else                        {                            //否则 客户端fd                             memset(acbuf,0,20);                            ret = read(rtl_events[i].data.fd,acbuf,20);                            printf("%d\n",ret);                            if( ret == 0) //客户端退出                            {                                close(rtl_events[i].data.fd);                                //从集合里删除                                epoll_ctl(epfd,EPOLL_CTL_DEL,rtl_events[i].data.fd,NULL);                            }                            else                                printf("receive: %s\n",acbuf);                        }                    }                }            }            return 0;        }

运行结果如前,正常收发。

0