在很长的时间内,相比于Windows平台众多的选择,select模式都是linux平台下网络通信的不多选择之一(在epoll未出生之前)。这种模式在面对大量短连接的时候,比OCOT有高的效率,可以避免频繁的上下文切换。
select系统调用是用来让我们的程序监视多个文件描述符(file descriptor)的状态变化的。程序会停在select这里等待,直到遇到以下情况之一select会退出:
(1) 监控的句柄至少有一个发生变化,返回值大于1。
(2) 等待时间已到,返回值等于0。
(3) 收到信号退出,返回值小于0。
select操作的内部会在指定的时间内,不断的检测受控集合中的套接字是否可读,可写或有错误,在select返回时只保留发生状态变化的套接字。通过判断select的返回值,就可以了解该调用是否有文件描述符可读或是select调用退出等信息。
要想让程序运行,光有一个select的调用不是不够的,我们还需要一系列的配套数据结构和算法来帮助将套接字归类,判断是否有指定的套接字等等,结构体fd_set和宏FD_ZERO,FD_CLR,FD_SET,FD_ISSET等就是我们所需要的。通过定义一个fd_set的对象,例如可读集合,有相同需求的套接字都可以放置到这个对象中,可以通过FD_SET这个宏来完成,当然在这之间需要利用FD_ZERO将这个对象初始化。等完成了以上操作后,便会调用select查询是否有套接字可读。等待select完成后,如果有套接字可读,就可以利用FD_ISSET判断是否为监听套接字,转而执行accept操作。如果一个套接字已经关闭,就可以利用FD_CLR将其从fd_set对象中剔除。
服务器端源代码:
1#include <iostream>
2#include <sys/socket.h>
3#include <sys/types.h>
4#include <arpa/inet.h>
5#include <sys/select.h>
6#include <sys/ioctl.h>
7using namespace std;
8
9int main()
10{
11
12 int server_sock = socket(AF_INET,SOCK_STREAM,0);
13 if(server_sock < 0)
14 {
15 cout << "create socket error" << endl;
16 return -1;
17 }
18
19 struct sockaddr_in server_addr;
20 memset(&server_addr,0,sizeof(server_addr));
21 server_addr.sin_family = AF_INET;
22 server_addr.sin_port = htons(5555);
23 server_addr.sin_addr.s_addr = htonl(INADDR_ANY);
24
25 if(bind(server_sock,(struct sockaddr*)&server_addr,sizeof(server_addr)) < 0)
26 {
27 cout << "bind socket error" << endl;
28 close(server_sock);
29 return -1;
30 }
31
32 listen(server_sock,10);
33
34 fd_set read_set;
35 fd_set test_set;
36 FD_ZERO(&read_set);
37 FD_SET(server_sock,&read_set);
38
39 struct timeval tm;
40 tm.tv_sec = 5;
41 tm.tv_usec = 500;
42
43
44 while(true)
45 {
46 int nread = 0;
47 test_set = read_set;
48 int ret = select(FD_SETSIZE,&test_set,NULL,NULL,&tm);
49
50 if(ret < 0)
51 {
52 cout << "select error" << endl;
53 close(server_sock);
54 return -1;
55 }
56 else if(ret == 0)
57 {
58 //cout << "waitout" << endl;
59 continue;
60 }
61 else
62 {
63 for(int fd=0; fd<FD_SETSIZE; ++fd)
64 {
65 if(FD_ISSET(fd,&test_set))
66 {
67 //如果有新的连接到达
68 if(fd == server_sock)
69 {
70 int sock = accept(server_sock,NULL,NULL);
71 FD_SET(sock,&read_set);
72 }
73 else
74 {
75 ioctl(fd,FIONREAD,&nread);
76
77 if(nread == 0) //客户端已经关闭
78 {
79 close(fd);
80 FD_CLR(fd,&read_set);
81 cout << "client has removed " << fd << endl;
82 }
83 else
84 {
85 char buf[128];
86 recv(fd,buf,128,0);
87 cout << buf << endl;
88 send(fd,buf,strlen(buf)+1,0);
89 }
90 }
91
92 }
93
94 }
95
96 }
97
98 }
99}
100