1. 程式人生 > >I/O複用——select的實現

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);
}

結果如下: