select 模型
1)引入
为什么要引入select模型呢 同步阻塞问题我们可以利用多线程 或者把socket改成非阻塞 当我们要接受数据的时候我们要来回查看接受缓冲区有没有数据这样我们就要来回切换用户和内核浪费时间降低效率 所以我们让一个把所有的socket的有无数据都看了呢 这样我没就要引入select
select 模型是Winsock中最常见的I/0模型,核心是利用select函数,实现对I/O的管理。利用select函数来判断某socket上是否有数据可读,或者能否向一个套接字写入数据,防止程序在socket处于阻塞模式中,在一次I/0调用过程中(send,recv,accept),被迫进入锁定状态:可以同时等待多个套接字,当某个或者多个套接字满足可读写条件时通知应用程序调用输入或者输出函数进行读写。
同时防止在套接字处于非阻塞模式中,产生WSAEWOULDBLOCK错误它意味着请求的操作在调用期间没有时间完成。举个例子来说,假如在系统的输入缓冲区中,尚不存在“待决”的数据,那么recv(接收数据)调用就会返回WSAEWOULDBLOCK错误。通常,我们需要重复调用同一个函数,直至获得一个成功返回代码。
select 的函数原型如下:
int select( __in int nfds, __in_out fd_set* readfds, __in_out fd_set* writefds, __in_out fd_set* exceptfds, __in const struct timeval* timeout ); 其中,第一个参数nfds会被忽略。之所以仍然要提供这个参数,只是为了保持与Berkeley套接字兼容。 后面看到有三个 fd_set类型的参数: 一个用于检查可读性(readfds), 一个用于检查可写性(writefds), 一个用于例外数据(exceptfds)。
fd_set 结构的定义如下:
typedef struct fd_set { u_int fd_count; SOCKET fd_array[FD_SETSIZE]; } fd_set;
#define FD_SETSIZE 64
所以 fd_set 结构中最多只能监视64个套接字。 fdset 代表着一系列特定套接字的集合。 其中, readfds 集合包括符合下述任何一个条件的套接字:
● 有数据可以读入。
● 连接已经关闭、重设或中止。
● 假如已调用了listen,而且一个连接正在建立,那么accept函数调用会成功。 writefds 集合包括符合下述任何一个条件的套接字:
● 有数据可以发出。
● 如果已完成了对一个非锁定连接调用的处理,连接就会成功。 exceptfds 集合包括符合下述任何一个条件的套接字:
● 假如已完成了对一个非锁定连接调用的处理,连接尝试就会失败。
● 有带外(Out-of-band,OOB)数据可供读取。
对 timeval 结构的定义如下:
tv_sec 字段以秒为单位指定等待时间;
tv_usec 字段则以毫秒为单位指定等待时间。
1秒 = 1000毫秒 若将超时值设置为(0 , 0),表明 select 会立即返回,出于对性能方面的考虑,应避免这样的设置。
select 函数返回值:
select 成功完成后,会在 fdset 结构中,返回刚好有未完成的 I/O操作的所有套接字句柄的总量。 若超过 timeval 设定的时间,便会返回0。若 select 调用失败,都会返回 SOCKET_ERROR,应该调用 WSAGetLastError 获取错误码! 用 select 对套接字进行监视之前,必须将套接字句柄分配给一个fdset的结构集合,之后再来调用 select,便可知道一个套接字上是否正在发生上述的 I/O 活动。 Winsock 提供了下列宏操作,可用来针对 I/O活动,对 fdset 进行处理与检查:
● FD_CLR(s, *set):从set中删除套接字s。
● FD_ISSET(s, *set):检查s是否set集合的一名成员;如答案是肯定的是,则返回TRUE。
● FD_SET(s, *set):将套接字s加入集合set。
● FD_ZERO( * set):将set初始化成空集合。
使用select模型的步骤
1) 使用FDZERO宏,初始化一个fdset对象;
2) 使用FDSET宏,将套接字句柄加入到fdset集合中;
3) 调用 select 函数,等待其返回……select 完成后,会返回在所有 fdset 集 合中设置的套接字句柄总数,并对每个集合进行相应的更新。
4) 根据 select的返回值和 FDISSET宏,对 fdset 集合进行检查。
5) 知道了每个集合中“待决”的 I/O操作之后,对 I/O进行处理,然后返回步骤 1 ),继续进行 select 处理。
#include "stdafx.h"
#include "UDPNet.h"
UDPNet::UDPNet(IMediator *pMediator)
{
m_sockListen = NULL;
m_hThread = NULL;
m_bFlagQuit = true;
m_pMediator = pMediator;
FD_ZERO(&fdsets);
}
UDPNet::~UDPNet()
{}
bool UDPNet::InitNetWork()
{
WORD wVersionRequested;
WSADATA wsaData;
int err;
/* Use the MAKEWORD(lowbyte, highbyte) macro declared in Windef.h */
wVersionRequested = MAKEWORD(2, 2);
err = WSAStartup(wVersionRequested, &wsaData);
if (err != 0) {
/* Tell the user that we could not find a usable */
/* Winsock DLL. */
return false;
}
if (LOBYTE(wsaData.wVersion) != 2 || HIBYTE(wsaData.wVersion) != 2) {
UnInitNetWork();
return false;
}
//2.雇个人 -- 创建套接字(与外界通信接口)-
m_sockListen = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
m_sockbak = socket(AF_INET,SOCK_DGRAM,IPPROTO_UDP);
if(m_sockListen == INVALID_SOCKET || INVALID_SOCKET == m_sockbak)
{
UnInitNetWork();
return false;
}
FD_SET(m_sockListen,&fdsets);
FD_SET(m_sockbak,&fdsets);
//改变socket 广播属性
BOOL bval = TRUE;
setsockopt(m_sockListen,SOL_SOCKET,SO_BROADCAST,(const char*)&bval,sizeof(bval));
//改变socket 属性
u_long argp = true;
ioctlsocket(m_sockListen,FIONBIO,&argp);
ioctlsocket(m_sockbak,FIONBIO,&argp);
//3.选个地方 -- 绑定--(IP,端口号)
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(_DEF_PORT);
addr.sin_addr.S_un.S_addr =INADDR_ANY /*inet_addr("127.0.0.1")*/;
if(SOCKET_ERROR == bind(m_sockListen,(const sockaddr *)&addr,sizeof(addr)))
{
UnInitNetWork();
return false;
}
sockaddr_in addrbak;
addrbak.sin_family = AF_INET;
addrbak.sin_port = htons(1245);
addrbak.sin_addr.S_un.S_addr =INADDR_ANY /*inet_addr("127.0.0.1")*/;
if(SOCKET_ERROR == bind(m_sockbak,(const sockaddr *)&addrbak,sizeof(addrbak)))
{
UnInitNetWork();
return false;
}
//recvfrom --创建线程
m_hThread = (HANDLE)_beginthreadex(NULL,0,&ThreadProc,this,0,0);
return true;
}
unsigned __stdcall UDPNet::ThreadProc( void * lpvoid)
{
UDPNet *pthis = (UDPNet*)lpvoid;
char szbuf[_DEF_SIZE] = {0};
sockaddr_in addrClient;
int nSize = sizeof(addrClient);
int nRelRecvNum;
fd_set fdReadsets;
timeval tv;
tv.tv_sec = 0;
tv.tv_usec = 100;
while(pthis->m_bFlagQuit)
{
fdReadsets = pthis->fdsets;
select(NULL,&fdReadsets,NULL,NULL,&tv);
int i = 0;
while(i < pthis->fdsets.fd_count)
{
if(FD_ISSET(pthis->fdsets.fd_array[i],&fdReadsets))
{
nRelRecvNum = recvfrom(pthis->fdsets.fd_array[i],szbuf,_DEF_SIZE,0,(sockaddr*)&addrClient,&nSize);
if(nRelRecvNum >0)
{
//交给中介者去处理
pthis->m_pMediator->DealData(szbuf,addrClient.sin_addr.S_un.S_addr);
}
}
i++;
}
}
return 0;
}
//bool UDPNet::SelectSocket()
//{
//
//
//
// if(!FD_ISSET(sock,&fdsets))
// return false;
//
// return true;
//
//}
void UDPNet::UnInitNetWork()
{
m_bFlagQuit = false;
if(m_hThread)
{
if(WAIT_TIMEOUT == WaitForSingleObject(m_hThread,100))
TerminateThread(m_hThread,-1);
CloseHandle(m_hThread);
m_hThread = NULL;
}
WSACleanup();
if(m_sockListen)
{
closesocket(m_sockListen);
m_sockListen = NULL;
}
if(m_sockbak)
{
closesocket(m_sockbak);
m_sockbak = NULL;
}
}
bool UDPNet::SendData(long lSendIp,char *szbuf,int nlen)
{
sockaddr_in addr;
addr.sin_family = AF_INET;
addr.sin_port = htons(_DEF_PORT);
addr.sin_addr.S_un.S_addr = lSendIp;
if(!szbuf || nlen <=0)
return false;
if(sendto(m_sockListen,szbuf,nlen,0,(const sockaddr*)&addr,sizeof(addr))<=0)
return false;
return true;
}