對於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