1. 程式人生 > >I/O函式複用 -- select

I/O函式複用 -- select

select系統呼叫的用途是:在一段指定時間內,監聽使用者感興趣的檔案描述符上的可讀、可寫和異常事件。

select API

select函式原型如下:

# include<stdio.h>
int select(int nfds, fd_set* readfds, fd_set* wtitefds, fd_set* exceptfds, struct timeval* timeout);

//nfd引數指定被監聽的檔案描述符的總數。它通常被設定為select監聽的所有檔案描述符中最大的值加一,因為檔案描述符是從0開始的。
//reafds、writefds和exceptfds引數分別指向可讀、可寫和異常等事件對應的檔案描述符結合。
//應用程式呼叫select時,通過這3個引數傳入自己感興趣的檔案描述符。select呼叫返回時,核心將修改它們來通知應用程式哪些檔案描述符已經就緒。
//這3個引數時fd_set結構體指標型別。fd_set結構體僅包含一個數組,該陣列的每個元素的每一位標記一個檔案描述符。
//fd_set能容納的檔案描述符數量由FD_SETSIZE指定,這就限制了select能同時處理的檔案描述符的總量。
//timeout引數用來設定select函式的超時時間。

由於位操作過於繁瑣,我們通常使用下面的一系列巨集來訪問fd_set結構體中的位:

# include<sys/select.h>
FD_ZERO(fd_set* fdset);//清楚fd_set中所有位
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);//測試fdset的位fd是否被設定

檔案描述符就緒的條件

哪些情況下檔案描述符可以被認為是可讀、可寫或出現異常,對於select的使用非常關鍵。在網路程式設計中:

下列情況下socket可讀:

  • socket核心接收緩衝區中的位元組數大於或等於其低水位標記SO_RCVLOWAT。此時我們可以無阻塞地讀該socket,並且讀操作返回的位元組數大於0。
  • socket通訊的對方關閉連線。此時隊該socket的讀操作將返回0。
  • 監聽套接字上有新的連線請求。
  • socket上有未處理的錯誤。此時我們可以使用getsockopt來讀取和清除該錯誤。

下列情況下socket可寫:

  • socke核心傳送快取區中的可用位元組數大於或等於其低水位標記SO_SNDLOWAT。此時我們可以無阻塞地寫該socket,並且寫操作返回地位元組數大於0。
  • socket的寫操作被關閉。對寫操作被關閉的soacket執行寫操作將觸發一個SIGPIPE訊號。
  • socket使用非阻塞connect連線成功或者失敗之後。
  • socket上有未處理的錯誤。此時我們可以使用getsockopt來讀取和清楚該錯誤。

程式碼清單

# include<stdio.h>
# include<stdlib.h>
# include<assert.h>
# include<unistd.h>
# include<arpa/inet.h>
# include<sys/socket.h>
# include<string.h>
# include<arpa/inet.h>
# include<sys/select.h>
# define MAXFD 10

//初始化fds陣列 
void fds_init(int *fds)
{
	int i = 0;
	for(; i < MAXFD; ++i)
	{
		fds[i] = -1;
	}
}
//新增套接字描述符到fds陣列 
void fds_add(int *fds, int fd)
{
	int i = 0;
	for(; i < MAXFD; ++i)
	{
		if(fds[i] == -1)
		{
			fds[i] = fd;
			break;
		}
	}
}
//刪除指定套接字描述符 
void fds_del(int *fds, int fd)
{
	int i = 0;
	for(; i < MAXFD; ++i)
	{
		if(fds[i] == fd)
		{
			fds[i] = -1;
			break;
		}
	}
}
int main()
{
	//建立套接字 
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	assert(sockfd != -1);

	struct sockaddr_in saddr, caddr;
	memset(&saddr, 0, sizeof(saddr));

	saddr.sin_family = AF_INET;
	saddr.sin_port = htons(6000);
	saddr.sin_addr.s_addr = inet_addr("127.0.0.1");
	//命名套接字 
	int res = bind(sockfd, (struct sockaddr*)&saddr,sizeof(saddr));
	assert(res != -1);
	//監聽套接字 
	listen(sockfd, 5);
	//定義陣列 
	int fds[MAXFD];
	fds_init(fds);
	//將定義好的套接字描述符新增到陣列中 
	fds_add(fds, sockfd);
	
	while(1)
	{
		fd_set fdset;
		FD_ZERO(&fdset);
		
		int maxfd = -1;

		int i = 0;
		for(; i < MAXFD; i++)
		{
			if(fds[i] == -1)
			{
				continue;
			}

			FD_SET(fds[i], &fdset);
			if(fds[i] > maxfd)
			{
				maxfd = fds[i];
			}
		}

		struct timeval tv = {5, 0};
		int n = select(maxfd + 1, &fdset, NULL, NULL, &tv);
		if(n == -1)
		{
			printf("select error\n");
			continue;
		}
		else if(n == 0)
		{
			printf("time out\n");
			continue;
		}
		else
		{
			int i = 0;
			for(; i < MAXFD; i++)
			{
				if(fds[i] == -1)
				{
					continue;
				}
				if(FD_ISSET(fds[i], &fdset))
				{
					if(fds[i] == sockfd)
					{
						int len = sizeof(caddr);
						int c = accept(sockfd,(struct sockaddr*)&caddr, &len);
						if(c < 0)
						{
							continue;
						}
						printf("accept c = %d\n", c);
						fds_add(fds, c);
					}
					else
					{
						char buff[128] = {0};
						int num = recv(fds[i], buff, 127, 0);
						if(num <= 0)
						{
							close(fds[i]);
							fds_del(fds,fds[i]);
							printf("one client close\n");
						}
						else
						{
							printf("recv(%d)=%s\n", fds[i], buff);
							send(fds[i], "ok", 2, 0);
						}
					}
				}
			}
		}
	}
}