I/0模型(select模型)
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;
}