Free mind

          Be fresh and eager every morning, and tired and satisfied every night.
          posts - 39, comments - 2, trackbacks - 0, articles - 0
             :: 首頁 :: 新隨筆 :: 聯系 :: 聚合  :: 管理

          SOCKET 匯總

          Posted on 2007-02-09 22:44 morphis 閱讀(666) 評論(0)  編輯  收藏 所屬分類: 5. P2P

          關于同步、異步,阻塞、非阻塞的解釋

          windows socket api 下:

          異步 方式 指的是發送方不等接收方響應,便接著發下個數據包的通信方式;而 同步 指發送方發出數據后,等收到接收方發回的響應,才發下一個數據包的通信方式。

          阻塞套接字 是指執行此套接字的網絡調用時,直到成功才返回,否則一直阻塞在此網絡調用上,比如調用 recv() 函數讀取網絡緩沖區中的數據,如果沒有數據到達,將一直掛在 recv() 這個函數調用上,直到讀到一些數據,此函數調用才返回;而非阻塞套接字是指執行此套接字的網絡調用時,不管是否執行成功,都立即返回。比如調用 recv() 函數讀取網絡緩沖區中數據,不管是否讀到數據都立即返回,而不會一直掛在此函數調用上。在實際 Windows 網絡通信軟件開發中,異步非阻塞套接字是用的最多的。

          (同步阻塞、異步非阻塞)

          1 默認用作同步阻塞方式,那就是當你從不調用 WSAIoctl() ioctlsocket() 來改變 Socket IO 模式,也從不調用 WSAAsyncSelect() WSAEventSelect() 來選擇需要處理的 Socket 事件。正是由于函數 accept() WSAAccept() connect() WSAConnect() send() WSASend() recv() WSARecv() 等函數被用作阻塞方式,所以可能你需要放在專門的線程里,這樣以不影響主程序的運行和主窗口的刷新。
          2 、如果作為異步非阻塞方式用,那么程序主要就是要處理事件。它有兩種處理事件的辦法:
          ???
          第一種,它常關聯一個窗口,也就是異步 Socket 的事件將作為消息發往該窗口,這是由 WinSock 擴展規范里的一個函數 WSAAsyncSelect() 來實現和窗口關聯。最終你只需要處理窗口消息,來收發數據。
          ??
          第二種,用到了擴展規范里另一個關于事件的函數 WSAEventSelect() ,它是用事件對象的方式來處理 Socket 事件,也就是,你必須首先用 WSACreateEvent() 來創建一個事件對象,然后調用 WSAEventSelect() 來使得 Socket 的事件和這個事件對象關聯。最終你將要在一個線程里用 WSAWaitForMultipleEvents() 來等待這個事件對象被觸發。這個過程也稍顯復雜。

          ?

          要點一、 UNIIX BSD SOCKET

          UNIIX BSD SOCKET 主要是同步的 ,但有阻塞和非阻塞兩種方式。阻塞方式定義與前面定義相同,要解決阻塞有兩種方法

          一種是設置 SOCKET 屬性 ,設置為非阻塞( fcntl() 函數),

          sockfd?=?socket(AF_INET,?SOCK_STREAM,?0);?

          fcntl(sockfd,?F_SETFL,?O_NONBLOCK);?  

          通過設置套接字為非阻塞,你能夠有效地 " 詢問 " 套接字以獲得信息。如果嘗試著從一個非阻塞的套接字讀信息并且沒有任何數據,它不允許阻 ? 塞,它將返回 ?-1? 并將 ?errno? 設置為 ?EWOULDBLOCK ? 但是一般說來,這種詢問不是個好主意。如果讓程序在忙等狀 ? 態查詢套接字的數據,將浪費大量的 ?CPU? 時間。更好的解決之道是用 ? 下一章講的 ?select()? 去查詢是否有數據要讀進來。

          另一種是使用 select() 函數

          同步方式中解決 recv send 阻塞問題

          采用 select 函數解決,在收發前先檢查讀寫可用狀態。

             A 、讀

            例子:

          TIMEVAL tv01 = {0, 1};//1ms 鐘延遲 , 實際為 0-10 毫秒

          int nSelectRet;

          int nErrorCode;

          FD_SET fdr = {1, sConnect};

          nSelectRet=::select(0, &fdr, NULL, NULL, &tv01);// 檢查可讀狀態

          if(SOCKET_ERROR==nSelectRet)

          {

          nErrorCode=WSAGetLastError();

          TRACE("select read status errorcode=%d",nErrorCode);

          ::closesocket(sConnect);

          goto 重新連接(客戶方),或服務線程退出(服務方) ;

          }

          if(nSelectRet==0)// 超時發生,無可讀數據

          {

          繼續查讀狀態或向對方主動發送

          }

          else

          {

          讀數據

          }?

          B 、寫

          TIMEVAL tv01 = {0, 1};//1ms 鐘延遲 , 實際為 9-10 毫秒

          int nSelectRet;

          int nErrorCode;

          FD_SET fdw = {1, sConnect};

          nSelectRet=::select(0, NULL, NULL,&fdw, &tv01);// 檢查可寫狀態

          if(SOCKET_ERROR==nSelectRet)

          {

          nErrorCode=WSAGetLastError();

          TRACE("select write status errorcode=%d",nErrorCode);

          ::closesocket(sConnect);

          //goto 重新連接(客戶方),或服務線程退出(服務方) ;

          }

          if(nSelectRet==0)// 超時發生,緩沖滿或網絡忙

          {

          // 繼續查寫狀態或查讀狀態

          }

          else

          {

          // 發送

          }?

          對于 Windows 這種非搶先多任務操作系統來說,這兩種工作方式都是很難以接受的,為此, WINSOCK 在盡量與 BSD Socket 保持一致外,又對它作了必要的擴充

          ?

          附:

          改變 TCP 收發緩沖區大小

            系統默認為 8192 ,利用如下方式可改變。

          SOCKET sConnect;

          sConnect=::socket(AF_INET,SOCK_STREAM,IPPROTO_TCP);

          int nrcvbuf=1024*20;

          int err=setsockopt(

          sConnect,

          SOL_SOCKET,

          SO_SNDBUF,// 寫緩沖,讀緩沖為 SO_RCVBUF

          (char *)&nrcvbuf,

          sizeof(nrcvbuf));

          if (err != NO_ERROR)

          {

          TRACE("setsockopt Error!\n");

          }

          ?

          在設置緩沖時,檢查是否真正設置成功用

          int getsockopt(

          SOCKET s,

          int level,

          int optname,

          char FAR *optval,

          int FAR *optlen

          );?

          服務方同一端口多 IP 地址的 bind listen

           在可靠性要求高的應用中,要求使用雙網和多網絡通道,再服務方很容易實現,用如下方式可建立客戶對本機所有 IP 地址在端口 3024 下的請求服務。

          SOCKET hServerSocket_DS=INVALID_SOCKET;

          struct sockaddr_in HostAddr_DS;// 服務器主機地址

          LONG lPort=3024;

          HostAddr_DS.sin_family=AF_INET;

          HostAddr_DS.sin_port=::htons(u_short(lPort));

          HostAddr_DS.sin_addr.s_addr=htonl(INADDR_ANY);

          hServerSocket_DS=::socket( AF_INET, SOCK_STREAM,IPPROTO_TCP);

          if(hServerSocket_DS==INVALID_SOCKET)

          {

          AfxMessageBox(" 建立數據服務器 SOCKET 失敗 !");

          return FALSE;

          }

          if(SOCKET_ERROR==::bind(hServerSocket_DS,(struct

          sockaddr *)(&(HostAddr_DS)),sizeof(SOCKADDR)))

          {

          int nErrorCode=WSAGetLastError ();

          TRACE("bind error=%d\n",nErrorCode);

          AfxMessageBox("Socket Bind 錯誤 !");

          return FALSE;

          }

          ?

          if(SOCKET_ERROR==::listen(hServerSocket_DS,10))//10 個客戶

          {

          AfxMessageBox("Socket listen 錯誤 !");

          return FALSE;

          }

          ?

          AfxBeginThread(ServerThreadProc,NULL,THREAD_PRIORITY_NORMAL);?

          要點二、 windows socket api

          WINSOCK BSD Socket 的擴充主要是在基于消息、對網絡事件的異步存取接口上。下表列出了 WINSOCK 擴充的函數功能。

          ?????? ?? ??

          ???????? ???????????

          WSAAsyncGetHostByAddr()

          標準 Berkeley 函數 getXbyY 的異步版本,例

          WSAAsyncGetHostByName()

          如:函數 WSAAsyncGetHostByName() 就是提

          WSAAsyncGetProtoByName()

          供了標準 Berkeley 函數 gethostbyname 的一

          WSAAsyncGetProtoByNumber()

          種基于消息的異步實現。

          WSAAsyncGetServByName()

          WSAAsyncGetServByPort()

          WSAAsyncSelect()

          函數 select() 的異步版本

          WSACancelAsyncRequest()

          取消函數 WSAAsyncGetXByY 執行中的實例

          WSACancelBlockingCall()

          取消一個執行中的 阻塞 ”API 調用

          WSACleanup()

          終止使用隱含的 Windows Sockets DLL

          WSAGetLastError()

          獲取 Windows Sockets API 的最近錯誤號

          WSAIsBlocking()

          檢測隱含的 Windows Sockets DLL 是否阻塞了一個當前線索的調用

          WSASetBlockingHook()

          設置應用程序自己的 阻塞 處理函數

          WSASetLastError()

          設置 Windows Sockets API 的最近錯誤號

          WSAStartup()

          初始化隱含的 Windows Sockets DLL

          WSAUnhookBlockingHook()

          恢復原來的 阻塞 處理函數

          從表1可以看出, WINSOCK 的擴充功能 可以分為如下幾類:

          ??? (1)異步選擇機制:

          ??? 異步選擇函數 WSAAsyncSelect() 允許應用程序提名一個或多個感興趣的網絡事件, 所有阻塞的網絡 I/O 例程(如 send() resv() ),不管它是已經使用還是即將使用,都可作為 WSAAsyncSelect() 函數選擇的候選。當被提名的網絡事件發生時, Windows 應用程序的窗口函數將收到一個消息,消息附帶的參數指示被提名過的某一網絡事件。

          ??? (2)異步請求例程:

          ??? 異步請求例程允許應用程序用異步方式獲取請求的信息,如 WSAAsyncGetXByY() 類函數允許用戶請求異步服務,這些功能在使用標準 Berkeley 函數時是阻塞的。函數 WSACancelAsyncRequest() 允許用戶終止一個正在執行的異步請求。

          ??? (3)阻塞處理方法:

          ??? WINSOCK 在調用處于阻塞時進入一個叫 “Hook” 的例程,它負責處理 Windows 消息,使得 Windows 的消息循環能夠繼續。 WINSOCK 還提供了兩個函數( WSASetBlockingHook() WSAUnhookBlockingHook() )讓用戶能夠設置和取消自己的阻塞處理例程。另外,函數 WSAIsBlocking() 可以檢測調用是否阻塞,函數 WSACancelBlockingCall() 可以取消一個阻塞的調用。

          ??? (4)出錯處理:

          ?? ? 為了和以后的多線索環境(如 Windows/NT )兼容, WINSOCK 提供了兩個出錯處理函數 WSAGetLastError() WSASetLastError() 來獲取和設置本線索的最近錯誤號。

          ??? (5)啟動與終止:

          ??? WINSOCK 的應用程序在使用上述 WINSOCK 函數前,必須先調用 WSAStartup() 函數對 Windows Sockets DLL 進行初始化,以協商 WINSOCK 的版本支持,并分配必要的資源。在應用程序退出之前,應該先調用函數 WSAClearnup() 終止對 Windows Sockets DLL 的使用,并釋放資源,以利下一次使用。

          ??? 在這些函數中,實現 Windows 網絡實時通信的關鍵是異步選擇函數 WSAAsyncSelect() 的使用 ,其原型如下:

          int PASCAL FAR WSAAsyncSelect(SOCTET s,HWND hWnd,unsigned int wMsg,long lEvent);

          它請求 Windows Sockets DLL 在檢測到在套接字 s 上發生的 lEvent 事件時,向窗口 hWnd 發送一個消息 wMsg 。它自動地設置套接字 s 處于非阻塞工作方式。參數 lEvent 由下列事件的一個或多個組成:

          ?????? ???????? ???? ??????

          ??????? FD_READ?????? 希望在套接字 s 收到數據(即讀準備好)時接到通知

          ??????? FD_WRITE????? 希望在套接字 s 可發送數據(即寫準備好)時接到通知

          ??????? FD_OOB??????? 希望在套接字 s 上有帶外數據到達時接到通知

          ??????? FD_ACCEPT???? 希望在套接字 s 上有外部連接到來時接到通知

          ??????? FD_CONNECT 希望在套接字 s 連接建立完成時接到通知

          ??????? FD_CLOSE????? 希望在套接字 s 關閉時接到通知

          ? ????? 表2 .?? 異步選擇網絡事件表

          ??? 例如,我們要在套接字 s 讀準備好或寫準備好時接到通知,可以使用下面的語句:

          ??? rc = WSAAsyncSelect(s, hWnd, wMsg, FD_READ | FD_WRITE);

          當套接字 s 上被提名的一個網絡事件發生時,窗口 hWnd 將收到消息 wMsg ,變量 lParam 的低字指示網絡發生的事件,高字指示錯誤碼。應用程序就可以通過這些信息來決定自己的下一步動作。

          【理解:基于消息驅動的可稱之為 異步 。】

          [ 1 ]

          熟悉 WINSOCK 編程的讀者一定會覺得奇怪吧,為什么 INDY 是是完全基于 SOCKET 阻塞工作模式的呢?異步模式(非阻塞模式)是 WINSOCK 的一大特點,為什么不用呢?

            其實,之所以大多數 WINDOWS 下的 INTERNET 程序都使用異步模式,這和 WINSOCK 的歷史有關。當 WINSOCK 被移植到 WINDOWS 的時候,當時的 WINDOWS 操作系統還是 WINDOWS 3.1 ,而 WINDOWS 3.1 是不支持多線程的,不象 UNIX 下可以使用 FORK 來運行多進程。在 WINDOWS 3.1 下,如果使用阻塞模式,在通訊時會鎖定用戶界面使程序沒有響應,為了避免這種情況, WINSOCK 就引入異步模式這個新特性。而使用異步模式來編制 INTERNET 程序也就成了 WINDOWS 程序員的經典教條。但是,隨著新的 WINDOWS 操作系統的出現,如 WINDOWS 95 NT 98 ME 2000 等,這些操作系統開始支持多線程。異步模式這個教條仍然深入人心,使很多程序員會下意識的拒絕使用阻塞模式。

          事實上, UNIX 下的 SOCKET 只支持阻塞模式(現在 UNXI SOCKET 有了一些新的非阻塞特性,不過絕大多數應用仍然使用阻塞模式) 。阻塞模式具有以下幾個比異步模式優越的特點:

          編程更簡單,可以把所有處理 SOCKET 的代碼放在一起,順序執行,而不用分散在不同的事件處理代碼段里。

          更容易移植到 UNIX ,使用 INDY DELPHI 程序,可以不做太多(甚至不做)修改,就可以把 WINDOWS DELPHI 源代碼拿到 LINUX 下,用 Kylix 來編譯成 LINUX 下的網絡程序。

          [ 2 ]

          Windows Sockets API Microsoft Windows 的網絡程序設計接口,它在繼承了 Berkeley Sockets 主要特征的基礎上,又對它進行了重要擴充。這些擴充主要是提供了一些異步函數,并增加了符合 Windows 消息驅動特性的網絡事件異步選擇機制 。這些擴充有利于應用程序開發者編制符合 Windows 編程模式的軟件,它使在 Windows 下開發高性能的網絡通信程序成為可能


          只有注冊用戶登錄后才能發表評論。


          網站導航:
           
          主站蜘蛛池模板: 梨树县| 光山县| 肥乡县| 日照市| 新田县| 通许县| 阆中市| 西青区| 龙里县| 米脂县| 莒南县| 临海市| 嵊泗县| 临漳县| 南京市| 鹿邑县| 图们市| 建瓯市| 五家渠市| 阿荣旗| 潞西市| 宜州市| 北宁市| 平利县| 耒阳市| 本溪| 石棉县| 阳泉市| 南乐县| 普安县| 卢湾区| 永修县| 黎平县| 台南市| 池州市| 邳州市| 罗源县| 奇台县| 本溪| 都兰县| 巴林右旗|