基於select模型的TCP服務器

之前的一篇博文是基於TCP的服務器和客戶機程序,今天在這我要實現一個基於select模型的TCP服務器(僅實現了服務器)。

socket套接字編程提供了很多模型來使服務器高效的接受客戶端的請求,select就是其中之一。

瞭解select模型我們先來看一下的代碼:

int iResult = recv(s, buffer,1024);
這 是用來接收數據的,在默認的阻塞模式下的套接字裏,recv會阻塞在那裏,直到套接字連接上有數據可讀,把數據讀到buffer裏後recv函數纔會返 回,不然就會一直阻塞在那裏。在單線程的程序裏出現這種情況會導致主線程(單線程程序裏只有一個默認的主線程)被阻塞,這樣整個程序被鎖死在這裏,如果永 遠沒數據發送過來,那麼程序就會被永遠鎖死。這個問題可以用多線程解決,但是在有多個套接字連接的情況下,這不是一個好的選擇,擴展性很差。Select 模型就是爲了解決這個問題而出現的。


select函數:

<span style="font-family:Microsoft YaHei;font-size:14px;">int select(int nfds, fd_set  *readfds, fd_set  *writefds, fd_set *exceptfds, const struct timeval *timeout );</span>


參數nfds是需要監視的最⼤的⽂件描述符值+1;
rdset,wrset,exset分別對應於需要檢測的可讀⽂件描述符的集合,可寫⽂件描述符的集 合及異
常⽂件描述符的集合。
struct timeval結構⽤於描述⼀段時間長度,如果在這個時間內,需要監視的描述符沒有事件

發⽣則函數返回,返回值爲0。

<span style="font-family:Microsoft YaHei;font-size:14px;">timeval結構體:
struct  timeval {
        long    tv_sec;        //秒
        long    tv_usec;     //毫秒
};</span>


select返回fd_set中可用的套接字個數。


下⾯的宏提供了處理這三種描述詞組的⽅式:
FD_CLR(inr fd,fd_set* set);⽤來清除描述詞組set中相關fd 的位
FD_ISSET(int fd,fd_set *set);⽤來測試描述詞組set中相關fd 的位是否爲真
FD_SET(int fd,fd_set*set);⽤來設置描述詞組set中相關fd的位
FD_ZERO(fd_set *set);⽤來清除描述詞組set的全部位


函數返回值:
執⾏成功則返回⽂件描述詞狀態已改變的個數。
如果返回0代表在描述詞狀態改變前已超過timeout時間,沒有返回;
當有錯誤發⽣時則返回-1, 錯誤原因存於errno,此時參數readfds,writefds,exceptfds和timeout的值變成不可預測。錯誤值可能爲:
EBADF ⽂件描述詞爲⽆效的或該⽂件已關閉。
EINTR 此調⽤被信號所中斷。
EINVAL 參數n 爲負值。
ENOMEM 核⼼內存不⾜。



根據以上的只是前提,我們可以得到select的特點:

select模型的特點

  (1)可監控的⽂件描述符個數取決與sizeof(fd_set)的值。我這邊服務 器上sizeof(fd_set)=

512,每bit表⽰⼀個⽂件描述符,則我服務器上⽀持的最⼤⽂件描述符是512*8=4096。據說

可調,另有說雖 然可調,但調整上限受於編譯內核時的變量值。本⼈對調整fd_set的⼤⼩不

http://www.cppblog.com /CppExplore/archive/2008/03/21/45061.html 太感興趣,參考中的模

型2(1)可以有效突破select可監控的⽂件描述符上 限。

  (2)將fd加⼊select監控集的同時,還要再使⽤⼀個數據結構array保存放到select監控集

中的fd,⼀是⽤於再select 返回後,array作爲源數據和fd_set進⾏FD_ISSET判斷。⼆是select

返回後會把以前加⼊的但並⽆事件發⽣的fd清空,則每次開始 select前都要重新從array取得fd

逐⼀加⼊(FD_ZERO最先),掃描array的同時取得fd最⼤值maxfd,⽤於select的第⼀個 參

數。

  (3)可見select模型必須在select前循環array(加fd,取maxfd),select返回後循環array

(FD_ISSET判斷是否有時間發⽣)。



以下是select模型的工作過程

1:用FD_ZERO宏來初始化我們感興趣的fd_set。

也就是select函數的第二三四個參數。

2:用FD_SET宏來將套接字句柄分配給相應的fd_set。

如果想要檢查一個套接字是否有數據需要接收,可以用FD_SET宏把套接接字句柄加入可讀性檢查隊列中

3:調用select函數。

如果該套接字沒有數據需要接收,select函數會把該套接字從可讀性檢查隊列中刪除掉,

4:用FD_ISSET對套接字句柄進行檢查。

如果我們所關注的那個套接字句柄仍然在開始分配的那個fd_set裏,那麼說明馬上可以進行相應的IO操 作。比如一個分配給select第一個參數的套接字句柄在select返回後仍然在select第一個參數的fd_set裏,那麼說明當前數據已經來了, 馬上可以讀取成功而不會被阻塞。



基於select模型的TCP服務器的實現:

<span style="font-family:Microsoft YaHei;font-size:14px;">#include<stdio.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/select.h>
#include<netinet/in.h>

#define _MAX_SIZE_ 10
int fd_arr[_MAX_SIZE_];
int max_fd=0;
static void Useage(const char* proc)
{
	printf("Useage:%s,[ip][port]");
	exit(1);
}
static int add_fd_arr(int fd)
{
	//fd add to fd_arr
	int i=0;
	for(;i<_MAX_SIZE_;++i)
	{
		if(fd_arr[i]==-1)
		{
			fd_arr[i]=fd;
			return 0;
		}
	}
	return 1;
}
int select_server(char* ip,char* port)
{
	struct sockaddr_in ser;
	struct sockaddr_in cli;
	fd_set fds;
	int fd=socket(AF_INET,SOCK_STREAM,0);
	if(fd<0)
	{
		perror("socket()");
		exit(2);
	}
	int yes=1;
	setsockopt(fd,SOL_SOCKET,SO_REUSEADDR,&yes,sizeof(int));

	memset(&ser,'\0',sizeof(ser));
	ser.sin_family=AF_INET;
	ser.sin_port=htons(port);
	ser.sin_addr.s_addr=ip;


	if(bind(fd,(struct sockaddr*)&ser,sizeof(ser))<0)
	{
		perror("bind()");
		exit(3);
	}

	//init fd_arr
	int i=0;
	for(;i<_MAX_SIZE_;++i)
	{
		fd_arr[i]=-1;
	}

	add_fd_arr(fd);


	FD_ZERO(&fds);
	if(listen(fd,5)<0)
	{
		perror("listen");
		exit(4);
	}


	while(1)
	{
		//reset fd_arr
		for(i=0;i<_MAX_SIZE_;++i)
		{
			if(fd_arr[i]!=-1)
			{
				FD_SET(fd_arr[i],&fds);
				if(fd_arr[i]>max_fd)
				{
					max_fd=fd_arr[i];
				}
			}
		}
		struct timeval timeout={3,0};
		switch(select(max_fd+1,&fds,NULL,NULL,&timeout))
		{
			case -1:
				{
					perror("select");
					exit(5);
					break;
				}
			case 0:
				{
					printf("select timeout......");
					break;
				}
			default:
				{
					for(i=0;i<_MAX_SIZE_;++i)
					{
						if(i==0&&fd_arr[i]!=-1&&FD_ISSET(fd_arr[i],&fds))
						{
							socklen_t len=sizeof(cli);
							int new_fd=accept(fd,(struct sockaddr*)&cli,&len);
							if(-1!=new_fd)
							{
								printf("get a new link");
								if(1==add_fd_arr(new_fd))
								{
									perror("fd_arr is full,close new_fd\n");
									close(new_fd);
								}
								
							}
							continue;
						}
						if(fd_arr[i]!=-1&&FD_ISSET(fd_arr[i],&fds))
						{
							char buf[1024];
							memset(buf,'\0',sizeof(buf));
							ssize_t size=recv(fd_arr[i],buf,sizeof(buf)-1,0);
							if(size==0||size==-1)
							{
								printf("remote client close,size is%d\n",size);
								int j=0;
								for(;j<_MAX_SIZE_;++j)
								{
									if(fd_arr[j]==fd_arr[i])
									{
										fd_arr[j]=-1;
										break;
									}
								}
								close(fd_arr[i]);
								FD_CLR(fd_arr[i],&fds);
							}else
							{
								printf("fd:%d,msg:%s",fd_arr[i],buf);
							}
						}
					}
				}
				break;
		}

	}
}
int main(int argc,char* argv[])
{
	if(argc!=3)
	{
		Useage(argv[0]);
	}
	select_server(argv[1],argv[2]);
	return 0;
}
</span>



發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章