1. 程式人生 > >Linux高效能伺服器程式設計——I/O複用 select

Linux高效能伺服器程式設計——I/O複用 select

提出背景

    不管是多執行緒,或者多程序,以及執行緒池,程序池。他們都存在一定的效率問題。

1.每個程序或執行緒只能為一個客戶端進行服務,知道該客戶端結束。(如果客戶端在同一時間的訪問數量特別大呢?)

2.當客戶端傳送來資料後,分配執行緒或程序為其服務完後,就要等待該客戶端的下一次資料。(如果該客戶端連線成功僅僅發了一次資料呢?)

    他們雖然能夠進行與客戶的互動服務,但是當在規定的時間內,系統(或者池)分配不出足夠的程序或執行緒,達不到及時響應的要求。

I/O複用

    1.決解的問題:一個程序或執行緒能夠同時對多個檔案描述符(socket)進行服務。

    2.伺服器上的程序或執行緒 如何將多個檔案描述符進行同一監聽,當任意檔案描述符上有事件發生,其都能夠及時處理。

 

列舉幾種我們要學習的I/O複用技術:1.select ; 2. poll ; 3. epoll(Linux獨有的)。

select函式

1.函式原型:

#include<sys/select.h>
​int select (int nfds , fd_set *readfds , fd_set *writefds , fd_set *excefds , struct timeval*timeout);​

2.fd_set結構體

#include<typesizes.h>
#define _FD_SETSIZE 1024 //決定了fd_set能容納的檔案描述符數量

#include<sys/select.h>
#define FD_SETSIZE _FD_SETSIZE
typedef long_int _fd_mask;
#undef _NFDBITS
#define _NFDBITS { 8 *(int)sizeof(_fd_mask) ) // 8 * 4 = 32

typedef struct
{
#ifdef _USE_XOPEN
    _fd_mask fds_bits[_FD_SETSIZE / _NFDBITS]; //位陣列 1024 / 32 = 32
#define _FDS_BITS(set) ((set) -> fds_bits)
#else
    fd_mask _fds_bits[_FD_SETSIZE / _NFDBITS]; //位陣列 1024 / 32 = 32
#define _FDS_BITS(set) ((set) -> fds_bits)
#endif
}fd_set;

其實呢,簡而言之就是

typedef struct
{
    int fds_bits[32];
}fd_set;

fd_set結構體僅僅包含一個整型陣列,該陣列的每一個元素的每一位(bit)標記一個檔案描述符。int型別,總共32個元素,那麼總共可以標記32 * 32 = 1024個檔案描述符。

由於位運算操作過於繁瑣,提供了一系列的巨集來訪問fd_set結構體中的位:

標頭檔案: #include<sys/select.h>
清除fdset的所有位 FD_ZERO(fd_set *fdset);
設定fdset的位fd FD_SET(int fd, fd_set *fdset);
清除fdset的位fd FD_CLR( int fd , fd_set *fdset);
測試fdset的位fd是否被設定 int FD_ISSET(int fd , fd_set *fdset);

3.引數介紹

1.nfd: 被監聽檔案描述符總數  + 1
2.readfds: 使用者感興趣的可讀事件的檔案描述符集合

3.writefds:

 

可寫事件的檔案描述符的集合
4.exceptfds: 異常事件的檔案描述符集合
5.timeout 設定超時時間,如果timeout設定為NULL,則select一直阻塞,直到某個檔案就緒。

4.如何將檔案描述符分別設定到readfds, writefds , exceptfds裡?

    通過位運算巨集函式FD_SET()。

5.select返回後,如何知道哪些檔案描述符已經就緒?

    迴圈探測;通過巨集函式int FD_ISSET(int fd , fd_set *fdset);

6.每次呼叫select之前,需要做什麼準備工作?

    將所有的readfds , writefds , exceptfds全部都置為空。

邏輯理念

測試程式碼

#include<sys/socket.h>
#include<arpa/inet.h>
#include<netinet/in.h>
#include<assert.h>
#include<stdio.h>
#include<unistd.h>
#include<string.h>

void Init_fds(int* fds,int len)
{
		int i = 0;
		for(;i<len;++i)
		{
				fds[i] = -1;//都是無效的
		}
}
void Delete_fds(int* fds,int fd,int len)
{
		for(int i = 0;i<len;++i)
		{
				if(fds[i] == fd)
				{
						fds[i] = -1;
						break;
				}

		}
}
void Insert_fds(int* fds,int fd,int len)
{
		int i =0;
		for(;i<len;++i)
		{
				if(fds[i] == -1)
				{
					fds[i] = fd;
					break;
				}
		}
}
int main()
{		//完成TCP連線服務
		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(6000);
		ser.sin_addr.s_addr = inet_addr("127.0.0.1");

		int  res = bind(sockfd, (struct sockaddr*)&ser,sizeof(ser));
		assert(res != -1);

		listen(sockfd,5);
		//fd_set,並將sockfd插入進去
		fd_set readfds;
		int fds[100];//陣列
		Init_fds(fds,100);//初始化全部設定為-1,均為無效的檔案描述符。
		Insert_fds(fds,sockfd,100);//將sockfd新增到fds中

		while(1)
		{
				int maxfd = -1;
				FD_ZERO(&readfds);//清空
				for(int i = 0;i<100;++i)//fds[100]的迴圈探測
				{
						if(fds[i] != -1)//說明陣列有了就緒的事件
						{
								if((fds[i] > maxfd))//對於maxfd的處理
								{
										maxfd = fds[i];
								}
								FD_SET(fds[i],&readfds);//設定fd位。
						}
				}

				int n = select(maxfd + 1,&readfds,NULL,NULL,NULL);//讀取
				if(n<=0)
				{
						printf("select is fail:\n");
						continue;
				}
				//n>0有檔案描述符就緒,如何進行探測呢?
				for(int i = 0;i < 100;++i)//迴圈探測
				{
						if(fds[i] != -1 && FD_ISSET(fds[i],&readfds))//探測fdset的位fd是否被設定
								//就緒
						{
								if(fds[i] == sockfd)//連線請求
								{
										int len = sizeof(cli);
										int c = accept(sockfd,(struct sockaddr*)&cli,(socklen_t*)&len);
										if(c<0)
										{
												printf("accept is fail:\n");
												continue;
										}
										Insert_fds(fds,c,100);//對這個c加入到 readfds中fds[100]
								}
						
								else
								{
									int fd = fds[i];
									char buff[128]={0};//有資料可讀取
									int n= recv(fd,buff,127,0);//只讀取一次
									if(n<=0)
									{
											close(fd);
											Delete_fds(fds,fd,100);
											continue;
									}
									else
									{
											printf("%d: %s\n",fd,buff);
											send(fd,"OK",2,0);
									}
								}
						
						}
				}
		}
		close(sockfd);
}