Socket的阻塞/非阻塞

來源:互聯網
上載者:User

WinSock基本知識

這裡不打算系統地介紹socket或者WinSock的知識。首先介紹WinSock API函數,講解阻塞/非阻塞的概念;然後介紹socket的使用。

 

WinSock API
Socket介面是網路編程(通常是TCP/IP協議,也可以是其他協議)的API。最早的Socket介面是Berkeley介面,在Unxi作業系統中實現。WinSock也是一個基於Socket模型的API,在Microsoft Windows作業系統類中使用。它在Berkeley介面函數的基礎之上,還增加了基於訊息驅動機制的Windows擴充函數。Winscok1.1隻支援TCP/IP網路,WinSock2.0增加了對更多協議的支援。這裡,討論TCP/IP網路上的API。

Socket介面包括三類函數:

第一類是WinSock API包含的Berkeley socket函數。這類函數分兩部分。第一部分是用於網路I/O的函數,如

accept、Closesocket、connect、recv、recvfrom、Select、Send、Sendto

另一部分是不涉及網路I/O、在本地端完成的函數,如

bind、getpeername、getsockname、getsocketopt、htonl、htons、inet_addr、inet_nton

ioctlsocket、listen、ntohl、ntohs、setsocketopt、shutdow、socket等

第二類是檢索有關網域名稱、通訊服務和協議等Internet資訊的資料庫函數,如

gethostbyaddr、gethostbyname、gethostname、getprotolbyname

getprotolbynumber、getserverbyname、getservbyport。

第三類是Berkekley socket常式的Windows專用的擴充函數,如gethostbyname對應的WSAAsynGetHostByName(其他資料庫函數除了gethostname都有非同步版本),select對應的WSAAsynSelect,判斷是否阻塞的函數WSAIsBlocking,得到上一次Windsock API錯誤資訊的WSAGetLastError,等等。

從另外一個角度,這些函數又可以分為兩類,一是阻塞函數,一是非阻塞函數。所謂阻塞函數,是指其完成指定的任務之前不允許程式調用另一個函數,在Windows下還會阻塞本線程訊息的發送。所謂非阻塞函數,是指操作啟動之後,如果可以立即得到結果就返回結果,否則返回表示結果需要等待的錯誤資訊,不等待任務完成函數就返回。

首先,非同步函數是非阻塞函數;

其次,擷取遠地資訊的資料庫函數是阻塞函數(因此,WinSock提供了其非同步版本);

在Berkeley socket函數部分中,不涉及網路I/O、本地端工作的函數是非阻塞函數;

在Berkeley socket函數部分中,網路I/O的函數是可阻塞函數,也就是它們可以阻塞執行,也可以不阻塞執行。這些函數都使用了一個socket,如果它們使用的socket是阻塞的,則這些函數是阻塞函數;如果它們使用的socket是非阻塞的,則這些函數是非阻塞函數。

建立一個socket時,可以指定它是否阻塞。在預設情況下,Berkerley的Socket函數和WinSock都建立“阻塞”的socket。阻塞socket通過使用select函數或者WSAAsynSelect函數在指定操作下變成非阻塞的。WSAAsyncSelect函數原型如下。

int WSAAsyncSelect(

SOCKET s,

HWND hWnd,

u_int wMsg,

long lEvent

);

其中,參數1指定了要操作的socket控制代碼;參數2指定了一個視窗控制代碼;參數3指定了一個訊息,參數4指定了網路事件,可以是多個事件的組合,如:

FD_READ 準備讀

FD_WRITE 準備寫

FD_OOB 帶外資料到達

FD_ACCEPT 收到串連

FD_CONNECT 完成串連

FD_CLOSE 關閉socket。

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

WSAAsyncSelect函數表示對socket s監測lEvent指定的網路事件,如果有事件發生,則給視窗hWnd發送訊息wMsg。

假定應用程式的一個socket s指定了監測FD_READ事件,則在FD_READ事件上變成非阻塞的。當read函數被調用時,不管是否讀到資料都馬上返回,如果返回一個錯誤資訊表示還在等待,則在等待的資料到達後,訊息wMsg發送給視窗hWnd,應用程式處理該訊息讀取網路資料。

對於非同步函數的調用,以類似的過程最終得到結果資料。以gethostbyname的非同步版本的使用為例進行說明。該函數原型如下:

HANDLE WSAAsyncGetHostByName(

HWND hWnd,

u_int wMsg,

const char FAR *name,

char FAR *buf,

int buflen

);

在調用WSAAsyncGetHostByName啟動操作時,不僅指定主機名稱字name,還指定了一個視窗控制代碼hWnd,一個訊息ID wMsg,一個緩衝區及其長度。如果不能立即得到主機地址,則返回一個錯誤資訊表示還在等待。當要的資料到達時,WinSock DLL給視窗hWnd發送訊息wMsg告知得到了主機地址,視窗過程從指定的緩衝區buf得到主機地址。

使用非同步函數或者非阻塞的socket,主要是為了不阻塞本線程的執行。在多進程或者多線程的情況下,可以使用兩個線程通過同步手段來完成非同步函數或者非阻塞函數的功能。

 

Socket的使用
WinSock以DLL的形式提供,在調用任何WinSock API之前,必須調用函數WSAStartup進行初始化,最後,調用函數WSACleanUp作清理工作。

MFC使用函數AfxSocketInit封裝了函數WSAStartup,在WinSock應用程式的初始化函數IninInstance中調用AfxSocketInit進行初始化。程式不必調用WSACleanUp。

 

Socket是網路通訊過程中端點的抽象表示。Socket在實現中以控制代碼的形式被建立,包含了進行網路通訊必須的五種資訊:串連使用的協議,本地主機的IP地址,本地進程的協議連接埠,遠地主機的IP地址,遠地進程的協議連接埠。

要使用socket,首先必須建立一個socket;然後,按要求配置socket;接著,按要求通過socket接收和發送資料;最後,程式關閉此socket。

 

為了建立socket,使用socket函數得到一個socket控制代碼:
socket_handle = socket(protocol_family. Socket_type, protocol);

其中:protocol_family指定socket使用的協議,取值PF_INET,表示Internet(TCP/IP)協議族;Socket_type指socket連線導向或者使用資料報;第三個參數表示使用TCP或者UDP協議。

當一個socket被建立時,WinSock將為一個內部結構分配記憶體,在此結構中儲存此socket的資訊,到此,socket串連使用的協議已經確定。

 

建立了socket之後,配置socket。
對於連線導向的客戶,WinSock自動儲存本地IP地址和選擇協議連接埠,但是必須使用connect函數配置遠地IP地址和遠地協議連接埠:

result = connect(socket_handle, remote_socket_address, address_length)

remote_socket_address是一個指向特定socket結構的指標,該地址結構為socket儲存了地址族、協議連接埠、網路主機地址。

連線導向的伺服器則使用bind指定本地資訊,使用listen和accept擷取遠地資訊。

使用資料報的客戶或者伺服器使用bind給socket指定本地資訊,在發送或者接收資料時指定遠地資訊。

bind給socket指定一個本地IP地址和協議連接埠,如下:

result = bind( socket_hndle, local_socket_address, address_length)

參數類型同connect。

函數listen監聽bind指定的連接埠,如果有遠地客戶請求串連,使用accept接收請求,建立一個新的socket,並儲存資訊。

socket_new = accept(socket_listen, socket_address, address_length)

 

在socket配置好之後,使用socket發送或者接收資料。
連線導向的socket使用send發送資料,recv接收資料;

使用資料報的socket使用sendto發送資料,recvfrom接收資料。

 

MFC對WinSockt API的封裝
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地址。

 

非同步網路事件的處理
當網路事件發生時,“socket視窗”接收WM_SOCKET_NOTIFY訊息,訊息處理函數OnSocketNotify被調用。“socket視窗”的定義和訊息處理是MFC實現的,這裡不作詳細的討論。

OnSocketNotify回調CAsyncSocket的成員函數DoCallBack,DoCallBack呼叫事件處理函數,如OnRead、OnWrite等。摘錄DoCallBack的一段代碼如下:

switch (WSAGETSELECTEVENT(lParam))

{

case FD_READ:

{

DWORD nBytes;

//得到可以一次讀取的位元組數

pSocket->IOCtl(FIONREAD, &nBytes);

if (nBytes != 0)

pSocket->OnReceive(nErrorCode);

}

break;

case FD_WRITE:

pSocket->OnSend(nErrorCode);

break;

case FD_OOB:

pSocket->OnOutOfBandData(nErrorCode);

break;

case FD_ACCEPT:

pSocket->OnAccept(nErrorCode);

break;

case FD_CONNECT:

pSocket->OnConnect(nErrorCode);

break;

case FD_CLOSE:

pSocket->OnClose(nErrorCode);

break;

 

lParam是WM_SOCKET_NOFITY的訊息參數,OnSocketNotify傳遞給函數DoCallBack,表示通知事件。

函數IOCtl是CAsyncSocket的成員函數,用來對socket的I/O進行控制。這裡的使用表示本次調用Receive函數至多可以讀nBytes個位元組。

從上面的討論可以看出,從建立socket到網路I/O,CAsyncSocket直接封裝了低層的WinSock API,簡化了WinSock編程,實現了一個非同步作業的介面。如果希望某個操作是阻塞操作,則在調用Create時不要指定該操作對應的網路事件。例如,希望Connect和Send是阻塞操作,在任務完成之後才返回,則可以使用如下的語句:

pSocket->Create(0, SOCK_STREAM,

FR_WRITE|FR_OOB|FR_ACCEPT|FR_CLOSE);

這樣,在Connect和Send時,如果是使用介面執行緒的話,可能阻塞線程訊息迴圈。所以,最好在工作者線程中使用阻塞操作。

 

CSocket
如果希望在使用介面執行緒中使用阻塞socket,則可以使用CSocket。它在非阻塞socket基礎之上實現了阻塞操作,在阻塞期間實現了訊息迴圈。

對於CSocket,處理網路事件通知的函數OnAccept、OnClose、OnReceive仍然可以使用,OnConnect、OnSend在CSocket中永遠不會被調用,另外OnOutOfBandData在CSocket中不鼓勵使用。

CSocket對象在調用Connect、Send、Accept、Close、Receive等成員函數後,這些函數在完成任務之後(串連被建立、資料被發送、串連請求被接收、socket被關閉、資料被讀取)之後才會返回。因此,Connect和Send不會導致OnConnect和OnSend被調用。如果覆蓋虛擬函數OnReceive、OnAccept、OnClose,不主動調用Receive、Accept、Close,則在網路事件到達之後導致對應的虛擬函數被調用,虛擬函數的實現應該調用Receive、Accept、Close來完成操作。下面,就一個函數Receive來考察CSocket如何?阻塞操作和訊息迴圈的。

int CSocket::Receive(void* lpBuf, int nBufLen, int nFlags)

{

//m_pbBlocking是CSocket的成員變數,用來標識當前是否進行中

//阻塞操作。但不能同時進行兩個阻塞操作。

if (m_pbBlocking != NULL)

{

WSASetLastError(WSAEINPROGRESS);

return FALSE;

}

//完成資料讀取

int nResult;

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

== SOCKET_ERROR)

{

if (GetLastError() == WSAEWOULDBLOCK)

{

//進入訊息迴圈,等待網路事件FD_READ

if (!PumpMessages(FD_READ))

return SOCKET_ERROR;

}

else

return SOCKET_ERROR;

}

return nResult;

}

其中:

參數1指定一個緩衝區儲存讀取的資料;參數2指定緩衝區的大小;參數3取值MSG_PEEK(資料拷貝到緩衝區,但不從輸入隊列移走),或者MSG_OOB(處理帶外資料),或者MSG_PEEK|MSG_OOB。

Receive函數首先判斷當前CSocket對象是否正在處理一個阻塞操作,如果是,則返回錯誤WSAEINPROGRESS;否則,開始資料讀取的處理。

讀取資料時,如果基類CAsyncSocket的Receive讀取到了資料,則返回;否則,如果返回一個錯誤,而且錯誤號碼是WSAEWOULDBLOCK,則表示操作阻塞,於是調用PumpMessage進入訊息迴圈等待資料到達(網路事件FD_READ發生)。資料到達之後退出訊息迴圈,再次調用CAsyncSocket的Receive讀取資料,直到沒有資料可讀為止。

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

(1)設定m_pbBlocking,表示進入阻塞操作。

(2)進行訊息迴圈,如果有以下事件發生則退出訊息迴圈:收到指定定時器的定時事件訊息WM_TIMER,退出迴圈,返回TRUE;收到發送給本socket的訊息WM_SOCKET_NOTIFY,網路事件FD_CLOSE或者等待的網路事件發生,退出迴圈,返回TRUE;發送錯誤或者收到WM_QUIT訊息,退出迴圈,返回FALSE;

(3)在訊息迴圈中,把WM_SOCKET_DEAD訊息和發送給其他socket的通知訊息WM_SOCKET_NOFITY放進模組線程狀態的通知訊息列表m_listSocketNotifications,在阻塞操作完成之後處理;對其他訊息,則把它們送給目的視窗的視窗過程處理。

 

CSocketFile
MFC還提供了一個網路編程模式,可以充分利用CSocket的特性。該模式的基礎是CSocketFile類。使用方法如下:

首先,構造一個CSocket對象;調用Create函數建立一個socket對象(SOCK_STREAM類型)。

接著,如果是客戶程式,調用Connect串連到遠地主機;如果是伺服器程式,先調用Listen監聽socket連接埠,收到串連請求後調用Accept接收請求。

然後,建立一個和CSocket對象關聯的CSocketFile對象,建立一個和CSocketFile對象關聯的CArchive對象,指定CArchive對象是用於讀或者寫。如果既要讀又要寫,則建立兩個CArchive對象。

建立工作完成之後,使用CArchive對象在客戶和伺服器之間傳送資料

使用完畢,銷毀CArchive對象、CSocketFile對象、CSocket對象。

從前面的章節可以知道,CArchive可以以一個CFile對象為基礎,通過<<和>>操作符完成對檔案的二進位流的操作。所以可以從CFile派生一個類,實現CFile的操作介面(Read和Write)。由於CSocket提供了阻塞操作,所以完全可以像讀寫檔案一樣讀寫socket資料。

下面,分析CSocketFile的設計和實現。

 

CSocketFile的建構函式和解構函式的實現
 

建構函式的實現
CSocketFile::CSocketFile(CSocket* pSocket, BOOL bArchiveCompatible)

{

m_pSocket = pSocket;

m_bArchiveCompatible = bArchiveCompatible;

 

#ifdef _DEBUG

ASSERT(m_pSocket != NULL);

ASSERT(m_pSocket->m_hSocket != INVALID_SOCKET);

 

int nType = 0;

int nTypeLen = sizeof(int);

ASSERT(m_pSocket->GetSockOpt(SO_TYPE,&nType,&nTypeLen));

ASSERT(nType == SOCK_STREAM);

#endif // _DEBUG

}

其中:

建構函式的參數1指向關聯的CSocket對象,被儲存在成員變數m_pSocket中;

參數2指定該對象是否和一個CArchive對象關聯(不關聯則獨立使用),被儲存在成員變數bArchiveCompatible中。

Degug部分用於檢測m_pSocket是否是SOCK_STREAM類型。

 

解構函式的實現
CSocketFile::~CSocketFile()

{

}

(2)CSocketFile的讀寫的實現

分析CSocketFile如何用檔案的讀寫實現網路I/O。

 

檔案讀的實現
UINT CSocketFile::Read(void* lpBuf, UINT nCount)

{

ASSERT(m_pSocket != NULL);

 

int nRead;

 

//CSocketFile對象獨立使用

if (!m_bArchiveCompatible)

{

int nLeft = nCount;

PBYTE pBuf = (PBYTE)lpBuf;

 

//讀完nCount個位元組的資料

while(nLeft > 0)

{

//CSocket的Receive,阻塞操作,讀取到資料才繼續

nRead = m_pSocket->Receive(pBuf, nLeft);

if (nRead == SOCKET_ERROR)

{

int nError = m_pSocket->GetLastError();

AfxThrowFileException(CFileException::generic, nError);

ASSERT(FALSE);

}

else if (nRead == 0)

{

return nCount - nLeft;

}

 

nLeft -= nRead;

pBuf += nRead;

}

return nCount - nLeft;

}

 

//和一個CArchive對象關聯使用

//讀取資料,能讀多少是多少

nRead = m_pSocket->Receive(lpBuf, nCount, 0);

if (nRead == SOCKET_ERROR)

{

int nError = m_pSocket->GetLastError();

AfxThrowFileException(CFileException::generic, nError);

ASSERT(FALSE);

}

return nRead;

}

 

檔案寫的實現
void CSocketFile::Write(const void* lpBuf, UINT nCount)

{

ASSERT (m_pSocket!=NULL);

 

//CSocket的函數Send,阻塞操作,發送完畢才繼續

int nWritten = m_pSocket->Send(lpBuf, nCount);

if (nWritten == SOCKET_ERROR)

{

int nError = m_pSocket->GetLastError();

AfxThrowFileException(CFileException::generic, nError);

}

}

從CSockefFile的讀寫實現可以看出,CSocketFile如果獨立使用,在Read操作時可能出現無限等待,因為資料是分多個訊息多次送達的,沒有讀取到指定長度的資料並不表示資料讀取完畢。但是和CArchive配合使用,則僅僅讀取到資料就返回。至於資料是否讀取完畢,可以使用CArchive的IsBufferEmpty函數來判斷。

其他CFile介面,CSocketFile沒有實現。

從CScocketFile的設計和實現來看,CSocketFile是使用CSocket的一個很好的例子,也是使用CFile的一個例子。

本文來自CSDN部落格,轉載請標明出處:http://blog.csdn.net/lspo816/archive/2006/04/14/663554.aspx

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.