国产一级a片免费看高清,亚洲熟女中文字幕在线视频,黄三级高清在线播放,免费黄色视频在线看

打開APP
userphoto
未登錄

開通VIP,暢享免費電子書等14項超值服

開通VIP
第十四章 SOCKET類的設計和實現(xiàn)
  1. SOCKET類的設計和實現(xiàn)

     

    1. WinSock基本知識

       

      這里不打算系統(tǒng)地介紹socket或者WinSock的知識。首先介紹WinSock API函數(shù),講解阻塞/非阻塞的概念;然后介紹socket的使用。

      1. WinSock API

         

        Socket接口是網(wǎng)絡編程(通常是TCP/IP協(xié)議,也可以是其他協(xié)議)的API。最早的Socket接口是Berkeley接口,在Unxi操作系統(tǒng)中實現(xiàn)。WinSock也是一個基于Socket模型的API,在Microsoft Windows操作系統(tǒng)類中使用。它在Berkeley接口函數(shù)的基礎之上,還增加了基于消息驅(qū)動機制的Windows擴展函數(shù)。Winscok1.1只支持TCP/IP網(wǎng)絡,WinSock2.0增加了對更多協(xié)議的支持。這里,討論TCP/IP網(wǎng)絡上的API。

        Socket接口包括三類函數(shù):

        第一類是WinSock API包含的Berkeley socket函數(shù)。這類函數(shù)分兩部分。第一部分是用于網(wǎng)絡I/O的函數(shù),如

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

        另一部分是不涉及網(wǎng)絡I/O、在本地端完成的函數(shù),如

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

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

        第二類是檢索有關域名、通信服務和協(xié)議等Internet信息的數(shù)據(jù)庫函數(shù),如

        gethostbyaddr、gethostbyname、gethostname、getprotolbyname

        getprotolbynumber、getserverbyname、getservbyport。

        第三類是Berkekley socket例程的Windows專用的擴展函數(shù),如gethostbyname對應的WSAAsynGetHostByName(其他數(shù)據(jù)庫函數(shù)除了gethostname都有異步版本),select對應的WSAAsynSelect,判斷是否阻塞的函數(shù)WSAIsBlocking,得到上一次Windsock API錯誤信息的WSAGetLastError,等等。

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

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

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

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

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

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

        int WSAAsyncSelect(

        SOCKET s,

        HWND hWnd,

        u_int wMsg,

        long lEvent

        );

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

        FD_READ 準備讀

        FD_WRITE 準備寫

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

        FD_ACCEPT 收到連接

        FD_CONNECT 完成連接

        FD_CLOSE 關閉socket。

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

        WSAAsyncSelect函數(shù)表示對socket s監(jiān)測lEvent指定的網(wǎng)絡事件,如果有事件發(fā)生,則給窗口hWnd發(fā)送消息wMsg。

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

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

        HANDLE WSAAsyncGetHostByName(

        HWND hWnd,

        u_int wMsg,

        const char FAR *name,

        char FAR *buf,

        int buflen

        );

        在調(diào)用WSAAsyncGetHostByName啟動操作時,不僅指定主機名字name,還指定了一個窗口句柄hWnd,一個消息ID wMsg,一個緩沖區(qū)及其長度。如果不能立即得到主機地址,則返回一個錯誤信息表示還在等待。當要的數(shù)據(jù)到達時,WinSock DLL給窗口hWnd發(fā)送消息wMsg告知得到了主機地址,窗口過程從指定的緩沖區(qū)buf得到主機地址。

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

      2. Socket的使用

         

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

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

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

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

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

     

socket_handle = socket(protocol_family. Socket_type, protocol);

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

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

  • 創(chuàng)建了socket之后,配置socket。

     

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

result = connect(socket_handle, remote_socket_address, address_length)

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

面向連接的服務器則使用bind指定本地信息,使用listen和accept獲取遠地信息。

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

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

result = bind( socket_hndle, local_socket_address, address_length)

參數(shù)類型同connect。

函數(shù)listen監(jiān)聽bind指定的端口,如果有遠地客戶請求連接,使用accept接收請求,創(chuàng)建一個新的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ù)報的socket使用sendto發(fā)送數(shù)據(jù),recvfrom接收數(shù)據(jù)。

    1. MFC對WinSockt API的封裝

       

      MFC提供了兩個類CAsyncSocket和CSocket來封裝WinSock API,這給程序員提供了一個更簡單的網(wǎng)絡編程接口。

      CAsyncSocket在較低層次上封裝了WinSock API,缺省情況下,使用該類創(chuàng)建的socket是非阻塞的socket,所有操作都會立即返回,如果沒有得到結果,返回WSAEWOULDBLOCK,表示是一個阻塞操作。

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

      1. CAsyncSocket

         

        CAsyncSocket封裝了低層的WinSock API,其成員變量m_hSocket保存其對應的socket句柄。使用CAsyncSocket的方法如下:

        首先,在堆或者棧中構造一個CAsyncSocket對象,例如:

        CAsyncSocket sock;或者

        CAsyncSocket *pSock = new CAsyncSocket;

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

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

        sock.Create()

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

        pSocket.Create(30, SOCK_DGRM);

        其三,如果是客戶程序,使用Connect連接到遠地;如果是服務程序,使用Listen監(jiān)聽遠地的連接請求。

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

        最后,銷毀CAsyncSocket,析構函數(shù)調(diào)用Close成員函數(shù)關閉socket。

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

      2. socket對象的創(chuàng)建和捆綁

         

        (1)Create函數(shù)

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

        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;

        }

        其中:

        參數(shù)1表示本socket的端口,缺省是0,如果要創(chuàng)建數(shù)據(jù)報的socket,則必須指定一個端口號。

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

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

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

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

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

        (2)Socket函數(shù)

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

        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;

        }

        其中:

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

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

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

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

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

        (3)捆綁(Attatch)

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

        另外,如果本模塊線程狀態(tài)的“socket窗口”沒有創(chuàng)建,則創(chuàng)建一個,該窗口在異步操作時用來接收WinSock的通知消息,窗口句柄保存到模塊線程狀態(tài)的m_hSocketWindow變量中。函數(shù)AsyncSelect將指定該窗口為網(wǎng)絡事件消息的接收窗口。

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

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

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

        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;

        }

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

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

        被指定的網(wǎng)絡事件對應的網(wǎng)絡I/O將是異步操作,是非阻塞操作。例如:指定FR_READ導致Receive是一個異步操作,如果不能立即讀到數(shù)據(jù),則返回一個錯誤WSAEWOULDBLOCK。在數(shù)據(jù)到達之后,WinSock通知窗口m_hSocketWindow,導致OnReceive被調(diào)用。

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

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

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

        所以,使用CAsyncSocket時,如果使用Create缺省創(chuàng)建socket,則所有網(wǎng)絡I/O都是異步操作,進行有關網(wǎng)絡I/O時則必須覆蓋以下的相關函數(shù):

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

        (5)Bind函數(shù)

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

        BOOL CAsyncSocket::Bind(UINT nSocketPort, LPCTSTR lpszSocketAddress)

        {

        USES_CONVERSION;

        //使用WinSock的地址結構構造地址信息

        SOCKADDR_IN sockAddr;

        memset(&sockAddr,0,sizeof(sockAddr));

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

        LPSTR lpszAscii = T2A((LPTSTR)lpszSocketAddress);

        //指定是Internet地址類型

        sockAddr.sin_family = AF_INET;

        if (lpszAscii == NULL)

        //沒有指定地址,則自動得到一個本地IP地址

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

        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比特的數(shù)據(jù)從主機字節(jié)序轉(zhuǎn)換成網(wǎng)絡字節(jié)序

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

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

        return Bind((SOCKADDR*)&sockAddr, sizeof(sockAddr));

        }

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

        函數(shù)Bind首先使用結構SOCKADDR_IN構造地址信息。該結構的域sin_family表示地址格式(TCP/IP同協(xié)議族),賦值為AF_INET(Internet地址格式);域sin_port表示端口,如果參數(shù)1為0,則WinSock分配一個端口給它,范圍在1024和5000之間;域sin_addr是表示地址信息,它是一個聯(lián)合體,其中s_addr表示如下形式的字符串,“28.56.22.8”。如果參數(shù)沒有指定地址,則WinSock自動地得到本地IP地址(如果有幾個網(wǎng)卡,則使用其中一個的地址)。

        (6)總結Create的過程

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

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

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

         

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

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

      switch (WSAGETSELECTEVENT(lParam))

      {

      case FD_READ:

      {

      DWORD nBytes;

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

      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的消息參數(shù),OnSocketNotify傳遞給函數(shù)DoCallBack,表示通知事件。

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

      從上面的討論可以看出,從創(chuàng)建socket到網(wǎng)絡I/O,CAsyncSocket直接封裝了低層的WinSock API,簡化了WinSock編程,實現(xiàn)了一個異步操作的界面。如果希望某個操作是阻塞操作,則在調(diào)用Create時不要指定該操作對應的網(wǎng)絡事件。例如,希望Connect和Send是阻塞操作,在任務完成之后才返回,則可以使用如下的語句:

      pSocket->Create(0, SOCK_STREAM,

      FR_WRITE|FR_OOB|FR_ACCEPT|FR_CLOSE);

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

    2. CSocket

       

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

      對于CSocket,處理網(wǎng)絡事件通知的函數(shù)OnAccept、OnClose、OnReceive仍然可以使用,OnConnect、OnSend在CSocket中永遠不會被調(diào)用,另外OnOutOfBandData在CSocket中不鼓勵使用。

      CSocket對象在調(diào)用Connect、Send、Accept、Close、Receive等成員函數(shù)后,這些函數(shù)在完成任務之后(連接被建立、數(shù)據(jù)被發(fā)送、連接請求被接收、socket被關閉、數(shù)據(jù)被讀取)之后才會返回。因此,Connect和Send不會導致OnConnect和OnSend被調(diào)用。如果覆蓋虛擬函數(shù)OnReceive、OnAccept、OnClose,不主動調(diào)用Receive、Accept、Close,則在網(wǎng)絡事件到達之后導致對應的虛擬函數(shù)被調(diào)用,虛擬函數(shù)的實現(xiàn)應該調(diào)用Receive、Accept、Close來完成操作。下面,就一個函數(shù)Receive來考察CSocket如何實現(xiàn)阻塞操作和消息循環(huán)的。

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

      {

      //m_pbBlocking是CSocket的成員變量,用來標識當前是否正在進行

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

      if (m_pbBlocking != NULL)

      {

      WSASetLastError(WSAEINPROGRESS);

      return FALSE;

      }

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

      int nResult;

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

      == SOCKET_ERROR)

      {

      if (GetLastError() == WSAEWOULDBLOCK)

      {

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

      if (!PumpMessages(FD_READ))

      return SOCKET_ERROR;

      }

      else

      return SOCKET_ERROR;

      }

      return nResult;

      }

      其中:

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

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

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

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

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

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

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

    3. CSocketFile

       

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

首先,構造一個CSocket對象;調(diào)用Create函數(shù)創(chuàng)建一個socket對象(SOCK_STREAM類型)。

接著,如果是客戶程序,調(diào)用Connect連接到遠地主機;如果是服務器程序,先調(diào)用Listen監(jiān)聽socket端口,收到連接請求后調(diào)用Accept接收請求。

然后,創(chuàng)建一個和CSocket對象關聯(lián)的CSocketFile對象,創(chuàng)建一個和CSocketFile對象關聯(lián)的CArchive對象,指定CArchive對象是用于讀或者寫。如果既要讀又要寫,則創(chuàng)建兩個CArchive對象。

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

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

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

下面,分析CSocketFile的設計和實現(xiàn)。

  1. CSocketFile的構造函數(shù)和析構函數(shù)的實現(xiàn)

     

  • 構造函數(shù)的實現(xiàn)

     

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

}

其中:

構造函數(shù)的參數(shù)1指向關聯(lián)的CSocket對象,被保存在成員變量m_pSocket中;

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

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

  • 析構函數(shù)的實現(xiàn)

     

CSocketFile::~CSocketFile()

{

}

(2)CSocketFile的讀寫的實現(xiàn)

分析CSocketFile如何用文件的讀寫實現(xiàn)網(wǎng)絡I/O。

  • 文件讀的實現(xiàn)

     

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個字節(jié)的數(shù)據(jù)

while(nLeft > 0)

{

//CSocket的Receive,阻塞操作,讀取到數(shù)據(jù)才繼續(xù)

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對象關聯(lián)使用

//讀取數(shù)據(jù),能讀多少是多少

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

if (nRead == SOCKET_ERROR)

{

int nError = m_pSocket->GetLastError();

AfxThrowFileException(CFileException::generic, nError);

ASSERT(FALSE);

}

return nRead;

}

  • 文件寫的實現(xiàn)

     

void CSocketFile::Write(const void* lpBuf, UINT nCount)

{

ASSERT (m_pSocket!=NULL);

//CSocket的函數(shù)Send,阻塞操作,發(fā)送完畢才繼續(xù)

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

if (nWritten == SOCKET_ERROR)

{

int nError = m_pSocket->GetLastError();

AfxThrowFileException(CFileException::generic, nError);

}

}

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

其他CFile界面,CSocketFile沒有實現(xiàn)。

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


本站僅提供存儲服務,所有內(nèi)容均由用戶發(fā)布,如發(fā)現(xiàn)有害或侵權內(nèi)容,請點擊舉報。
打開APP,閱讀全文并永久保存 查看更多類似文章
猜你喜歡
類似文章
CAsyncSocket,CSocket內(nèi)幕及其用法
CSocket Class
Windows Sockets網(wǎng)絡編程讀書筆記(及簡單C/S實現(xiàn))
深入 CSocket 編程之阻塞和非阻塞模式
非阻塞Winsock編程的問題及解決辦法
基于多線程的CSocket網(wǎng)絡編程技術
更多類似文章 >>
生活服務
分享 收藏 導長圖 關注 下載文章
綁定賬號成功
后續(xù)可登錄賬號暢享VIP特權!
如果VIP功能使用有故障,
可點擊這里聯(lián)系客服!

聯(lián)系客服