之前的一篇博文是基於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>