基于事件套接字集合的select 模型
select (选择)模型是Winsock 中最常见的I/O 模型。之所以称其为“select 模型”,是由于它的“中心思想”便是利用select 函数,实现对I/O 的管理!最初设计该模型时,主要面向的是某些使用Unix 操作系统的计算机,它们采用的是Berkeley 套接字方案。select 模型已集成到Winsock 1.1 中,它使那些想避免在套接字调用过程中被无辜“锁定”的应用程序,采取一种有序的方式,同时进行对多个套接字的管理。
select 模型本质上是一种分类处理思想,预先声明几个FD_SET(fd_set 结构) 集合,例如ReadSet ,WriteSet,然后调用宏FD_SET(s,&ReadSet) 将关注FD_READ 事件的套接字s 添加到ReadSet 集合,调用宏FD_SET(s,&WriteSet)将关注FD_WRITE 事件的套接字s 添加到WriteSet 集合。其中宏FD_SET(SOCKET s, fd_set set) 将s 添加到set 集合。从根本上说,fd_set 数据类型代表着一系列按关注事件分类的套接字集合。
typedef struct fd_set {
u_int fd_count; /* how many are SET? */
SOCKET fd_array[FD_SETSIZE]; /* an array of SOCKETs */
} fd_set;
FD_ZERO(fd_set *fdset);将指定的文件描述符集清空,在对文件描述符集合进行设置前,必须对其进行初始化,如果不清空,由于在系统分配内存空间后,通常并不作清空处理,所以结果是不可知的。
FD_SET(fd_set *fdset);用于在文件描述符集合中增加一个新的文件描述符。
FD_CLR(fd_set *fdset);用于在文件描述符集合中删除一个文件描述符。
FD_ISSET(int fd,fd_set *fdset);用于测试指定的文件描述符是否在该集合中。
然后再调用select 函数,对声明的集合ReadSet 或WriteSet 进行扫描,其函数原型如下:
WINSOCK_API_LINKAGE int WSAAPI
select (
int nfds ,
fd_set FAR * readfds ,
fd_set FAR * writefds ,
fd_set FAR * exceptfds ,
const struct timeval FAR * timeout );
其中,第一个参数 nfds 会被忽略,一般赋值0 。之所以仍然要提供这个参数,只是为了保持与早期的Berkeley套接字应用程序的兼容。
其他的三个fd_set 参数,一个用于检查可读性(readfds ),一个用于检查可写性(writefds ),另一个用于例外数据(exceptfds )。例如我们只关注FD_READ事件,则select(0,&ReadSet,NULL,NULL,NULL)。一般来说,这三个fd_set 参数至少有一个不为NULL。
调用select 会修改每个fd_set 结构,它扫描注册到集合ReadSet 和WriteSet 中的套接字是否有读写事件发生,若有,则对集合进行更新,即将套接字添加到集合ReadSet 和WriteSet 中。同时,删除那些不存在待决I/O 操作的套接字句柄。select 完成后,会返回在所有fd_set 集合中设置的套接字句柄总数。
然后,我们需要遍历查询之前注册到某个集合中的套接字是否仍为其中一部分。这需要调用FD_ISSET(SOCKET s, fd_set set) 来测试套接字是否属于关注同类事件的套接字集合set 。若是,则对待决的I/O 进行处理。
使用select 模型,可能需要调用ioctlsocket 函数将一个套接字从锁定模式切换为非锁定模式。
typedef struct{
char packetType; //r:request rootdriver ,d:directory f:file D:data
unsigned int length;
char content[2000];
}DATA_PACKET;
----------------------------------------
void CFileTransSvrView::OnStartsvr()
{
// TODO: Add your control notification handler code here
CString strIP;
DWORD dwAddress;
m_IpAddress.GetAddress(dwAddress);
strIP.Format("%d.%d.%d.%d",
HIBYTE(HIWORD(dwAddress)),
LOBYTE(HIWORD(dwAddress)),
HIBYTE(LOWORD(dwAddress)),
LOBYTE(LOWORD(dwAddress))
);
SOCKET sListen=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);
sockaddr_in sin;
sin.sin_family=AF_INET;
sin.sin_port=htons(1032);
sin.sin_addr.S_un.S_addr=inet_addr(strIP);
if(::bind(sListen,(sockaddr*)&sin,sizeof(sin))==SOCKET_ERROR)
{
MessageBox("Bind failure!","Error",MB_OK);
closesocket(sListen);
}
else
{
m_Tips="Server has started,binded succeed.";
UpdateData(false);
}
::listen(sListen,5);
DWORD dwThread;
::CreateThread(NULL,0,ThreadSelect,(LPVOID)sListen,0,&dwThread);
//HWND hWnd = AfxGetApp()->m_pMainWnd->GetSafeHwnd();
//::ShowWindow( hWnd,SW_MINIMIZE );
}
-----------------------------、
DWORD WINAPI CFileTransSvrView::ThreadSelect(LPVOID lpParameter)
{
SOCKET sListen=(SOCKET)lpParameter;
fd_set fdSocket;
FD_ZERO(&fdSocket);
FD_SET(sListen,&fdSocket);
timeval timeout;
timeout.tv_sec=5;
while(true)
{
fd_set fdRead=fdSocket;
int nRet=::select(0,&fdRead,NULL,NULL,NULL);
if(nRet>0)
{
for(int i=0;i<(int)fdSocket.fd_count;i++)
{ if(FD_ISSET(fdSocket.fd_array[i],&fdRead))
{ if(fdSocket.fd_array[i]==sListen)
{
CString strIP;
sockaddr_in addrRemote;
int nAddrLen=sizeof(addrRemote);
SOCKET sNew=::accept(sListen,(sockaddr*)&addrRemote,&nAddrLen);
FD_SET(sNew,&fdSocket);
strIP.Format( "%s",inet_ntoa(addrRemote.sin_addr) );
}
else
{
DATA_PACKET recvPacket;
int nRecev=::recv(fdSocket.fd_array[i],(char*)&recvPacket,
sizeof(recvPacket),0);
if(nRecev>0)
{
//Parameters declared following are for switch
int dchnum;
char* pDrivesString;
CString requestPath,retArrFiles[200];
switch(recvPacket.packetType)
{
case 'r': //Send logical drives after "my computer"
pDrivesString=GetDrivesString();
memcpy(recvPacket.content,pDrivesString,64);
recvPacket.length=64;
::send(fdSocket.fd_array[i],(char*)&recvPacket,
sizeof(recvPacket),0);
break;
case 'd': //Send directory
requestPath=FormatPath(recvPacket.content,recvPacket.length);
pDrivesString=new char[2000];
if( (recvPacket.length=GetDirectoryString(retArrFiles,requestPath,pDrivesString))!=0 )
{
recvPacket.packetType='d';
//memset(recvPacket.content,0,sizeof(recvPacket.content));
memcpy( recvPacket.content,pDrivesString,2000 );
::send(fdSocket.fd_array[i],(char*)&recvPacket,
sizeof(recvPacket),0);
}
break;
case 'f': //Send file size
requestPath=FormatPath(recvPacket.content,recvPacket.length);
recvPacket.length=GetFileLength(requestPath);
recvPacket.packetType='f';
::send(fdSocket.fd_array[i],(char*)&recvPacket,
sizeof(recvPacket),0);
break;
case 'D': //Send file data
dchnum=recvPacket.length;
requestPath=FormatPath(recvPacket.content,recvPacket.length);
SendFile(fdSocket.fd_array[i],dchnum,requestPath);
break;
default:
break;
}
}
else
{
::closesocket(fdSocket.fd_array[i]);
FD_CLR(fdSocket.fd_array[i],&fdSocket);
}
}
}
}
}
else{
// AfxMessageBox("Select failure");
break;
}
}
return 0;
}