Linux關於IO複用(select使用)
阿新 • • 發佈:2018-12-28
I/O複用網路應用場合
當客戶處理多個描述字 一個客戶同時處理多個套介面 如果一個tcp伺服器既要處理監聽套介面,又要處理連線套介面 如果一個伺服器既要處理TCP,又要處理UDP
select函式作用
這個函式允許程序指示核心等待多個事件中的任一個發生,並僅在一個或多個事件發生或經過某指定的時間後才喚醒程序
select函式什麼情況下返回
作為一個例子,我們可以呼叫函式select並通知核心僅在下列情況發生時才返回: 集合{1,4,5}中的任何描述子準備好讀 或 集合{2,7}中的任何描述字準備好寫或 集合{1,4}中的任何描述字有異常條件待處理或 已經過了10.2秒 也就是說,通知核心我們對哪些描述字感興趣 (讀、寫或異常條件)以及等待多長時間。
select函式
包含標頭檔案<sys/select.h><sys/socket.h> 功能:提供了即時響應多個套接的讀寫事件 原型: int select(int maxfdp1,fd_set *readset,fd_set *writeset,fd_set *except,const struct timeval *timeout); 引數 maxfdp1:等待最大套接字值加1,(等待套接字的數量) readset:要檢查讀事件的容器 writeset:要檢查寫事件的容器 timeout:超時時間 返回值:返回觸發套件接字的個數
select函式使用
我們還是模擬CS架構,來使用select
下面是伺服器程式碼:
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <sys/types.h> #include <sys/wait.h> #include <sys/socket.h> #include <netinet/in.h> /* for struct sockaddr_in*/ #include <sys/errno.h> #include <signal.h> #include <sys/select.h> //關於IO複用伺服器的socket void error_exit(char *name) { perror(name); exit(-1); } int main(int argc,char *argv[]) { int sockfd=socket(AF_INET,SOCK_STREAM,0); if(sockfd<0) { error_exit("create error"); } //繫結地址(ip和埠號) struct sockaddr_in svraddr; memset(&svraddr,0,sizeof(svraddr)); svraddr.sin_family=AF_INET; svraddr.sin_addr.s_addr=INADDR_ANY; //svraddr.sin_addr.s_addr=inet_addr("127.0.0.1");第二種寫法 svraddr.sin_port=htons(5555); int ret=bind(sockfd,(struct sockaddr*)&svraddr,sizeof(svraddr)); if(ret<0) { error_exit("bind error"); } //設定監聽引數back login 半連線數最大 ret=listen(sockfd,1024); if(ret<0) { error_exit("listen error"); } //新增service到fd fd_set listen_set,temp_set; FD_ZERO(&temp_set); FD_ZERO(&listen_set); FD_SET(sockfd,&listen_set); int maxfd=sockfd; struct sockaddr removeaddr; int addr_len = sizeof(removeaddr); //定義一個屬於去儲存accept fd,FD_SETSIZE函式能夠監聽檔案描述符最大值 int fds[FD_SETSIZE]={0}; int i=0; for(;i<FD_SETSIZE;i++) { fds[i]=-1; } char buf[1024]={0}; while(1) { temp_set=listen_set; int nevent=select(maxfd+1,&temp_set,NULL,NULL,NULL); if(nevent==0) { printf("timeout\n"); continue; } else if(nevent<0) { error_exit("select error"); } if(FD_ISSET(sockfd,&temp_set)) { int fd=accept(sockfd,(struct sockaddr *)&removeaddr,&addr_len); if(fd<0) { error_exit("accept error"); } printf("new connection %d…\n",fd); //把accept新增到監聽集 FD_SET(fd,&listen_set); //數量改變 maxfd=maxfd>fd?maxfd:fd; for(i=0;i<FD_SETSIZE;i++) { if(fds[i]==-1) { fds[i]=fd; break; } } } for(i=0;i<FD_SETSIZE;i++) { if(fds[i]==-1) { continue; } if(FD_ISSET(fds[i],&temp_set)) { memset(buf,0,1024); int rdsize=read(fds[i],buf,1024); if(rdsize<=0) { printf("close %d\n",fds[i]); //對方socket關閉,或者出錯,把accept,fd移出監聽集合 close(fds[i]); FD_CLR(fds[i],&listen_set); fds[i]=-1; } else { printf("read buf =%s,%d\n",buf,fds[i]); } } } } return 0; }
下面是客戶端程式碼,寫入資料hello
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <sys/socket.h>
#include <netinet/in.h> /* for struct sockaddr_in*/
#include <sys/errno.h>
#include <signal.h>
//關於客戶端的socket
void error_exit(char *name)
{
perror(name);
exit(-1);
}
int main(int argc,char *argv[])
{
if(argc<3)
{
printf("run program+ip+port\n");
return-1;
}
int sockfd=socket(AF_INET,SOCK_STREAM,0);
if(sockfd<0)
{
error_exit("create error");
}
//連線伺服器,設定伺服器的地址(ip和埠)
struct sockaddr_in svraddr;
memset(&svraddr,0,sizeof(svraddr));
svraddr.sin_family=AF_INET;
svraddr.sin_addr.s_addr= inet_addr(argv[1]);
svraddr.sin_port=htons(atoi(argv[2]));
int ret =connect(sockfd,(struct sockaddr *)&svraddr,sizeof(svraddr));
if(ret<0)
{
error_exit("connect error");
}
write(sockfd,"hello",strlen("hello"));
sleep(5);
close(sockfd);
return 0;
}
先執行伺服器程式碼:
等待中......
執行客戶端程式碼:
檢視服務端結果如下:
總結:成功讀出來資料,證明我們輸入成功,並且把fd讀出來,解釋一下為何從fd為何從4開始,我們知道系統中有0,1,2分別代表標準輸入、標準輸出和標準錯誤資訊輸出,總共3個,所以我們建立的fd從4開始計算