linux下的I/O复用模型之select详解【值得看】

select函数详解
int select(int maxfdp, fd_set *readfds, fd_set *writefds, 
               fd_set *exceptfds, struct timeval *timeout);
参数:
(1)maxfdp:    当前最大描述符数+1
(2)readfds:    指向一个套接字集合,用于检测其可读性    
(3)writefds:    指向一个套接字集合,用于检测其可写性    
(4)exceptfds:指向一个套接字集合,用于检测错误
(5)timeout:    select函数的超时时间
   结构体如下:
   struct timeval{
       time_t tv_sec;        
       time_t tv_usec;    
   };
   它可以使select处于三种状态:
   (a)若将NULL以形参传入,即不传入时间结构,就是将select置于阻塞状态,
       一定等到监视文件描述符集合中某个文件描述符发生变化为止;
   (b)若将时间值设为0秒0微秒,就变成一个纯粹的非阻塞函数,
       不管文件描述符是否有变化,都立刻返回继续执行,
       文件无变化返回0,有变化返回一个正值;
   (c)timeout的值大于0,这就是等待的超时时间,即 select在timeout时间内阻塞,
       超时时间之内有事件到来就返回了,否则在超时后不管怎样一定返回。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
fd_set其实这是一个数组的宏定义,实际上是一long类型的数组,
每一个数组元素都能与一打开的文件句柄(socket、文件、管道、设备等)建立联系,
建立联系的工作由程序员完成,当调用select()时,由内核根据IO状态修改fd_set的内容,
由此来通知执行了select()的进程哪个句柄可读。
=============================
ps:
linux下的fd_set结构和windows下的fd_set结构还是有所不同的,
Windows系统下的fd_count和fd_array[]是不可以使用的

1
2
3
4
5
6
7
8
9
系统提供了以下宏可以在不同的套接字环境中提高移植性
(1)FD_CLR(s,*set):从集合set中删除套接字s
(2)FD_ISSET(s,*set):若套接字s是集合中的一员,返回值非0,否则是0    
(3)FD_SET(s,*set)    :将套接字s添加到集合中
(4)FD_ZERO(*set):将set集合初始化为空集NULL

select模型流程(重点)
select函数返回的是就绪socket的数量,用户需要自定义数组来保存就绪的socket并且处理。
fd_set这个结构只是负责监听,但是要注意需要我们自己人为的将传入时的集合(inset)和传出时的集合(outset)分离开来。
inset:代表需要监视的文件描述符,将他们置为1
outset:代表将就绪的socket返回时依旧保留为1,其余的就是置为0
如下图所示:


客户端代码

#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<sys/time.h>

#define ServerIP "192.168.43.8"
#define ServerPort 27015


int main()
{
    int ret;
    char sendbuf[128];
    bzero(sendbuf,sizeof(sendbuf));
    

    struct sockaddr_in addrServer;
    bzero(&addrServer,sizeof(addrServer));
    addrServer.sin_family = AF_INET;
    addrServer.sin_port = htons(ServerPort);
    inet_pton(AF_INET,ServerIP,&addrServer.sin_addr.s_addr);
    
    //创建socket
    int clientfd = socket(AF_INET,SOCK_STREAM,0);
    if(clientfd ==-1){
        printf("socket error\n");
        return -1;
    }
    //发起连接请求
    ret = connect(clientfd,(struct sockaddr*)&addrServer,sizeof(addrServer));
    if(ret == -1){
        printf("connect error\n");
        close(clientfd);
        return -1;
    }
    
    printf("connect successful.....\n");
    
    printf("Please input sendbuf:");
    scanf("%s",sendbuf);
    
    //发送数据

    ret = send(clientfd,sendbuf,sizeof(sendbuf),0);
    if(ret > 0){
        printf("host send:%s\n",sendbuf);
    }
    close(clientfd);
    return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
select模型的服务器代码
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/select.h>
#include<arpa/inet.h>

int main()
{
    //网络初始化
    char recvbuf[1024];
    char ip[16];
    int  ret;
    int     maxfd;
    int  clientfdArr[1024];

    struct sockaddr_in addrserver,addrclient;
    bzero(&addrserver,sizeof(addrserver));
    bzero(&recvbuf,sizeof(recvbuf));
    bzero(&ip,sizeof(ip));

    addrserver.sin_family = AF_INET;
    addrserver.sin_port = htons(27015);
    addrserver.sin_addr.s_addr = htonl(INADDR_ANY);

    //创建socket
    int serverfd = socket(AF_INET,SOCK_STREAM,0);
    if(serverfd == -1){
        printf("create socket error\n");
        return 1;
    }
    //绑定
    ret = bind(serverfd,(struct sockaddr*)&addrserver,sizeof(addrserver));
    if(ret == -1)
    {
        printf("bind error\n");
        close(serverfd);
        return 1;
    }
    //监听
    ret = listen(serverfd,128);
    if(ret == -1)
    {
        printf("listen error\n");
        close(serverfd);
        return 1;
    }
    printf("Select Server Runing.....\n");
    
    
    //启用select模型

    fd_set inset,outset;            
    FD_ZERO(&inset);                
    FD_SET(serverfd,&inset);        //将serverfd放入readset集合中
    
    //初始化socket描述符数组
    for(int i = 0; i < 1024; ++i)
        clientfdArr[i] = -1;
    
    maxfd = serverfd;
    
    //select循环调用
    while(1)
    {
                
        outset = inset;    //将传入监听集合赋给传出监听集合
        ret = select(maxfd+1,&outset,NULL,NULL,NULL);
        if(ret > 0)
        {
            printf("select successful...\n");
            printf("就绪的数量为:%d\t事件:某客户端请求连接.....\n",ret);
            
            //serverfd就绪
            if(FD_ISSET(serverfd,&outset))
            {
                socklen_t size = sizeof(addrclient);
                int clientfd = accept(serverfd,(struct sockaddr*)&addrclient,&size);
                //将新的clientfd加入监听集合和socket描述符数组中
                FD_SET(clientfd,&inset);
                for(int i = 0; i < 1024; ++i)
                    if(clientfdArr[i] == -1){
                        clientfdArr[i] = clientfd;
                        break;
                    }
                maxfd = clientfd > maxfd ? clientfd : maxfd;
            }

            //clientfd就绪,数据传输
            else
            {
                for(int i = 0; i < 1024; ++i)
                {
                    if(clientfdArr[i] != -1)
                    {
                        if(FD_ISSET(clientfdArr[i],&outset))
                        {
                            ret = recv(clientfdArr[i],recvbuf,sizeof(recvbuf),0);
                            if(ret > 0)
                            {
                                printf("recv data is:%s\n",recvbuf);
                                continue;
                            }
                            else if(ret == 0)
                            {
                                printf("client normal closed...\n");
                                close(clientfdArr[i]);
                                FD_CLR(clientfdArr[i],&inset);
                                clientfdArr[i] = -1;
                                break;
                            }
                            else{
                                printf("recv error\n");
                                close(clientfdArr[i]); 
                                FD_CLR(clientfdArr[i],&inset);
                                clientfdArr[i] = -1;
                                break;
                            }
                        }
                    }
                }
            }
        }
        else
        {
            printf("select error\n");
            break;
        }

    }

    close(serverfd);
    return 0;
}

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
测试结果

6.模型评价:
(1)系统开销比较小,实现和维护比较方便,局域网首选
(2)如果IO复用的时候对时间精度要求较高,select支持微妙级别,而poll,epoll只支持到毫秒级别
(3)具有良好的兼容性,支持跨平台
(4)在Linux内核中,select所用到的FD_SET是有限的,由参数FD_SETSIZE来决定,最多也就是1024个
(5)在内核中采用轮询方法,即每次检测都会遍历所有FD_SET中的句柄,即 select要检测的句柄数越多就会越费时,效率会变得很低。
(6)select没有将传入传出分离,用户每次要自己准备传入参数,select返回的只是就绪的数量,用户需要自己取出来校验哪个socket就绪,有一定的系统开销
(7)select函数调用时每次都会将整个监听集合拷贝给内核,内核将集合中所有的socket挂载到设备队列中(里面有大量的重复socket描述符),这种方案产生大量无意义的拷贝开销和设备挂载开销
--------------------- 
作者:bryant_xw 
来源:CSDN 
原文:https://blog.csdn.net/bryant_xw/article/details/89032938 
版权声明:本文为博主原创文章,转载请附上博文链接!

转载于:https://my.oschina.net/u/4000302/blog/3044646

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值