Socket C TCP阻塞 非阻塞總結(jié)_第1頁(yè)
Socket C TCP阻塞 非阻塞總結(jié)_第2頁(yè)
Socket C TCP阻塞 非阻塞總結(jié)_第3頁(yè)
Socket C TCP阻塞 非阻塞總結(jié)_第4頁(yè)
Socket C TCP阻塞 非阻塞總結(jié)_第5頁(yè)
已閱讀5頁(yè),還剩30頁(yè)未讀, 繼續(xù)免費(fèi)閱讀

下載本文檔

版權(quán)說(shuō)明:本文檔由用戶提供并上傳,收益歸屬內(nèi)容提供方,若內(nèi)容存在侵權(quán),請(qǐng)進(jìn)行舉報(bào)或認(rèn)領(lǐng)

文檔簡(jiǎn)介

本文格式為Word版,下載可任意編輯——SocketCTCP阻塞非阻塞總結(jié)

gethostbyaddr、gethostbyname、gethostname、getprotolbyname

getprotolbynumber、getserverbyname、getservbyport。

第三類是Berkekleysocket例程的Windows專用的擴(kuò)展函數(shù),如gethostbyname對(duì)應(yīng)的WSAAsynGetHostByName(其他數(shù)據(jù)庫(kù)函數(shù)除了gethostname都有異步版本),select對(duì)應(yīng)的WSAAsynSelect,判斷是否阻塞的函數(shù)WSAIsBlocking,得到上一次WindsockAPI錯(cuò)誤信息的WSAGetLastError,等等。

從另外一個(gè)角度,這些函數(shù)又可以分為兩類,一是阻塞函數(shù),一是非阻塞函數(shù)。所謂阻塞函數(shù),是指其完成指定的任務(wù)之前不允許程序調(diào)用另一個(gè)函數(shù),在Windows下還會(huì)阻塞本線程消息的發(fā)送。所謂非阻塞函數(shù),是指操作啟動(dòng)之后,假使可以馬上得到結(jié)果就返回結(jié)果,否則返回表示結(jié)果需要等待的錯(cuò)誤信息,不等待任務(wù)完成函數(shù)就返回。

首先,異步函數(shù)是非阻塞函數(shù);

其次,獲取遠(yuǎn)地信息的數(shù)據(jù)庫(kù)函數(shù)是阻塞函數(shù)(因此,WinSock提供了其異步版本);

在Berkeleysocket函數(shù)部分中,不涉及網(wǎng)絡(luò)I/O、本地端工作的函數(shù)是非阻塞函數(shù);

在Berkeleysocket函數(shù)部分中,網(wǎng)絡(luò)I/O的函數(shù)是可阻塞函數(shù),也就是它們可以阻塞執(zhí)行,也可以不阻塞執(zhí)行。這些函數(shù)都使用了一個(gè)socket,假使它們使用的socket是阻塞的,則這些函數(shù)是阻塞函數(shù);假使它們使用的socket是非阻塞的,則這些函數(shù)是非阻塞函數(shù)。

創(chuàng)立一個(gè)socket時(shí),可以指定它是否阻塞。在缺省狀況下,Berkerley的Socket函數(shù)和WinSock都創(chuàng)立“阻塞〞的socket。阻塞socket通過(guò)使用select函數(shù)或者WSAAsynSelect函數(shù)在指定操作下變成非阻塞的。WSAAsyncSelect函數(shù)原型如下。

intWSAAsyncSelect(

SOCKETs,

HWNDhWnd,

u_intwMsg,

longlEvent);

其中,參數(shù)1指定了要操作的socket句柄;參數(shù)2指定了一個(gè)窗口句柄;參數(shù)3指定了一個(gè)消息,參數(shù)4指定了網(wǎng)絡(luò)事件,可以是多個(gè)事件的組合,如:

FD_READ準(zhǔn)備讀

FD_WRITE準(zhǔn)備寫

FD_OOB帶外數(shù)據(jù)到達(dá)

FD_ACCEPT收到連接

FD_CONNECT完成連接

FD_CLOSE關(guān)閉socket。

用OR操作組合這些事件值,如FD_READ|FD_WRITE

WSAAsyncSelect函數(shù)表示對(duì)sockets監(jiān)測(cè)lEvent指定的網(wǎng)絡(luò)事件,假使有事件發(fā)生,則給窗口hWnd發(fā)送消息wMsg。

假定應(yīng)用程序的一個(gè)sockets指定了監(jiān)測(cè)FD_READ事件,則在FD_READ事件上變成非阻塞的。當(dāng)read函數(shù)被調(diào)用時(shí),不管是否讀到數(shù)據(jù)都馬上返回,假使返回一個(gè)錯(cuò)誤信息表示還在等待,則在等待的數(shù)據(jù)到達(dá)后,消息wMsg發(fā)送給窗口hWnd,應(yīng)用程序處理該消息讀取網(wǎng)絡(luò)數(shù)據(jù)。

對(duì)于異步函數(shù)的調(diào)用,以類似的過(guò)程最終得到結(jié)果數(shù)據(jù)。以gethostbyname的異步版本的使用為例進(jìn)行說(shuō)明。該函數(shù)原型如下:

HANDLEWSAAsyncGetHostByName(

HWNDhWnd,

u_intwMsg,

constcharFAR*name,

charFAR*buf,

intbuflen);

在調(diào)用WSAAsyncGetHostByName啟動(dòng)操作時(shí),不僅指定主機(jī)名字name,還指定了一個(gè)窗口句柄hWnd,一個(gè)消息IDwMsg,一個(gè)緩沖區(qū)及其長(zhǎng)度。假使不能馬上得到主機(jī)地址,則返回一個(gè)錯(cuò)誤信息表示還在等待。當(dāng)要的數(shù)據(jù)到達(dá)時(shí),WinSockDLL給窗口hWnd發(fā)送消息wMsg告知得到了主機(jī)地址,窗口過(guò)程從指定的緩沖區(qū)buf得到主機(jī)地址。

使用異步函數(shù)或者非阻塞的socket,主要是為了不阻塞本線程的執(zhí)行。在多進(jìn)程或者多線程的狀況下,可以使用兩個(gè)線程通過(guò)同步手段來(lái)完成異步函數(shù)或者非阻塞函數(shù)的功能。

2.Socket的使用

WinSock以DLL的形式提供,在調(diào)用任何WinSockAPI之前,必需調(diào)用函數(shù)WSAStartup進(jìn)行初始化,最終,調(diào)用函數(shù)WSACleanUp作清理工作。

MFC使用函數(shù)AfxSocketInit包裝了函數(shù)WSAStartup,在WinSock應(yīng)用程序的初始化函數(shù)IninInstance中調(diào)用AfxSocketInit進(jìn)行初始化。程序不必調(diào)用WSACleanUp。

Socket是網(wǎng)絡(luò)通信過(guò)程中端點(diǎn)的抽象表示。Socket在實(shí)現(xiàn)中以句柄的形式被創(chuàng)立,包含了進(jìn)行網(wǎng)絡(luò)通信必需的五種信息:連接使用的協(xié)議,本地主機(jī)的IP地址,本地進(jìn)程的協(xié)議端口,遠(yuǎn)地主機(jī)的IP地址,遠(yuǎn)地進(jìn)程的協(xié)議端口。

要使用socket,首先必需創(chuàng)立一個(gè)socket;然后,按要求配置socket;接著,按要求通過(guò)socket接收和發(fā)送數(shù)據(jù);最終,程序關(guān)閉此socket。

為了創(chuàng)立socket,使用socket函數(shù)得到一個(gè)socket句柄:

socket_handle=socket(protocol_family.Socket_type,protocol);

其中:protocol_family指定socket使用的協(xié)議,取值PF_INET,表示Internet(TCP/IP)協(xié)議族;Socket_type指socket面向連接或者使用數(shù)據(jù)報(bào);第三個(gè)參數(shù)表示使用TCP或者UDP協(xié)議。

當(dāng)一個(gè)socket被創(chuàng)立時(shí),WinSock將為一個(gè)內(nèi)部結(jié)構(gòu)分派內(nèi)存,在此結(jié)構(gòu)中保存此socket的信息,到此,socket連接使用的協(xié)議已經(jīng)確定。

創(chuàng)立了socket之后,配置socket:

對(duì)于面向連接的客戶,WinSock自動(dòng)保存本地IP地址和選擇協(xié)議端口,但是必需使用connect函數(shù)配置遠(yuǎn)地IP地址和遠(yuǎn)地協(xié)議端口:

result=connect(socket_handle,remote_socket_address,address_length)

remote_socket_address是一個(gè)指向特定socket結(jié)構(gòu)的指針,該地址結(jié)構(gòu)為socket保存了地址族、協(xié)議端口、網(wǎng)絡(luò)主機(jī)地址。

面向連接的服務(wù)器則使用bind指定本地信息,使用listen和accept獲取遠(yuǎn)地信息。

使用數(shù)據(jù)報(bào)的客戶或者服務(wù)器使用bind給socket指定本地信息,在發(fā)送或者接收數(shù)據(jù)時(shí)指定遠(yuǎn)地信息。

bind給socket指定一個(gè)本地IP地址和協(xié)議端口,如下:

result=bind(socket_hndle,local_socket_address,address_length)

參數(shù)類型同connect。

函數(shù)listen監(jiān)聽bind指定的端口,假使有遠(yuǎn)地客戶請(qǐng)求連接,使用accept接收請(qǐng)求,創(chuàng)立一個(gè)新的socket,并保存信息。

socket_new=accept(socket_listen,socket_address,address_length)

在socket配置好之后,使用socket發(fā)送或者接收數(shù)據(jù):

面向連接的socket使用send發(fā)送數(shù)據(jù),recv接收數(shù)據(jù);

使用數(shù)據(jù)報(bào)的socket使用sendto發(fā)送數(shù)據(jù),recvfrom接收數(shù)據(jù)。

1.MFC對(duì)WinSocktAPI的封裝

MFC提供了兩個(gè)類CAsyncSocket和CSocket來(lái)封裝WinSockAPI,這給程序員提供了一個(gè)更簡(jiǎn)單的網(wǎng)絡(luò)編程接口。

CAsyncSocket在較低層次上封裝了WinSockAPI,缺省狀況下,使用該類創(chuàng)立的socket是非阻塞的socket,所有操作都會(huì)馬上返回,假使沒有得到結(jié)果,返回WSAEWOULDBLOCK,表示是一個(gè)阻塞操作。

CSocket建立在CAsyncSocket的基礎(chǔ)上,是CAsyncSocket的派生類。也就是缺省狀況下使用該類創(chuàng)立的socket是非阻塞的socket,但是CSocket的網(wǎng)絡(luò)I/O是阻塞的,它在完成任務(wù)之后才返回。CSocket的阻塞不是建立在“阻塞〞socket的基礎(chǔ)上,而是在“非阻塞〞socket上實(shí)現(xiàn)的阻塞操作,在阻塞期間,CSocket實(shí)現(xiàn)了本線程的消息循環(huán),因此,雖然是阻塞操作,但是并不影響消息循環(huán),即用戶依舊可以和程序交互。

1.CAsyncSocket

CAsyncSocket封裝了低層的WinSockAPI,其成員變量m_hSocket保存其對(duì)應(yīng)的socket句柄。使用CAsyncSocket的方法如下:

首先,在堆或者棧中構(gòu)造一個(gè)CAsyncSocket對(duì)象,例如:

CAsyncSocketsock;或者

CAsyncSocket*pSock=newCAsyncSocket;

其次,調(diào)用Create創(chuàng)立socket,例如:

使用缺省參數(shù)創(chuàng)立一個(gè)面向連接的socket

sock.Create()

指定參數(shù)參數(shù)創(chuàng)立一個(gè)使用數(shù)據(jù)報(bào)的socket,本地端口為30

pSocket.Create(30,SOCK_DGRM);

其三,假使是客戶程序,使用Connect連接到遠(yuǎn)地;假使是服務(wù)程序,使用Listen監(jiān)聽遠(yuǎn)地的連接請(qǐng)求。

其四,使用成員函數(shù)進(jìn)行網(wǎng)絡(luò)I/O。

最終,銷毀CAsyncSocket,析構(gòu)函數(shù)調(diào)用Close成員函數(shù)關(guān)閉socket。

下面,分析CAsyncSocket的幾個(gè)函數(shù),從中可以看到它是如何封裝低層的WinSockAPI,簡(jiǎn)化有關(guān)操作的;還可以看到它是如何實(shí)現(xiàn)非阻塞的socket和非阻塞操作。

2.socket對(duì)象的創(chuàng)立和捆綁

(1)Create函數(shù)

首先,探討Create函數(shù),分析socket句柄如何被創(chuàng)立并和CAsyncSocket對(duì)象關(guān)聯(lián)。Create的實(shí)現(xiàn)如下:

BOOLCAsyncSocket::Create(UINTnSocketPort,intnSocketType,

longlEvent,LPCTSTRlpszSocketAddress)

{

if(Socket(nSocketType,lEvent))

{

if(Bind(nSocketPort,lpszSocketAddress))

returnTRUE;

intnResult=GetLastError();

Close();

WSASetLastError(nResult);

}

returnFALSE;

}

其中:

參數(shù)1表示本socket的端口,缺省是0,假使要?jiǎng)?chuàng)立數(shù)據(jù)報(bào)的socket,則必需指定一個(gè)端口號(hào)。

參數(shù)2表示本socket的類型,缺省是SOCK_STREAM,表示面向連接類型。

參數(shù)3是屏蔽位,表示希望對(duì)本socket監(jiān)測(cè)的事件,缺省是FD_READ|FD_WRITE|FD_OOB|FD_ACCEPT|FD_CONNECT|FD_CLOSE。

參數(shù)4表示本socket的IP地址字符串,缺省是NULL。

Create調(diào)用Socket函數(shù)創(chuàng)立一個(gè)socket,并把它捆綁在this所指對(duì)象上,監(jiān)測(cè)指定的網(wǎng)絡(luò)事件。參數(shù)2和3被傳遞給Socket函數(shù),假使希望創(chuàng)立數(shù)據(jù)報(bào)的socket,不要使用缺省參數(shù),指定參數(shù)2是SOCK_DGRM。

假使上一步驟成功,則調(diào)用bind給新的socket分派端口和IP地址。

(2)Socket函數(shù)

接著,分析Socket函數(shù),其實(shí)現(xiàn)如下:

BOOLCAsyncSocket::Socket(intnSocketType,longlEvent,

intnProtocolType,intnAddressFormat)

{

ASSERT(m_hSocket==INVALID_SOCKET);

m_hSocket=socket(nAddressFormat,nSocketType,nProtocolType);

if(m_hSocket!=INVALID_SOCKET)

{

CAsyncSocket::AttachHandle(m_hSocket,this,FALSE);

returnAsyncSelect(lEvent);

}

returnFALSE;

}

其中:

參數(shù)1表示Socket類型,缺省值是SOCK_STREAM。

參數(shù)2表示希望監(jiān)測(cè)的網(wǎng)絡(luò)事件,缺省值同Create,指定了全部事件。

參數(shù)3表示使用的協(xié)議,缺省是0。實(shí)際上,SOCK_STREAM類型的socket使用TCP協(xié)議,SOCK_DGRM的socket則使用UDP協(xié)議。

參數(shù)4表示地址族(地址格式),缺省值是PF_INET(等同于AF_INET)。對(duì)于TCP/IP來(lái)說(shuō),協(xié)議族和地址族是同值的。

在socket沒有被創(chuàng)立之前,成員變量m_hSocket是一個(gè)無(wú)效的socket句柄。Socket函數(shù)把協(xié)議族、socket類型、使用的協(xié)議等信息傳遞給WinSockAPI函數(shù)socket,創(chuàng)立一個(gè)socket。假使創(chuàng)立成功,則把它捆綁在this所指對(duì)象。

(3)捆綁(Attatch)

捆綁過(guò)程類似于其他Windows對(duì)象,將在模塊線程狀態(tài)的WinSock映射中添加一對(duì)新的映射:this所指對(duì)象和新創(chuàng)立的socket對(duì)象的映射。

另外,假使本模塊線程狀態(tài)的“socket窗口〞沒有創(chuàng)立,則創(chuàng)立一個(gè),該窗口

在異步操作時(shí)用來(lái)接收WinSock的通知消息,窗口句柄保存到模塊線程狀態(tài)的m_hSocketWindow變量中。函數(shù)AsyncSelect將指定該窗口為網(wǎng)絡(luò)事件消息的接收窗口。

函數(shù)AttachHandle的實(shí)現(xiàn)在此不列舉了。

(4)指定要監(jiān)測(cè)的網(wǎng)絡(luò)事件

在捆綁完成之后,調(diào)用AsyncSelect指定新創(chuàng)立的socket將監(jiān)測(cè)的網(wǎng)絡(luò)事件。AsyncSelect實(shí)現(xiàn)如下:

BOOLCAsyncSocket::AsyncSelect(longlEvent)

{

ASSERT(m_hSocket!=INVALID_SOCKET);

_AFX_SOCK_THREAD_STATE*pState=_afxSockThreadState;

ASSERT(pState->m_hSocketWindow!=NULL);

returnWSAAsyncSelect(m_hSocket,pState->m_hSocketWindow,

WM_SOCKET_NOTIFY,lEvent)!=SOCKET_ERROR;

}

函數(shù)參數(shù)lEvent表示希望監(jiān)視的網(wǎng)絡(luò)事件。

_afxSockThreadState得到的是當(dāng)前的模塊線程狀態(tài),m_hSocketWindow是本模塊在當(dāng)前線程的“socket窗口〞,指定監(jiān)視m_hSocket的網(wǎng)絡(luò)事件,如指定事件發(fā)生,給窗口m_hSocketWindow發(fā)送WM_SOCKET_NOTIFY消息。

被指定的網(wǎng)絡(luò)事件對(duì)應(yīng)的網(wǎng)絡(luò)I/O將是異步操作,是非阻塞操作。例如:指定FR_READ導(dǎo)致Receive是一個(gè)異步操作,假使不能馬上讀到數(shù)據(jù),則返回一個(gè)錯(cuò)誤WSAEWOULDBLOCK。在數(shù)據(jù)到達(dá)之后,WinSock通知窗口m_hSocketWindow,導(dǎo)致OnReceive被調(diào)用。

指定FR_WRITE導(dǎo)致Send是一個(gè)異步操作,即使數(shù)據(jù)沒有送出也返回一個(gè)錯(cuò)誤WSAEWOULDBLOCK。在數(shù)據(jù)可以發(fā)送之后,WinSock通知窗口m_hSocketWindow,導(dǎo)致OnSend被調(diào)用。

指定FR_CONNECT導(dǎo)致Connect是一個(gè)異步操作,還沒有連接上就返回錯(cuò)誤信息WSAEWOULDBLOCK,在連接完成之后,WinSock通知窗口m_hSocketWindow,導(dǎo)致OnConnect被調(diào)用。

對(duì)于其他網(wǎng)絡(luò)事件,就不一一解釋了。

所以,使用CAsyncSocket時(shí),假使使用Create缺省創(chuàng)立socket,則所有網(wǎng)絡(luò)I/O都是異步操作,進(jìn)行有關(guān)網(wǎng)絡(luò)I/O時(shí)則必需覆蓋以下的相關(guān)函數(shù):

OnAccept、OnClose、OnConnect、OnOutOfBandData、OnReceive、OnSend。

(5)Bind函數(shù)

經(jīng)過(guò)上述過(guò)程,socket創(chuàng)立完畢,下面,調(diào)用Bind函數(shù)給m_hSocket指定本地端口和IP地址。Bind的實(shí)現(xiàn)如下:

BOOLCAsyncSocket::Bind(UINTnSocketPort,LPCTSTRlpszSocketAddress)

{

USES_CONVERSION;

//使用WinSock的地址結(jié)構(gòu)構(gòu)造地址信息

SOCKADDR_INsockAddr;

memset(

//得到地址參數(shù)的值

LPSTRlpszAscii=T2A((LPTSTR)lpszSocketAddress);

//指定是Internet地址類型

sockAddr.sin_family=AF_INET;

if(lpszAscii==NULL)

//沒有指定地址,則自動(dòng)得到一個(gè)本地IP地址

//把32比特的數(shù)據(jù)從主機(jī)字節(jié)序轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序

sockAddr.sin_addr.s_addr=htonl(INADDR_ANY);

else

{

//得到地址

DWORDlResult=inet_addr(lpszAscii);

if(lResult==INADDR_NONE)

{

WSASetLastError(WSAEINVAL);

returnFALSE;

}

sockAddr.sin_addr.s_addr=lResult;

}

//假使端口為0,則WinSock分派一個(gè)端口(1024—5000)

//把16比特的數(shù)據(jù)從主機(jī)字節(jié)序轉(zhuǎn)換成網(wǎng)絡(luò)字節(jié)序

sockAddr.sin_port=htons((u_short)nSocketPort);

//Bind調(diào)用WinSockAPI函數(shù)bind

returnBind((SOCKADDR*)

}

其中:函數(shù)參數(shù)1指定了端口;參數(shù)2指定了一個(gè)包含本地地址的字符串,缺省是NULL。

函數(shù)Bind首先使用結(jié)構(gòu)SOCKADDR_IN構(gòu)造地址信息。該結(jié)構(gòu)的域sin_family表示地址格式(TCP/IP同協(xié)議族),賦值為AF_INET(Internet地址格式);域

sin_port表示端口,假使參數(shù)1為0,則WinSock分派一個(gè)端口給它,范圍在1024和5000之間;域sin_addr是表示地址信息,它是一個(gè)聯(lián)合體,其中s_addr表示如下形式的字符串,“28.56.22.8〞。假使參數(shù)沒有指定地址,則WinSock自動(dòng)地得到本地IP地址(假使有幾個(gè)網(wǎng)卡,則使用其中一個(gè)的地址)。

(6)總結(jié)Create的過(guò)程

首先,調(diào)用socket函數(shù)創(chuàng)立一個(gè)socket;然后把創(chuàng)立的socket對(duì)象映射到CAsyncSocket對(duì)象(捆綁在一起),指定本socket要通知的網(wǎng)絡(luò)事件,并創(chuàng)立一個(gè)“socket窗口〞來(lái)接收網(wǎng)絡(luò)事件消息,最終,指定socket的本地信息。

下一步,是使用成員函數(shù)Connect連接遠(yuǎn)地主機(jī),配置socket的遠(yuǎn)地信息。函數(shù)Connect類似于Bind,把指定的遠(yuǎn)地地址轉(zhuǎn)換成SOCKADDR_IN對(duì)象表示的地址信息(包括網(wǎng)絡(luò)字節(jié)序的轉(zhuǎn)換),然后調(diào)用WinSock函數(shù)Connect連接遠(yuǎn)地主機(jī),配置socket的遠(yuǎn)地端口和遠(yuǎn)地IP地址。

3.異步網(wǎng)絡(luò)事件的處理

當(dāng)網(wǎng)絡(luò)事件發(fā)生時(shí),“socket窗口〞接收WM_SOCKET_NOTIFY消息,消息處理函數(shù)OnSocketNotify被調(diào)用?!皊ocket窗口〞的定義和消息處理是MFC實(shí)現(xiàn)的,這里不作詳細(xì)的探討。

OnSocketNotify回調(diào)CAsyncSocket的成員函數(shù)DoCallBack,DoCallBack調(diào)用事件處理函數(shù),如OnRead、OnWrite等。摘錄DoCallBack的一段代碼如下:

switch(WSAGETSELECTEVENT(lParam))

{

caseFD_READ:

{

DWORDnBytes;

//得到可以一次讀取的字節(jié)數(shù)

pSocket->IOCtl(FIONREAD,

if(nBytes!=0)

pSocket->OnReceive(nErrorCode);

}

break;

caseFD_WRITE:

pSocket->OnSend(nErrorCode);

break;

caseFD_OOB:

pSocket->OnOutOfBandData(nErrorCode);

break;

caseFD_ACCEPT:

pSocket->OnAccept(nErrorCode);

break;

caseFD_CONNECT:

pSocket->OnConnect(nErrorCode);

break;

caseFD_CLOSE:

pSocket->OnClose(nErrorCode);

break;

lParam是WM_SOCKET_NOFITY的消息參數(shù),OnSocketNotify傳遞給函數(shù)DoCallBack,表示通知事件。

函數(shù)IOCtl是CAsyncSocket的成員函數(shù),用來(lái)對(duì)socket的I/O進(jìn)行控制。這里的使用表示本次調(diào)用Receive函數(shù)至多可以讀nBytes個(gè)字節(jié)。

從上面的探討可以看出,從創(chuàng)立socket到網(wǎng)絡(luò)I/O,CAsyncSocket直接封裝了低層的WinSockAPI,簡(jiǎn)化了WinSock編程,實(shí)現(xiàn)了一個(gè)異步操作的界面。假使希望某個(gè)操作是阻塞操作,則在調(diào)用Create時(shí)不要指定該操作對(duì)應(yīng)的網(wǎng)

絡(luò)事件。例如,希望Connect和Send是阻塞操作,在任務(wù)完成之后才返回,則可以使用如下的語(yǔ)句:

pSocket->Create(0,SOCK_STREAM,

FR_WRITE|FR_OOB|FR_ACCEPT|FR_CLOSE);

這樣,在Connect和Send時(shí),假使是用戶界面線程的話,可能阻塞線程消息循環(huán)。所以,最好在工線程中使用阻塞操作。

2.CSocket

假使希望在用戶界面線程中使用阻塞socket,則可以使用CSocket。它在非阻塞socket基礎(chǔ)之上實(shí)現(xiàn)了阻塞操作,在阻塞期間實(shí)現(xiàn)了消息循環(huán)。

對(duì)于CSocket,處理網(wǎng)絡(luò)事件通知的函數(shù)OnAccept、OnClose、OnReceive依舊可以使用,OnConnect、OnSend在CSocket中永遠(yuǎn)不會(huì)被調(diào)用,另外OnOutOfBandData在CSocket中不勉勵(lì)使用。

CSocket對(duì)象在調(diào)用Connect、Send、Accept、Close、Receive等成員函數(shù)后,這些函數(shù)在完成任務(wù)之后(連接被建立、數(shù)據(jù)被發(fā)送、連接請(qǐng)求被接收、socket被關(guān)閉、數(shù)據(jù)被讀?。┲蟛艜?huì)返回。因此,Connect和Send不會(huì)導(dǎo)致OnConnect和OnSend被調(diào)用。假使覆蓋虛擬函數(shù)OnReceive、OnAccept、OnClose,不主動(dòng)調(diào)用Receive、Accept、Close,則在網(wǎng)絡(luò)事件到達(dá)之后導(dǎo)致對(duì)應(yīng)的虛擬函數(shù)被調(diào)用,虛擬函數(shù)的實(shí)現(xiàn)應(yīng)當(dāng)調(diào)用Receive、Accept、Close來(lái)完成操作。下面,就一個(gè)函數(shù)Receive來(lái)考察CSocket如何實(shí)現(xiàn)阻塞操作和消息循環(huán)的。

intCSocket::Receive(void*lpBuf,intnBufLen,intnFlags)

{

//m_pbBlocking是CSocket的成員變量,用來(lái)標(biāo)識(shí)當(dāng)前是否正在進(jìn)行

//阻塞操作。但不能同時(shí)進(jìn)行兩個(gè)阻塞操作。

if(m_pbBlocking!=NULL)

{

WSASetLastError(WSAEINPROGRESS);

returnFALSE;

}

//完成數(shù)據(jù)讀取

intnResult;

while((nResult=CAsyncSocket::Receive(lpBuf,nBufLen,nFlags))

==SOCKET_ERROR)

{

if(GetLastError()==WSAEWOULDBLOCK)

{

//進(jìn)入消息循環(huán),等待網(wǎng)絡(luò)事件FD_READ

if(!PumpMessages(FD_READ))

returnSOCKET_ERROR;

}

else

returnSOCKET_ERROR;

}

returnnResult;

}

其中:

參數(shù)1指定一個(gè)緩沖區(qū)保存讀取的數(shù)據(jù);參數(shù)2指定緩沖區(qū)的大??;參數(shù)3取值MSG_PEEK(數(shù)據(jù)拷貝到緩沖區(qū),但不從輸入隊(duì)列移走),或者M(jìn)SG_OOB(處理帶外數(shù)據(jù)),或者M(jìn)SG_PEEK|MSG_OOB。

Receive函數(shù)首先判斷當(dāng)前CSocket對(duì)象是否正在處理一個(gè)阻塞操作,假使是,則返回錯(cuò)誤WSAEINPROGRESS;否則,開始數(shù)據(jù)讀取的處理。

讀取數(shù)據(jù)時(shí),假使基類CAsyncSocket的Receive讀取到了數(shù)據(jù),則返回;否則,如

果返回一個(gè)錯(cuò)誤,而且錯(cuò)誤號(hào)是WSAEWOULDBLOCK,則表示操作阻塞,于是調(diào)用PumpMessage進(jìn)入消息循環(huán)等待數(shù)據(jù)到達(dá)(網(wǎng)絡(luò)事件FD_READ發(fā)生)。數(shù)據(jù)到達(dá)之后退出消息循環(huán),再次調(diào)用CAsyncSocket的Receive讀取數(shù)據(jù),直到?jīng)]有數(shù)據(jù)可讀為止。

PumpMessages是CSocket的成員函數(shù),它完成以下工作:

(1)設(shè)置m_pbBlocking,表示進(jìn)入阻塞操作。

(2)進(jìn)行消息循環(huán),假使有以下事件發(fā)生則退出消息循環(huán):收到指定定時(shí)器的定時(shí)事件消息WM_TIMER,退出循環(huán),返回TRUE;收到發(fā)送給本socket的消息WM_SOCKET_NOTIFY,網(wǎng)絡(luò)事件FD_CLOSE或者等待的網(wǎng)絡(luò)事件發(fā)生,退出循環(huán),返回TRUE;發(fā)送錯(cuò)誤或者收到WM_QUIT消息,退出循環(huán),返回FALSE;

(3)在消息循環(huán)中,把WM_SOCKET_DEAD消息和發(fā)送給其他socket的通知消息WM_SOCKET_NOFITY放進(jìn)模塊線程狀態(tài)的通知消息列表m_listSocketNotifications,在阻塞操作完成之后處理;對(duì)其他消息,則把它們送給目的窗口的窗口過(guò)程處理。

3.CSocketFile

MFC還提供了一個(gè)網(wǎng)絡(luò)編程模式,可以充分利用CSocket的特性。該模式的基礎(chǔ)是CSocketFile類。使用方法如下:

首先,構(gòu)造一個(gè)CSocket對(duì)象;調(diào)用Create函數(shù)創(chuàng)立一個(gè)socket對(duì)象(SOCK_STREAM類型)。

接著,假使是客戶程序,調(diào)用Connect連接到遠(yuǎn)地主機(jī);假使是服務(wù)器程序,先調(diào)用Listen監(jiān)聽socket端口,收到連接請(qǐng)求后調(diào)用Accept接收請(qǐng)求。

然后,創(chuàng)立一個(gè)和CSocket對(duì)象關(guān)聯(lián)的CSocketFile對(duì)象,創(chuàng)立一個(gè)和CSocketFile對(duì)象關(guān)聯(lián)的CArchive對(duì)象,指定CArchive對(duì)象是用于讀或者寫。假使既要讀又要寫,則創(chuàng)立兩個(gè)CArchive對(duì)象。

創(chuàng)立工作完成之后,使用CArchive對(duì)象在客戶和服務(wù)器之間傳送數(shù)據(jù)

使用完畢,銷毀CArchive對(duì)象、CSocketFile對(duì)象、CSocket對(duì)象。

從前面的章節(jié)可以知道,CArchive可以以一個(gè)CFile對(duì)象為基礎(chǔ),通過(guò)>操作符完成對(duì)文件的二進(jìn)制流的操作。所以可以從CFile派生一個(gè)類,實(shí)現(xiàn)CFile的操作界面(Read和Write)。由于CSocket提供了阻塞操作,所以完全可以像讀寫文件一樣讀寫socket數(shù)據(jù)。

當(dāng)Client端socket與Server端socket相互通信時(shí),兩端均會(huì)觸發(fā)socket事件。這里僅簡(jiǎn)要說(shuō)明兩個(gè)socket事件:

FD_CONNECT:連接事件,尋常Client端socket調(diào)用socketAPI函數(shù)Connect時(shí)所觸發(fā),這個(gè)事件發(fā)生在Client端。?FD_ACCEPT:正在引入的連接事件,尋常Server端socket正在接收來(lái)自Client端socket連接時(shí)觸發(fā),這個(gè)事件發(fā)生在Server端。

?

網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程將socket事件保存至socket的事件隊(duì)列中。此外,網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程還會(huì)向socketwindow發(fā)送消息WM_SOCKET_NOTIFY,通知有socket事件產(chǎn)生,見下文對(duì)socketwindow的詳細(xì)說(shuō)明。調(diào)用CSocket::Create函數(shù)后,socket被創(chuàng)立。socket創(chuàng)立過(guò)程中調(diào)用CAsyncSocket::AttachHandle(SOCKEThSocket,CAsyncSocket*pSocket,BOOLbDead)。該函數(shù)的作用是:

將socket實(shí)例句柄和socket指針添加至當(dāng)前模塊狀態(tài)(注1)的一個(gè)映射表變量m_pmapSocketHandle中。

?在AttachHandle過(guò)程中,會(huì)new一個(gè)CSocketWnd實(shí)例(基于CWnd派生),這里將這個(gè)實(shí)例稱之為socketwindow,進(jìn)一步理解為它是存放所有sockets的消息池(window消息),請(qǐng)細(xì)心查看,這里socket后多加了一個(gè)s,表示創(chuàng)立的多個(gè)socket將共享一個(gè)消息池。?當(dāng)Client端socket與Server端相互通信時(shí),此時(shí)網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程向socketwindow發(fā)送消息WM_SOCKET_NOTIFY,需要說(shuō)明的是CSocketWnd窗口句柄保存在當(dāng)前模塊狀態(tài)的m_hSocketWindow變量中。

?

2、阻塞模式

阻塞模式下Server端與Client端之間的通信處于同步狀態(tài)下。在Server端直接實(shí)例化CSocket類,調(diào)用Create方法創(chuàng)立socket,然后調(diào)用方法Listen開始偵聽,最終用一個(gè)while循環(huán)阻塞調(diào)用Accept函數(shù)用于等待來(lái)自Client端的連接,假使這個(gè)socket在主線程(主程序)中運(yùn)行,這將導(dǎo)致主線程的阻塞。因此,需要?jiǎng)?chuàng)立一個(gè)新的線程以運(yùn)行socket服務(wù)。調(diào)試跟蹤至CSocket::Accept函數(shù)源碼:

while(!Accept(...)){

//Thesocketismarkedasnonblockingandnoconnectionsarepresenttobeaccepted.

if(GetLastError()==WSAEWOULDBLOCK)PumpMessage(FD_ACCEPT);else

returnFALSE;}

它不斷調(diào)用CAsyncSocket::Accept(CSocket派生自CAsyncSocket類)判斷Server端socket的事件隊(duì)列中是否存在正在引入的連接事件-

FD_ACCEPT(見1),換句話說(shuō),就是判斷是否有來(lái)自Client端socket的連接請(qǐng)求。

假使當(dāng)前Server端socket的事件隊(duì)列中存在正在引入的連接事件,Accept返回一個(gè)非0值。否則,Accept返回0,此時(shí)調(diào)用GetLastError將返回錯(cuò)誤代碼WSAEWOULDBLOCK,表示隊(duì)列中無(wú)任何連接請(qǐng)求。注意到在循環(huán)體內(nèi)有一句代碼:

PumpMessage(FD_ACCEPT);

PumpMessage作為一個(gè)消息泵使得socketwindow中的消息能夠維持在活動(dòng)狀態(tài)。實(shí)際跟蹤進(jìn)入PumpMessage中,發(fā)現(xiàn)這個(gè)消息泵與Accept函數(shù)的調(diào)用并不相關(guān),它只是使很少的socketwindow消息(典型的是WM_PAINT窗口重繪消息)處于活動(dòng)狀態(tài),而絕大部分的socketwindow消息被阻塞,被阻塞的消息中含有WM_SOCKET_NOTIFY。

很顯然,假使沒有來(lái)自Client端socket的連接請(qǐng)求,CSocket就會(huì)不斷調(diào)用Accept產(chǎn)生循環(huán)阻塞,直到有來(lái)自Client端socket的連接請(qǐng)求而解除阻塞。

阻塞解除后,表示Server端socket和Client端socket已成功連接,Server端與Client端彼此相互調(diào)用Send和Receive方法開始通信。

3、非阻塞模式

在非阻塞模式下利用socket事件的消息機(jī)制,Server端與Client端之間的通信處于異步狀態(tài)下。

尋常需要從CSocket類派生一個(gè)新類,派生新類的目的是重載socket事件的消息函數(shù),然后在socket事件的消息函數(shù)中添入適合的代碼以完成Client端與Server端之間的通信,與阻塞模式相比,非阻塞模式無(wú)需創(chuàng)立一

個(gè)新線程。

這里將探討當(dāng)Server端socket事件-FD_ACCEPT被觸發(fā)后,該事件的處理函數(shù)OnAccept是如何進(jìn)一步被觸發(fā)的。其它事件的處理函數(shù)如OnConnect,OnReceive等的觸發(fā)方式與此類似。

在1中已提到Client/Server端通信時(shí),Server端socket正在接收來(lái)自Client端socket連接請(qǐng)求,這將會(huì)觸發(fā)FD_ACCEPT事件,同時(shí)Server端的網(wǎng)絡(luò)傳輸服務(wù)進(jìn)程向Server端的socketwindow(CSocketWnd)發(fā)送事件通知消息WM_SOCKET_NOTIFY,通知有FD_ACCEPT事件產(chǎn)生,CsocketWnd在收到事件通知消息后,調(diào)用消息處理函數(shù)OnSocketNotify:

LRESULTCSocketWnd::OnSocketNotify(WPARAMwParam,LPARAMlParam){

CSocket::AuxQueueAdd(WM_SOCKET_NOTIFY,wParam,lParam);CSocket::ProcessAuxQueue();return0L;}

消息參數(shù)wParam是socket的句柄,lParam是socket事件。這里稍作解釋一下,CSocketWnd類是作為CSocket類的友元類,這意味著它可以訪問(wèn)CSocket類中的保護(hù)和私有成員函數(shù)和變量,AuxQueueAdd和

ProcessAuxQueue是CSocket類的靜態(tài)成員函數(shù),假使你對(duì)友元不熟悉,請(qǐng)迅速找本有關(guān)C++書看一下友元的使用方法吧!

ProcessAuxQueue是實(shí)質(zhì)處理socket事件的函數(shù),在該函數(shù)中有這樣一句代碼:

CAsyncSocket*pSocket=CAsyncSocket::LookupHandle((SOCKET)wParam,TRUE);

其實(shí)也就是由socket句柄得到發(fā)送事件通知消息的socket指針pSocket:從m_pmapSocketHandle中查找(見1)!

最終,WSAGETSELECTEVENT(lParam)會(huì)取出事件類型,在一個(gè)簡(jiǎn)單的switch語(yǔ)句中判斷事件類型并調(diào)用事件處理函數(shù)。在這里,事件類型是FD_ACCEPT,當(dāng)然就調(diào)用pSocket->OnAccept!

終止語(yǔ)

Server端socket處于阻塞調(diào)用模式下,它必需在一個(gè)新創(chuàng)立的線程中工作,防止主線程被阻塞。

當(dāng)有多個(gè)Client端socket與Server端socket連接及通信時(shí),

Server端采用阻塞模式就顯得不適合

溫馨提示

  • 1. 本站所有資源如無(wú)特殊說(shuō)明,都需要本地電腦安裝OFFICE2007和PDF閱讀器。圖紙軟件為CAD,CAXA,PROE,UG,SolidWorks等.壓縮文件請(qǐng)下載最新的WinRAR軟件解壓。
  • 2. 本站的文檔不包含任何第三方提供的附件圖紙等,如果需要附件,請(qǐng)聯(lián)系上傳者。文件的所有權(quán)益歸上傳用戶所有。
  • 3. 本站RAR壓縮包中若帶圖紙,網(wǎng)頁(yè)內(nèi)容里面會(huì)有圖紙預(yù)覽,若沒有圖紙預(yù)覽就沒有圖紙。
  • 4. 未經(jīng)權(quán)益所有人同意不得將文件中的內(nèi)容挪作商業(yè)或盈利用途。
  • 5. 人人文庫(kù)網(wǎng)僅提供信息存儲(chǔ)空間,僅對(duì)用戶上傳內(nèi)容的表現(xiàn)方式做保護(hù)處理,對(duì)用戶上傳分享的文檔內(nèi)容本身不做任何修改或編輯,并不能對(duì)任何下載內(nèi)容負(fù)責(zé)。
  • 6. 下載文件中如有侵權(quán)或不適當(dāng)內(nèi)容,請(qǐng)與我們聯(lián)系,我們立即糾正。
  • 7. 本站不保證下載資源的準(zhǔn)確性、安全性和完整性, 同時(shí)也不承擔(dān)用戶因使用這些下載資源對(duì)自己和他人造成任何形式的傷害或損失。

最新文檔

評(píng)論

0/150

提交評(píng)論