初始select
- 系统提供select函数来实现多路复用输入/输出模型
- select系统调用是用来监视多个文件描述符的状态变化的
- 程序会停在select这里等待,直到被监视的文件描述符有一个或者多个发生了变化.
select函数原型
- selsect函数原型
参数解释:#include<sys/select.h> int select(int nfds,fd_set *readfds,fd_set*writefds,fd_set*exceptfds,struct timerval*timeout)
a>nfds:需要监视的最大的文件描述符+1;
b>readfds:可读事件文件描述符集合
c>writefds:可写事件文件描述符集合
d>exceptfds:异常事件文件描述符集合
readfds writefds exceptfds都是输入输出型参数,输入时表示:要关注的文件描述符指定的事件,输出时表示,就绪的文件描述符状态
e>timout:
time>0:等待一段固定时间之后,select() 调用会返回,即使没有一个文件描述符处于I/O就绪状态
timeout=0:非阻塞轮询方式,不断的去检测描述符集合的状态,然后立即返回
timeout=NULL:阻塞的方式等待事件发生
select()调用成功时,返回三个集合中I/O就绪的文件描述符总数;返回0代表在描述符词状态改变前已超过
timeout,没 有返回;出错返回-1
select函数原型中fd_set结构
- fd_set是一个结构体,也就是一个数组,底层是一个位图,位图的每一位表示一个文件描述符的状态,输出时1表示需要监听的文件描述符,输入时1表示就绪的文件描述符.
- 系统提供了一组操作位图的接口.
void FD_CLR(int fd,fd_set* set); //清除描述符词组set中对应的fd位 int FD_ISSET(int fd,fd_set* set); //判断相应的fd位是否为真 void FD_SET(int fd,fd_set* set); //设置描述符词组set中对应的fd位 void FD_ZERO(fd_set *set); //清除描述符词组set的全部位
select函数的特点
- 可监控的文件描述符个数取决于sizeof(fdset)的值.
- 将fd加入select监控的同时,还需要使用一个数据结构array来保存放到select监控的fd
一是因为select返回后,array作为元数据和fdset进行FDISSET判断
二是因为select返回会把以前加入到但并无事件发生的fd清空,则每次开始select都要重新从array取fd逐一加入.
select函数的缺点(相对于poll和epoll而言)
- 每次调用select,都需要手动的设置fd集合,从接口使用角度不方便.
- 每次调用select,都需要把fd集合从用户态拷贝到内核态,开销大
- 在内核需要遍历传递进来的fd,开销大
- 支持的文件描述符太小
select函数的优点(相对于多线程,多进程而言)
- 可移植性好
- 免去了系统调度的开销
select---检测标准输入输出
1 #include <stdio.h>
2 #include <unistd.h>
3 #include <sys/select.h>
4 //系统提供select函数来实现多路转接输入输出模型
5 //int select(int nfds,fd_set *readfds,fd_set * writefds,fd_set * exceptfds,struct timeval*timeout);
6 //fd_set是一个结构体,也就是一个数组,更严格的说是一个"位图",用位图的每一位来标识文件描述符
7 int main(){
8 while(1){
9 fd_set read_size;
10 FD_ZERO(&read_size);//将底层的位图全部置0;
11 FD_SET(0,&read_size);//将0号对应的文件描述符置1,
12 //第一个NULL不关注哪些文件写就绪,
13 //第二个NULL不关注哪些文件异常就绪,
14 //第三个NULL,select阻塞形式来执行,
15 int ret = select(1,&read_size,NULL,NULL,NULL);
16 if(ret<0){
17 perror("select");
18 return 1;
19 }
20 //执行read的时候,就说明数据都已经准备 好了,直接进行读就可以了.
21 char buf[1024] = {0};
22 read(0,buf,sizeof(buf)-1);
23 printf("%s\n",buf);
24 }
25 return 0;
26 }
select---网络服务器
1 #include<stdio.h>
2 #include<string.h>
3 #include<stdlib.h>
4 #include<unistd.h>
5 #include<sys/socket.h>
6 #include<netinet/in.h>
7 #include<arpa/inet.h>
8 #include<sys/select.h>
9 typedef struct sockaddr sockaddr;
10 typedef struct sockaddr_in sockaddr_in;
11 typedef struct FdSet{
12 fd_set set;
13 int max_fd;//set中最大的文件描述符
14 }FdSet;
15 int ProcessRequest(int new_sock){
16 //循环从socket中读写数据
17 char buf[1024] = {0};
18 ssize_t read_size = read(new_sock,buf,sizeof(buf)-1);
19 if(read_size<0){
20 perror("read");
21 return -1;
22 }
23 if(read_size==0){
24 printf("read done!\n");
25 return 0;
26 }
27 buf[read_size] = '\0';
28 printf("[client %d] say %s\n",new_sock,buf);
29 write(new_sock,buf,strlen(buf));
30 return 1;
31 }
32 void FdSetInit(FdSet *fdset){
33 FD_ZERO(&fdset->set);
34 fdset->max_fd = 0;
35 }
36 void FdSetAdd(FdSet * fdset,int fd){
37 FD_SET(fd,&fdset->set);//将位图中fd位置1;
38 //维护位图中的最大文件描述符
39 if(fdset->max_fd<fd){
40 fdset->max_fd = fd;
41 }
42 }
43 void FdSetDel(FdSet *fdset,int fd){
44 FD_CLR(fd,&fdset->set);
45 //删除有可能删掉最大的文件描述符,所以要时刻维护
46 int max_fd = -1;
47 int i=0;
48 for(i=0;i<=fdset->max_fd;i++){
49 if(!FD_ISSET(i,&fdset->set)){
50 continue;
51 }
52 if(i>max_fd){
53 max_fd = i;
54 }
55 }
56 fdset->max_fd = max_fd;
57 }
58 int Server_Init(char* ip,short port){
59 //创建监听套接字
60 int _listen_sock = socket(AF_INET,SOCK_STREAM,0);
61 if(_listen_sock<0){
62 perror("socket");
63 return -1;
64 }
65 struct sockaddr_in server;
66 server.sin_family = AF_INET;
67 server.sin_addr.s_addr = inet_addr(ip);
68 server.sin_port = htons(port);
69 //需要对服务器进行绑定,使得客户端能够找到自己
70 int ret = bind(_listen_sock,(struct sockaddr*)&server,sizeof(server));
71 if(ret<0){
72 perror("bind");
73 return -1;
74 }
75
76 if(listen(_listen_sock,5)){
77 perror("listen");
78 return -1;
79 }
80 return _listen_sock;
81 }
82 int main(int argc,char *argv[]){
83 if(argc!=3){
84 printf("usage: ./server [ip] [port]\n");
85 return 1;
86 }
87 //初始化服务器,创建监听套接字,绑定监听
88 int listen_sock = Server_Init(argv[1],atoi(argv[2]));
89 if(listen_sock<0){
90 printf("Server_Init failed\n");
91 return 1;
92 }
93 printf("Server_Init ok!\n");
94 //把listen_sock添加到对应的文件描述符位图中
95 FdSet input_fds;
96 FdSetInit(&input_fds);
97 FdSetAdd(&input_fds,listen_sock);
98 while(1){
99 //这里需注意我们必须保证每次循环调用select的时候,传的参数必须包括listen_sock和new_sock,否则,如果listen_sock丢了
100 //就会导致,后续有任何客户端尝试和服务器建立连接,就都处理不了了.
101 //select监视多个文件描述符的变化
102 //所以这里我们用input_fds来保存select输入时的参数
103 //input_fds :添加内容 :有新的客户端链接进来
104 //output_fds:删除内容:客户端断开连接才删除
105 //用output_fds来保存select输出时的参数.
106 FdSet output_fds = input_fds;
107 int ret = select(output_fds.max_fd+1,&output_fds.set,NULL,NULL,NULL);
108 if(ret<0){
109 perror("select");
110 continue;
111 }
112 if(FD_ISSET(listen_sock,&output_fds.set)){
113 //如果有新的客户端建立连接,那么listen_sock就在此位图中读就绪,此时就可以直接调用accept获取到这个链接
114 sockaddr_in client;
115 socklen_t len = sizeof(client);
116 int new_sock = accept(listen_sock,(sockaddr *)&client,&len);
117 if(new_sock<0){
118 perror("accept");
119 continue;
120 }
121 FdSetAdd(&input_fds,new_sock);
122 printf("[client %d]\n",new_sock);
123 }
124 //有可能是new_sock就绪也有可能是listen_sock就绪,以下处理new_sock就绪的情况
125 else{
126 //进行数据的读写
127 int i=0;
128 for(i=0;i<=output_fds.max_fd;i++){
129 if(!FD_ISSET(i,&output_fds.set)){
130 continue;
131 }
132 //对当前就绪的进行处理
133 int ret = ProcessRequest(i);
134 //当ret==0时说明客户端已经断开连接(即关闭了socket)
135 if(ret ==0){
136 close(i);
137 FdSetDel(&input_fds,i);
138 }
139 }
140
141 }
142 }
143 return 0;
144 }