select使用案例
本节内容:
- 使用fd_set的接口,来实现检测标准输入输出。
- 使用select编写网络服务器。
检测标准输入输出:
stdin.c 代码:
1 #include <stdio.h>
2 #include <stdlib.h>
3 #include <unistd.h>
4 #include <sys/select.h>
5
6 /////////////////////////////////////////
7 // 使用 select 完成对标准输入读状态就绪的等待
8 ////////////////////////////////////////////
9
10 /////////////////////////////////////////////
11 // IO多路复用的本质:
12 // 本来 read 既能完成等,又能完成拷贝
13 // 但是 read 有一个重要的缺陷:一次只能等一个文件描述符,
14 // 如果要想同时等待多个,就得配合多个线程。
15 //
16 // 但是线程如果太多,开销也很大,于是就想办法让一个线程就能
17 // 等待多个文件描述符。
18 //
19 // 于是就有了 IO多路复用
20 // 因为 select 一个函数能等一堆文件描述符。
21 //////////////////////////////////////////////////////////////
23 int main()
24 {
25 while(1)
26 {
27 fd_set readfds;
28 FD_ZERO(&readfds); // 将该文件描述符集清空
29 FD_SET(0, &readfds); // 将该文件描述符集的第0位设置成1
30 // 等待的过程交给 select ,让它去等一堆文件描述符
31 int ret = select(1, &readfds, NULL, NULL, NULL);
32 //最后一个参数设为NULL,表示按阻塞的行为去执行
33 if(ret < 0)
34 {
35 perror("select");
36 return 1;
37 }
38 if(!FD_ISSET(0, &readfds)) //判断第0位是否就绪
39 {
40 printf("readfds error\n");
41 return 1;
42 }
43
44 // 从文件描述符中读数据
45 char buf[1024]={0};
46 // 拷贝的过程交给 read
47 ssize_t read_size = read(0, buf,sizeof(buf)-1);
48 if(read_size < 0)
49 {
50 perror("read");
51 return 1;
52 }
53 if(read_size == 0)
54 {
55 printf("read done!\n");
56 return 0;
57 }
58 buf[read_size]='\0';
59 printf("buf = %s\n",buf);
60 }
61 return 0;
62 }
使用select编写TCP版本的服务器:
特点:
- 实现多个连接
- 只能有一个线程来完成
select_server_xiugai.c:
1 #include <stdio.h>
2 #include <string.h>
3 #include <stdlib.h>
4 #include <unistd.h>
5 #include <sys/select.h>
6 #include <sys/socket.h>
7 #include <netinet/in.h>
8 #include <arpa/inet.h>
9 typedef struct sockaddr sockaddr;
10 typedef struct sockaddr_in sockaddr_in;
11
12 //////////////////////////////////////////////
13 // 基于 fd_set 进行简单的封装。最重要的目的是为了
14 // 能够随时获取到文件描述符集中最大的文件描述符是多少
15 ///////////////////////////////////////////////////
16
17 typedef struct FdSet
18 {
19 fd_set set; // 文件描述符集
20 int max_fd; // set 中最大的文件描述符的数值
21 }FdSet;
22
23 void FdSetInit(FdSet* fds)
24 {
25 fds->max_fd = -1; // 0本身是一个合法的文件描述符
26 FD_ZERO(&fds->set); // 清空文件描述符集
27 }
28
29 void FdSetAdd(FdSet* fds, int fd)
30 {
31 FD_SET(fd, &fds->set); // 将fd加入到文件描述符集set中
32 if(fd > fds->max_fd)
33 {
34 fds->max_fd = fd;
35 }
36 }
37
38 void FdSetDel(FdSet* fds, int fd)
39 {
40 FD_CLR(fd, &fds->set); // 将fd从文件描述符集中删掉
41 // 在文件描述符集中删除一个文件描述符后,就要考虑
42 // 删除的这个文件描述符是否为最大的,这时,就要更新
43 // fds->max_fd 。
44 int max_fd = -1;
45 int i=0;
46 // 从前往后遍历
47 for(; i<= fds->max_fd; ++i)
48 {
49 // 先判断文件描述符集中的哪些位是有效的
50 if(!FD_ISSET(i, &fds->set))
51 {
52 continue;
53 }
54 // 将该有序位上的文件描述符的值和最大的文件描述符的值进行比较,
55 // 选出最大的文件描述符值。
56 if(i > max_fd)
57 {
58 max_fd = i;
59 }
60 }
61 // 此时 max_fd 值就已经是新的文件描述符集中最大的值了。
62 fds->max_fd = max_fd;
63 }
64
65 //////////////////////////////////////////////////////////////////
66 // 以下代码为 select server
67 // 个
68 //////////////////////////////////////////////////////////////
69
70 int ServerInit(const char* ip, short port)
71 {
72 // 创建 socket
73 int listen_sock =socket(AF_INET, SOCK_STREAM, 0);
74 if(listen_sock < 0)
75 {
76 perror("socket");// 文件描述符创建失败:可能是因为文件描述符表满了
77 return -1;
78 }
79
80 // 绑定端口号
81 sockaddr_in addr;
82 addr.sin_family = AF_INET;
83 addr.sin_addr.s_addr = inet_addr(ip);
84 addr.sin_port = htons(port);
85 int ret = bind(listen_sock, (sockaddr*)&addr, sizeof(addr));
86 if(ret < 0)
87 {
88 perror("bind");
89 return -1;
90 }
91
92 // 监听
93 ret = listen(listen_sock, 5);
94 if(ret < 0)
95 {
96 perror("listen");
97 return -1;
98 }
99 return listen_sock;
100 }
101
102 int ProcessRequest(int new_sock)
103 {
104 char buf[1024]={0};
105 ssize_t read_size = read(new_sock, buf, sizeof(buf)-1);
106 if(read_size < 0)
107 {
108 perror("read");
109 return -1;
110 }
111 if(read_size == 0)
112 {
113 printf("[client %d] disconnect\n", new_sock);
114 close(new_sock);
115 return 0;
116 }
117 buf[read_size] = '\0';
118 printf("[client %d] %s\n", new_sock, buf);
119 write(new_sock, buf, strlen(buf));
120 return 1;
121 }
122
123 int main(int argc, char* argv[])
124 {
125 if(argc != 3)
126 {
127 printf("Usage ./select_server.c [ip] [port]\n");
128 return 1;
129 }
130
131 int listen_sock = ServerInit(argv[1], atoi(argv[2]));
132 if(listen_sock < 0)
133 {
134 printf("ServerInit failed!\n");
135 return 1;
136 }
137 printf("ServerInit OK!\n");
138
139 FdSet fds;
140 FdSetInit(&fds);
141 FdSetAdd(&fds, listen_sock);
142 while(1)
143 {
144 ///////////////////////////////////////////////////////
145 // 此时我们要使用 select 完成所有文件描述符就绪的等待
146 // 按照上面代码来实现,其实还有一个致命缺陷。
147 // 例如:调用 select 的时候 fds.set 包含了100个文件描述符,
148 // 说明需要关注100个文件描述符的就绪状态。
149 // 当 select 返回的时候,假设此时只有1个文件描述符就绪,
150 // fds.set 也就只包含1位为1了。
151 //
152 // 此时,如果循环结束,第二次在调用 select ,fds.set 里面
153 // 就只有1个文件描述符了,而我们关注的是100个文件描述符
154 // 的就绪状态,即将99个文件描述符搞没了。
155 ///////////////////////////////////////////////////////
156 FdSet output_fds = fds; // 这里的备份,就是为了解决上面描述的问题
157 int ret = select(fds.max_fd+1, &output_fds.set, NULL, NULL, NULL);
158 if(ret < 0)
159 {
160 perror("select");
161 continue;
162 }
163 if(FD_ISSET(listen_sock, &output_fds.set))
164 {
165 // 就绪的文件描述符是 listen_sock ,此时意味着有
166 // 新的客户端连上了服务器,就应该调用 accept 把连接获取到
167 sockaddr_in peer;
168 socklen_t len = sizeof(peer);
169 int new_sock = accept(listen_sock, (sockaddr*)&peer, &len);
170 if(new_sock < 0)
171 {
172 perror("accept");
173 continue;
174 }
175 FdSetAdd(&fds, new_sock); // 将new_sock 加到文件描述符集中
176 printf("[client %d] connect!\n", new_sock);
177 }
178 else
179 {
180 // i 用于遍历文件描述符集。i 的含义也就是表示一个文件描述符
181 int i = 0;
182 for(; i <= output_fds.max_fd; ++i)
183 {
184 if(!FD_ISSET(i, &output_fds.set))
185 {
186 // 当前的文件描述符不在返回的文件描述符集之中(没就绪)。
187 continue;
188 }
189 // 此时某个 new_sock 读就绪了。
190 // 从当前的 new_sock 中读取一次请求,进行计算,并进行响应。
191 int ret = ProcessRequest(i);
192 // 此处需要判定 ret ,如果是0,表示对方断开连接。
193 // 此时就应该把对应的 socket 从 select 的位图上进行删除。
194 if(ret == 0)
195 {
196 FdSetDel(&fds, i);
197 } // end if(ret == 0)
198 } // end for(; i <= fds.max_fd; ++i)
199 } // end if(FD_ISSET(listen_sock, &fds.set))
200 } // end while(1)
201 return 0;
202 }