對於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 }
雖然這裡是以Linux為示例,Windows和這個也類似的,轉載請注明來源地址http://www.cnblogs.com/cyjwdm0503