| | mfc提供兩個類casyncsocket和csocket來封裝winsock api,這給程序員提供一個更簡單的網絡編程接口。
casyncsocket在較低層次上封裝winsock api,缺省情況下,使用該類創建的socket是非阻塞的socket,所有操作都會立即返,如果沒有得到結果,返wsaewouldblock,示是一個阻塞操作。
csocket建立在casyncsocket的基礎上,是casyncsocket的派生類。也就是缺省情況下使用該類創建的socket是非阻塞的socket,但是csocket的網絡i/o是阻塞的,它在完成任務之高才口才奴才蠢才天才人才之才英才多才賢才群才唯才幹才詩才降才五才乏才文才懷才奇才才能才路才力才高才伐才格才望才理才思才郎才哲才智才雄才英才情才分才略才貌才人才子才疏返。csocket的阻塞不是建立在“阻塞”socket的基礎上,而是在“非阻塞”socket上實現的阻塞操作,在阻塞期間,csocket實現本綫程的消息循環,因此,雖然是阻塞操作,但是並不影響消息循環,即用戶仍然可以和程序交互。
casyncsocket
casyncsocket封裝低層的winsock api,其成員變量m_hsocket保存其對應的socket句柄。使用casyncsocket的方法如下:
首先,在堆或者棧中構造一個casyncsocket對象,例如:
casyncsocket sock;或者
casyncsocket *psock = new casyncsocket;
其次,調用create創建socket,例如:
使用缺省參數創建一個面嚮連接的socket
sock.create()
指定參數參數創建一個使用數報的socket,本地端口為30
psocket.create(30, sock_dgrm);
其三,如果是客戶程序,使用connect連接到遠地;如果是服務程序,使用listen監聽遠地的連接請求。
其四,使用成員函數進行網絡i/o。
最,銷毀casyncsocket,析構函數調用close成員函數關閉socket。
下面,分析casyncsocket的幾個函數,從中可以看到它是如何封裝低層的winsock api,簡化有關操作的;還可以看到它是如何實現非阻塞的socket和非阻塞操作。
socket對象的創建和捆綁
(1)create函數
首先,討論create函數,分析socket句柄如何被創建和casyncsocket對象關聯。create的實現如下:
bool casyncsocket::create(uint nsocketport, int nsockettype,
long levent, lpctstr lpszsocketaddress)
{
if (socket(nsockettype, levent))
{
if (bind(nsocketport,lpszsocketaddress))
return true;
int nresult = getlasterror();
close();
wsasetlasterror(nresult);
}
return false;
}
其中:
參數1示本socket的端口,缺省是0,如果要創建數報的socket,則必須指定一個端口號。
參數2示本socket的類型,缺省是sock_stream,示面嚮連接類型。
參數3是屏蔽位,示希望對本socket監測的事件,缺省是fd_read | fd_write | fd_oob | fd_accept | fd_connect | fd_close。
參數4示本socket的ip地址字符串,缺省是null。
create調用socket函數創建一個socket,把它捆綁在this所指對象上,監測指定的網絡事件。參數2和3被傳遞給socket函數,如果希望創建數報的socket,不要使用缺省參數,指定參數2是sock_dgrm。
如果上一步驟成功,則調用bind給新的socket分配端口和ip地址。
(2)socket函數
接着,分析socket函數,其實現如下:
bool casyncsocket::socket(int nsockettype, long levent,
int nprotocoltype, int naddressformat)
{
assert(m_hsocket == invalid_socket);
m_hsocket = socket(naddressformat,nsockettype,nprotocoltype);
if (m_hsocket != invalid_socket)
{
casyncsocket::attachhandle(m_hsocket, this, false);
return async_select_(levent);
}
return false;
}
其中:
參數1示socket類型,缺省值是sock_stream。
參數2示希望監測的網絡事件,缺省值同create,指定全部事件。
參數3示使用的協議,缺省是0。實際上,sock_stream類型的socket使用tcp協議,sock_dgrm的socket則使用udp協議。
參數4示地址族(地址格式),缺省值是pf_inet(等同於af_inet)。對於tcp/ip來說,協議族和地址族是同值的。
在socket沒有被創建之前,成員變量m_hsocket是一個無效的socket句柄。socket函數把協議族、socket類型、使用的協議等信息傳遞給winsock api函數socket,創建一個socket。如果創建成功,則把它捆綁在this所指對象。
(3)捆綁(attatch)
捆綁過程類似於其他windows對象,將在模塊綫程狀態的winsock映射中添加一對新的映射:this所指對象和新創建的socket對象的映射。
另外,如果本模塊綫程狀態的“socket窗口”沒有創建,則創建一個,該窗口在異步操作時用來接收winsock的通知消息,窗口句柄保存到模塊綫程狀態的m_hsocketwindow變量中。函數async_select_將指定該窗口為網絡事件消息的接收窗口。
函數attachhandle的實現在此不列舉。
(4)指定要監測的網絡事件
在捆綁完成之,調用async_select_指定新創建的socket將監測的網絡事件。async_select_實現如下:
bool casyncsocket::async_select_(long levent)
{
assert(m_hsocket != invalid_socket);
_afx_sock_thread_state* pstate = _afxsockthreadstate;
assert(pstate->m_hsocketwindow != null);
return wsaasync_select_(m_hsocket, pstate->m_hsocketwindow,
wm_socket_notify, levent) != socket_error;
}
函數參數levent示希望監視的網絡事件。
_ afxsockthreadstate得到的是當前的模塊綫程狀態,m_ hsocketwindow是本模塊在當前綫程的“socket窗口”,指定監視m_hsocket的網絡事件,如指定事件生,給窗口m_hsocketwindow送wm_socket_notify消息。
被指定的網絡事件對應的網絡i/o將是異步操作,是非阻塞操作。例如:指定fr_read導致receive是一個異步操作,如果不能立即讀到數,則返一個錯誤wsaewouldblock。在數到達之,winsock通知窗口m_hsocketwindow,導致onreceive被調用。
指定fr_write導致send是一個異步操作,即使數沒有送出也返一個錯誤wsaewouldblock。在數可以送之,winsock通知窗口m_hsocketwindow,導致onsend被調用。
指定fr_connect導致connect是一個異步操作,還沒有連接上就返錯誤信息wsaewouldblock,在連接完成之,winsock通知窗口m_hsocketwindow,導致onconnect被調用。
對於其他網絡事件,就不一一解釋。
所以,使用casyncsocket時,如果使用create缺省創建socket,則所有網絡i/o都是異步操作,進行有關網絡i/o時則必須覆蓋以下的相關函數:
onaccept、onclose、onconnect、onoutofbanddata、onreceive、onsend。
(5)bind函數
經過上述過程,socket創建完畢,下面,調用bind函數給m_hsocket指定本地端口和ip地址。bind的實現如下:
bool casyncsocket::bind(uint nsocketport, lpctstr lpszsocketaddress)
{
uses_conversion;
//使用winsock的地址結構構造地址信息
sockaddr_in sockaddr;
memset(&sockaddr,0,sizeof(sockaddr));
//得到地址參數的值
lpstr lpszascii = t2a((lptstr)lpszsocketaddress);
//指定是internet地址類型
sockaddr.sin_family = af_inet;
if (lpszascii == null)
//沒有指定地址,則自動得到一個本地ip地址
//把32比特的數從主機字節序轉換成網絡字節序
sockaddr.sin_addr.s_addr = htonl(inaddr_any);
else
{
//得到地址
dword lresult = inet_addr(lpszascii);
if (lresult == inaddr_none)
{
wsasetlasterror(wsaeinval);
return false;
}
sockaddr.sin_addr.s_addr = lresult;
}
//如果端口為0,則winsock分配一個端口(1024—5000)
//把16比特的數從主機字節序轉換成網絡字節序
sockaddr.sin_port = htons((u_short)nsocketport);
//bind調用winsock api函數bind
return bind((sockaddr*)&sockaddr, sizeof(sockaddr));
}
其中:函數參數1指定端口;參數2指定一個包含本地地址的字符串,缺省是null。
函數bind首先使用結構sockaddr_in構造地址信息。該結構的域sin_family示地址格式(tcp/ip同協議族),賦值為af_inet(internet地址格式);域sin_port示端口,如果參數1為0,則winsock分配一個端口給它,圍在1024和5000之間;域sin_addr是示地址信息,它是一個聯体夫,其中s_addr示如下形式的字符串,“28.56.22.8”。如果參數沒有指定地址,則winsock自動地得到本地ip地址(如果有幾個網卡,則使用其中一個的地址)。
(6)總結create的過程
首先,調用socket函數創建一個socket;然把創建的socket對象映射到casyncsocket對象(捆綁在一起),指定本socket要通知的網絡事件,創建一個“socket窗口”來接收網絡事件消息,最,指定socket的本地信息。
下一步,是使用成員函數connect連接遠地主機,配置socket的遠地信息。函數connect類似於bind,把指定的遠地地址轉換成sockaddr_in對象示的地址信息(包括網絡字節序的轉換),然調用winsock函數connect連接遠地主機,配置socket的遠地端口和遠地ip地址。 | | MFC提供兩個類CAsyncSocket和CSocket來封裝WinSock API,這給程序員提供一個更簡單的網絡編程接口。
CAsyncSocket在較低層次上封裝WinSock API,缺省情況下,使用該類創建的socket是非阻塞的socket,所有操作都會立即返,如果沒有得到結果,返WSAEWOULDBLOCK,示是一個阻塞操作。
CSocket建立在CAsyncSocket的基礎上,是CAsyncSocket的派生類。也就是缺省情況下使用該類創建的socket是非阻塞的socket,但是CSocket的網絡I/O是阻塞的,它在完成任務之高才口才奴才蠢才天才人才之才英才多才賢才群才唯才幹才詩才降才五才乏才文才懷才奇才才能才路才力才高才伐才格才望才理才思才郎才哲才智才雄才英才情才分才略才貌才人才子才疏返。CSocket的阻塞不是建立在“阻塞”socket的基礎上,而是在“非阻塞”socket上實現的阻塞操作,在阻塞期間,CSocket實現本綫程的消息循環,因此,雖然是阻塞操作,但是並不影響消息循環,即用戶仍然可以和程序交互。
CAsyncSocket
CAsyncSocket封裝低層的WinSock API,其成員變量m_hSocket保存其對應的socket句柄。使用CAsyncSocket的方法如下:
首先,在堆或者棧中構造一個CAsyncSocket對象,例如:
CAsyncSocket sock;或者
CAsyncSocket *pSock = new CAsyncSocket;
其次,調用Create創建socket,例如:
使用缺省參數創建一個面嚮連接的socket
sock.Create()
指定參數參數創建一個使用數報的socket,本地端口為30
pSocket.Create(30, SOCK_DGRM);
其三,如果是客戶程序,使用Connect連接到遠地;如果是服務程序,使用Listen監聽遠地的連接請求。
其四,使用成員函數進行網絡I/O。
最,銷毀CAsyncSocket,析構函數調用Close成員函數關閉socket。
下面,分析CAsyncSocket的幾個函數,從中可以看到它是如何封裝低層的WinSock API,簡化有關操作的;還可以看到它是如何實現非阻塞的socket和非阻塞操作。
socket對象的創建和捆綁
(1)Create函數
首先,討論Create函數,分析socket句柄如何被創建和CAsyncSocket對象關聯。Create的實現如下:
BOOL CAsyncSocket::Create(UINT nSocketPort, int nSocketType,
long lEvent, LPCTSTR lpszSocketAddress)
{
if (Socket(nSocketType, lEvent))
{
if (Bind(nSocketPort,lpszSocketAddress))
return TRUE;
int nResult = GetLastError();
Close();
WSASetLastError(nResult);
}
return FALSE;
}
其中:
參數1示本socket的端口,缺省是0,如果要創建數報的socket,則必須指定一個端口號。
參數2示本socket的類型,缺省是SOCK_STREAM,示面嚮連接類型。
參數3是屏蔽位,示希望對本socket監測的事件,缺省是FD_READ | FD_WRITE | FD_OOB | FD_ACCEPT | FD_CONNECT | FD_CLOSE。
參數4示本socket的IP地址字符串,缺省是NULL。
Create調用Socket函數創建一個socket,把它捆綁在this所指對象上,監測指定的網絡事件。參數2和3被傳遞給Socket函數,如果希望創建數報的socket,不要使用缺省參數,指定參數2是SOCK_DGRM。
如果上一步驟成功,則調用bind給新的socket分配端口和IP地址。
(2)Socket函數
接着,分析Socket函數,其實現如下:
BOOL CAsyncSocket::Socket(int nSocketType, long lEvent,
int nProtocolType, int nAddressFormat)
{
ASSERT(m_hSocket == INVALID_SOCKET);
m_hSocket = socket(nAddressFormat,nSocketType,nProtocolType);
if (m_hSocket != INVALID_SOCKET)
{
CAsyncSocket::AttachHandle(m_hSocket, this, FALSE);
return AsyncSelect(lEvent);
}
return FALSE;
}
其中:
參數1示Socket類型,缺省值是SOCK_STREAM。
參數2示希望監測的網絡事件,缺省值同Create,指定全部事件。
參數3示使用的協議,缺省是0。實際上,SOCK_STREAM類型的socket使用TCP協議,SOCK_DGRM的socket則使用UDP協議。
參數4示地址族(地址格式),缺省值是PF_INET(等同於AF_INET)。對於TCP/IP來說,協議族和地址族是同值的。
在socket沒有被創建之前,成員變量m_hSocket是一個無效的socket句柄。Socket函數把協議族、socket類型、使用的協議等信息傳遞給WinSock API函數socket,創建一個socket。如果創建成功,則把它捆綁在this所指對象。
(3)捆綁(Attatch)
捆綁過程類似於其他Windows對象,將在模塊綫程狀態的WinSock映射中添加一對新的映射:this所指對象和新創建的socket對象的映射。
另外,如果本模塊綫程狀態的“socket窗口”沒有創建,則創建一個,該窗口在異步操作時用來接收WinSock的通知消息,窗口句柄保存到模塊綫程狀態的m_hSocketWindow變量中。函數AsyncSelect將指定該窗口為網絡事件消息的接收窗口。
函數AttachHandle的實現在此不列舉。
(4)指定要監測的網絡事件
在捆綁完成之,調用AsyncSelect指定新創建的socket將監測的網絡事件。AsyncSelect實現如下:
BOOL CAsyncSocket::AsyncSelect(long lEvent)
{
ASSERT(m_hSocket != INVALID_SOCKET);
_AFX_SOCK_THREAD_STATE* pState = _afxSockThreadState;
ASSERT(pState->m_hSocketWindow != NULL);
return WSAAsyncSelect(m_hSocket, pState->m_hSocketWindow,
WM_SOCKET_NOTIFY, lEvent) != SOCKET_ERROR;
}
函數參數lEvent示希望監視的網絡事件。
_ afxSockThreadState得到的是當前的模塊綫程狀態,m_ hSocketWindow是本模塊在當前綫程的“socket窗口”,指定監視m_hSocket的網絡事件,如指定事件生,給窗口m_hSocketWindow送WM_SOCKET_NOTIFY消息。
被指定的網絡事件對應的網絡I/O將是異步操作,是非阻塞操作。例如:指定FR_READ導致Receive是一個異步操作,如果不能立即讀到數,則返一個錯誤WSAEWOULDBLOCK。在數到達之,WinSock通知窗口m_hSocketWindow,導致OnReceive被調用。
指定FR_WRITE導致Send是一個異步操作,即使數沒有送出也返一個錯誤WSAEWOULDBLOCK。在數可以送之,WinSock通知窗口m_hSocketWindow,導致OnSend被調用。
指定FR_CONNECT導致Connect是一個異步操作,還沒有連接上就返錯誤信息WSAEWOULDBLOCK,在連接完成之,WinSock通知窗口m_hSocketWindow,導致OnConnect被調用。
對於其他網絡事件,就不一一解釋。
所以,使用CAsyncSocket時,如果使用Create缺省創建socket,則所有網絡I/O都是異步操作,進行有關網絡I/O時則必須覆蓋以下的相關函數:
OnAccept、OnClose、OnConnect、OnOutOfBandData、OnReceive、OnSend。
(5)Bind函數
經過上述過程,socket創建完畢,下面,調用Bind函數給m_hSocket指定本地端口和IP地址。Bind的實現如下:
BOOL CAsyncSocket::Bind(UINT nSocketPort, LPCTSTR lpszSocketAddress)
{
USES_CONVERSION;
//使用WinSock的地址結構構造地址信息
SOCKADDR_IN sockAddr;
memset(&sockAddr,0,sizeof(sockAddr));
//得到地址參數的值
LPSTR lpszAscii = T2A((LPTSTR)lpszSocketAddress);
//指定是Internet地址類型
sockAddr.sin_family = AF_INET;
if (lpszAscii == NULL)
//沒有指定地址,則自動得到一個本地IP地址
//把32比特的數從主機字節序轉換成網絡字節序
sockAddr.sin_addr.s_addr = htonl(INADDR_ANY);
else
{
//得到地址
DWORD lResult = inet_addr(lpszAscii);
if (lResult == INADDR_NONE)
{
WSASetLastError(WSAEINVAL);
return FALSE;
}
sockAddr.sin_addr.s_addr = lResult;
}
//如果端口為0,則WinSock分配一個端口(1024—5000)
//把16比特的數從主機字節序轉換成網絡字節序
sockAddr.sin_port = htons((u_short)nSocketPort);
//Bind調用WinSock API函數bind
return Bind((SOCKADDR*)&sockAddr, sizeof(sockAddr));
}
其中:函數參數1指定端口;參數2指定一個包含本地地址的字符串,缺省是NULL。
函數Bind首先使用結構SOCKADDR_IN構造地址信息。該結構的域sin_family示地址格式(TCP/IP同協議族),賦值為AF_INET(Internet地址格式);域sin_port示端口,如果參數1為0,則WinSock分配一個端口給它,圍在1024和5000之間;域sin_addr是示地址信息,它是一個聯体夫,其中s_addr示如下形式的字符串,“28.56.22.8”。如果參數沒有指定地址,則WinSock自動地得到本地IP地址(如果有幾個網卡,則使用其中一個的地址)。
(6)總結Create的過程
首先,調用socket函數創建一個socket;然把創建的socket對象映射到CAsyncSocket對象(捆綁在一起),指定本socket要通知的網絡事件,創建一個“socket窗口”來接收網絡事件消息,最,指定socket的本地信息。
下一步,是使用成員函數Connect連接遠地主機,配置socket的遠地信息。函數Connect類似於Bind,把指定的遠地地址轉換成SOCKADDR_IN對象示的地址信息(包括網絡字節序的轉換),然調用WinSock函數Connect連接遠地主機,配置socket的遠地端口和遠地IP地址。 |
|
|