I/O複用——select的實現
I/O複用
I/O複用是指一個程序或一個執行緒能夠同時對多對檔案描述符(sockfd)提供服務。那麼伺服器上的程序或執行緒如何對多個檔案描述符統一監聽,當任意一個檔案描述符上有事件發生,其都能及時處理?
有三種方法,今天我著重介紹一下第一種
1.select
2.poll
3.epoll
select
int select(int nfds,fd_set *readfds,fd_set *writefds,fd_set *exceptfds,struct timeval *timeout);
其中,引數的意義是:nfds:最大檔案描述符的值+1
readfds:使用者感興趣的可讀事件的檔案描述符集合
writefds:可讀事件的檔案描述符的集合
exceptfds:異常事件的檔案描述符的集合
timeout:設定超時時間,如果timeout為NULL,則select一直阻塞
返回值:>0 返回就緒檔案描述符的個數
==0 超時
==-1 出錯
流程:1.將檔案描述符設定到readfds,writefds,exceptfds
2.select返回後,知道哪些檔案描述符就緒
其中想將檔案描述符設定到readfds,writefds,exceptfds上面必須呼叫幾個系統呼叫函式:
1.FD_ZERO(fd_set *set) 清空set的所有位
2.FD_SET(int fd,fd_set *set) 設定set的位fd
3.FD_CLR(int fd,fd_set *set) 清除set的位fd
而想知道哪些檔案描述符已經就緒要藉助下面的系統呼叫函式
4.FD_ISSET(int fd,fd_set *set) 測試set的位fd是否被設定
fd_set
fd_set的結構:
typedef struct
{
int fd_bits[32];
}fd_set;
實現
程式碼如下:
伺服器:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<assert.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<sys/select.h>
#include<arpa/inet.h>
#include<netinet/in.h>
void Init(int *fds,int len) //初始化
{
int i=0;
for(;i<len;i++)
{
fds[i]=-1;
}
}
void Insert(int *fds,int fd,int len) //插入
{
int i=0;
for(;i<len;i++)
{
if(fds[i]==-1)
{
fds[i]=fd;
break;
}
}
}
void Delete(int *fds,int fd,int len) //刪除
{
int i=0;
for(;i<len;i++)
{
if(fds[i]!=-1)
{
if(fds[i]==fd)
{
fds[i]=-1;
break;
}
}
}
}
int main()
{
int sockfd=socket(AF_INET,SOCK_STREAM,0);
assert(sockfd!=-1);
struct sockaddr_in ser,cli;
memset(&ser,0,sizeof(ser));
ser.sin_family=AF_INET;
ser.sin_port=htons(8000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
int ret=bind(sockfd,(struct sockaddr*)(&ser),sizeof(ser));
assert(ret!=-1);
listen(sockfd,5);
int fds[100];
Init(fds,100);
Insert(fds,sockfd,100); //將sockfd插入到fds陣列中
fd_set readfds;
while(1)
{
FD_ZERO(&readfds);
int maxfd=-1;
int i=0;
for(;i<100;i++)
{
if(fds[i]!=-1)
{
if(fds[i]>maxfd)
{
maxfd=fds[i]; //找到最大的檔案描述符
}
FD_SET(fds[i],&readfds); //將fds陣列中的檔案描述符設定到readfds上面
}
}
int n=select(maxfd+1,&readfds,NULL,NULL,NULL); //啟動select
if(n<=0)
{
printf("error\n");
continue;
}
i=0;
for(;i<100;i++)
{
if(fds[i]!=-1 && FD_ISSET(fds[i],&readfds)) //探測到就緒檔案描述符後,判斷檔案描述符是sockfd還是不是,如果是sockfd表示有客戶端完成了三次握手,否則是表示客戶端有資料到達
{
if(fds[i]==sockfd)
{
int client=sizeof(cli);
int c=accept(sockfd,(struct sockaddr*)(&cli),&client);
if(c<=0)
{
printf("error");
continue;
}
else
{
Insert(fds,c,100); //將accept所返回的套接字也要插入到fds中
}
}
else
{
char buff[128]={0}; //客戶端有資料到達,讀取資料
int c=fds[i];
int n=recv(c,buff,127,0);
if(n<=0)
{
close(c); //如果其中沒有資料,就關閉檔案描述符
Delete(fds,c,100); //並從fds刪除這個檔案描述符
continue;
}
else
{
printf("%d: %s\n",c,buff); //讀取資料
send(c,"OK",2,0); //傳送ok
}
}
}
}
}
}
客戶端:
#include<stdio.h>
#include<stdlib.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<assert.h>
int main()
{
int sockfd=socket(PF_INET,SOCK_STREAM,0);
assert(sockfd != -1);
struct sockaddr_in ser,cli;
ser.sin_family=AF_INET;
ser.sin_port=htons(8000);
ser.sin_addr.s_addr=inet_addr("127.0.0.1");
connect(sockfd,(struct sockaddr*)(&ser),sizeof(ser));
while(1)
{
printf("please input: ");
fflush(stdout);
char buff[128]={0};
fgets(buff,128,stdin);
buff[strlen(buff)-1] = '\0';
send(sockfd,buff,127,0);
if(strcmp(buff,"end")==0)
{
break;
}
char recvbuff[128]={0};
recv(sockfd,recvbuff,127,0);
printf("%s\n",recvbuff);
}
close(sockfd);
}
結果如下: