socket 通信之select
对于 socket 通信,大家很多都用的单线程通信。同时只能监听一个端口 , 只能响应一个服务, select 的方式可以解决多个 socket 被连接的问题。一次可以分配多个资源,只要一个连接便可以进行通信。在网络已经有很多的 select 的例子。不过很多例子没有真正体现到 select 的精妙之处。此函数主要是对多个文件描述符进行监听,直到某一个或者多个被连接。
首先我们介绍 fd_set 这个结构:
fd_set 可以理解为一个集合,这个集合中存放的是文件描述符 (file descriptor) ,即文件句柄,这可以是我们所说的普通意义的文件,当然 Unix 下任何设备、管道、 FIFO 等都是文件形式,全部包括在内,所以毫无疑问一个 socket 就是一个文件, socket 句柄就是一个文件描述符。 fd_set 集合可以通过一些宏由人为来操作,比如清空集合 FD_ZERO(fd_set *) ,将一个给定的文件描述符加入集合之中 FD_SET(int ,fd_set *) ,将一个给定的文件描述符从集合中删除 FD_CLR(int ,fd_set*) 。
在 select 使用这个结构之前,我们需要调用 FD_SET, 设置对应 socket 的标志位,网络生很多的例子错误就在此,这里必须要绑定多个 socket 才是真正体会了 select 的多 fd 监听。而且不能随意指定,比如 FD_SET(0,XX ),这样的 fd 实际已经被系统占用了。在 select 成功返回后 检查集合中指定的文件描述符是否可以读写 FD_ISSET(int ,fd_set* ) 。进而根据对应的 fd 进行读写操作。不多说,直接粘贴代码:
1 #include <iostream>
2 #include <sys/times.h>
3 #include <sys/types.h>
4 #include <unistd.h>
5 #include <sys/socket.h>
6 #include <sys/select.h>
7 #include <cstdlib>
8 #include <cstdio>
9 #include <cstring>
10 #include <string>
11 #include <signal.h>
12 #include <netinet/in.h>
13 #include <arpa/inet.h>
14 #include <errno.h>
15 using namespace std;
16
17 #define max(a,b) ((a)>(b)?(a):(b))
18
19 static int listen_socket(int port)
20 {
21 sockaddr_in s_in;
22 int s;
23 int yes;
24 if((s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)) == -1)
25 {
26 cout<<"socket error"<<strerror(errno)<<endl;
27 return -1;
28 }
29 yes = 1;
30 if(setsockopt(s, SOL_SOCKET, SO_REUSEADDR, (char*)&yes,sizeof(yes)) == -1)
31 {
32 cout<<"setsockopt error"<<strerror(errno)<<endl;
33 close(s);
34 return -1;
35 }
36
37 memset(&s_in, 0, sizeof(s_in));
38 s_in.sin_port = htons(port);
39 s_in.sin_family = AF_INET;
40 s_in.sin_addr.s_addr = inet_addr("127.0.0.1");
41 if(bind(s,(sockaddr*)&s_in, sizeof(s_in)) == -1)
42 {
43 cout<<"bind error"<<strerror(errno)<<endl;
44 close(s);
45 return -1;
46 }
47 listen(s,10);
48 cout<<"socket -> setsockopt-> bind->listen"<<port<<endl;
49 return s;
50 }
51
52
53
54 int main(int argi, char* args[])
55 {
56 int h, h1;
57 int fd1 = -1, fd2 = -2;
58
59 if(argi != 3)
60 {
61 cout<<"Usage server listen_port1 listen_port2"<<endl;
62 exit(-1);
63
64 }
65
66 int listen_port1 = atoi(args[1]);
67 fd1 = listen_socket(listen_port1);
68 int listen_port2 = atoi(args[2]);
69 fd2 = listen_socket(listen_port2);//这里是建立了两个socket:cyjwdm0503
70 if(fd1==-1 || fd2==-1)
71 {
72 exit(-1);
73 }
74 for(;;)
75 {
76 int r, nfds = 0;
77 fd_set rfd,sfd;
78
79 FD_ZERO(&rfd);
80 FD_ZERO(&sfd);
81 FD_SET(fd1, &rfd);//绑定对应的两个socket
82 FD_SET(fd2,&rfd);
83
84 int maxnum = max(6, fd1);
85 maxnum = max(fd2, maxnum);
86 cout<<"maxnum:"<<maxnum<<"\t"<<h<<endl;
87
88 r = select(maxnum+1, &rfd, &sfd, NULL, NULL);//select的参赛为rfd,最大值为socket+1
89 if( r == -1 && errno == EINTR)
90 {
91 cout<<strerror(errno)<<endl;
92 continue;
93 }
94 if( r == -1)
95 {
96 cout<<"select error"<<strerror(errno)<<"\t"<<errno<<endl;
97 return -1;
98 }
99
100 // for(int index=1; index<=5; index++)
101 {
102 int select_fd = 0;
103 if(FD_ISSET(fd1,&rfd))
104 select_fd = fd1;
105 else if(FD_ISSET(fd2,&rfd))
106 select_fd = fd2;
107 else
108 select_fd = -1;
109 if(select_fd != -1)
110 {
111 cout<<"select retrun fd"<<select_fd<<endl;
112 sockaddr_in accept_addr;
113 socklen_t le = sizeof(accept_addr);//注意在accept时候的sockaddr_in 长度要初始化
114 int acc = accept(ddd, (sockaddr*)&accept_addr, &le);
115
116 cout<<"accept port"<<accept_addr.sin_port<<endl;
117
118 if(-1 == acc)
119 {
120 cout<<"accept error"<<strerror(errno)<<endl;
121 return -1;
122 }
123 char buffer[1024] = "";
124 int ret = recv(acc, buffer, sizeof(buffer),0);
125 if(ret <= 0)
126 {
127 cout<<"client close:"<<strerror(errno)<<"\t"<<errno<<endl;
128 }
129 else
130 {
131 cout<<"buffer from client:"<<buffer<<endl;
132 }
133
134 }
135 else
136 cout<<"select_fd = -1"<<endl;
137 }
138 cout<<"for over"<<endl;
139
140 }
141 }
select 在监听对应的 socket 的 rfd 集合时,如果有对应的客户端链接两个 socket 之中的某一个, select 边能够返回对应的信息,然后调用 FD_ISSET ,获取对应被链接的 socket ,进行 accept ,收发信息。
下面为客户端的示例代码:
1 #include <iostream>
2 #include <sys/socket.h>
3 #include <unistd.h>
4 #include <sys/types.h>
5 #include <netdb.h>
6 #include <cstdio>
7 #include <cstdlib>
8 #include <cstring>
9 #include <string>
10 #include <iostream>
11 #include <netinet/in.h>
12 #include <arpa/inet.h>
13 #include <errno.h>
14
15 using namespace std;
16
17 int main(int argi, char* args[])
18 {
19 if( argi<3)
20 {
21 cout<<args[0]<<"\t"<<"host port msg..."<<endl;
22 return -1;
23 }
24 sockaddr_in s_in;
25 memset(&s_in, 0, sizeof(s_in));
26 s_in.sin_family = AF_INET;
27 s_in.sin_addr.s_addr = inet_addr("127.0.0.1");//(args[1]);
28 s_in.sin_port = htons(atoi(args[1]));
29
30 cout<<"port"<<"\t"<<args[1]<<endl;
31 int s = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
32 if( s == -1)
33 {
34 cout<<"socket error"<<strerror(errno)<<endl;
35 }
36
37 socklen_t length = sizeof(sockaddr_in);
38 if(-1 == connect(s, (sockaddr*)&s_in, length))
39 {
40 cout<<"connect error"<<strerror(errno)<<endl;
41 close(s);
42 return -1;
43 }
44 for(;;)
45 {
46 char buffer[512];
47 48 if(strcmp(args[2],"q")==0)
49 break;
50 ssize_t size = send(s, buffer, strlen(buffer),0);
51 cout<<"socket num:"<<s<<endl;
52 sprintf(buffer,"%s%s",args[2],"www.cnblogs.com/cyjwdm0503");
53 size = send(s,args[2],strlen(args[2]),0);
54 if( -1 == size)
55 {
56 cout<<"write error"<<strerror(errno)<<endl;
57 close(s);
58 return -1;
59 }
60 }
61 close(s);
62
63 }