Free mind

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

          SOCKET 匯總

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

          關(guān)于同步、異步,阻塞、非阻塞的解釋

          windows socket api 下:

          異步 方式 指的是發(fā)送方不等接收方響應(yīng),便接著發(fā)下個數(shù)據(jù)包的通信方式;而 同步 指發(fā)送方發(fā)出數(shù)據(jù)后,等收到接收方發(fā)回的響應(yīng),才發(fā)下一個數(shù)據(jù)包的通信方式。

          阻塞套接字 是指執(zhí)行此套接字的網(wǎng)絡(luò)調(diào)用時,直到成功才返回,否則一直阻塞在此網(wǎng)絡(luò)調(diào)用上,比如調(diào)用 recv() 函數(shù)讀取網(wǎng)絡(luò)緩沖區(qū)中的數(shù)據(jù),如果沒有數(shù)據(jù)到達(dá),將一直掛在 recv() 這個函數(shù)調(diào)用上,直到讀到一些數(shù)據(jù),此函數(shù)調(diào)用才返回;而非阻塞套接字是指執(zhí)行此套接字的網(wǎng)絡(luò)調(diào)用時,不管是否執(zhí)行成功,都立即返回。比如調(diào)用 recv() 函數(shù)讀取網(wǎng)絡(luò)緩沖區(qū)中數(shù)據(jù),不管是否讀到數(shù)據(jù)都立即返回,而不會一直掛在此函數(shù)調(diào)用上。在實(shí)際 Windows 網(wǎng)絡(luò)通信軟件開發(fā)中,異步非阻塞套接字是用的最多的。

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

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

          ?

          要點(diǎn)一、 UNIIX BSD SOCKET

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

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

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

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

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

          另一種是使用 select() 函數(shù)

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

          采用 select 函數(shù)解決,在收發(fā)前先檢查讀寫可用狀態(tài)。

             A 、讀

            例子:

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

          int nSelectRet;

          int nErrorCode;

          FD_SET fdr = {1, sConnect};

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

          if(SOCKET_ERROR==nSelectRet)

          {

          nErrorCode=WSAGetLastError();

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

          ::closesocket(sConnect);

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

          }

          if(nSelectRet==0)// 超時發(fā)生,無可讀數(shù)據(jù)

          {

          繼續(xù)查讀狀態(tài)或向?qū)Ψ街鲃影l(fā)送

          }

          else

          {

          讀數(shù)據(jù)

          }?

          B 、寫

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

          int nSelectRet;

          int nErrorCode;

          FD_SET fdw = {1, sConnect};

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

          if(SOCKET_ERROR==nSelectRet)

          {

          nErrorCode=WSAGetLastError();

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

          ::closesocket(sConnect);

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

          }

          if(nSelectRet==0)// 超時發(fā)生,緩沖滿或網(wǎng)絡(luò)忙

          {

          // 繼續(xù)查寫狀態(tài)或查讀狀態(tài)

          }

          else

          {

          // 發(fā)送

          }?

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

          ?

          附:

          改變 TCP 收發(fā)緩沖區(qū)大小

            系統(tǒng)默認(rèn)為 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");

          }

          ?

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

          int getsockopt(

          SOCKET s,

          int level,

          int optname,

          char FAR *optval,

          int FAR *optlen

          );?

          服務(wù)方同一端口多 IP 地址的 bind listen

           在可靠性要求高的應(yīng)用中,要求使用雙網(wǎng)和多網(wǎng)絡(luò)通道,再服務(wù)方很容易實(shí)現(xiàn),用如下方式可建立客戶對本機(jī)所有 IP 地址在端口 3024 下的請求服務(wù)。

          SOCKET hServerSocket_DS=INVALID_SOCKET;

          struct sockaddr_in HostAddr_DS;// 服務(wù)器主機(jī)地址

          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(" 建立數(shù)據(jù)服務(wù)器 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);?

          要點(diǎn)二、 windows socket api

          WINSOCK BSD Socket 的擴(kuò)充主要是在基于消息、對網(wǎng)絡(luò)事件的異步存取接口上。下表列出了 WINSOCK 擴(kuò)充的函數(shù)功能。

          ?????? ?? 數(shù) ??

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

          WSAAsyncGetHostByAddr()

          標(biāo)準(zhǔn) Berkeley 函數(shù) getXbyY 的異步版本,例

          WSAAsyncGetHostByName()

          如:函數(shù) WSAAsyncGetHostByName() 就是提

          WSAAsyncGetProtoByName()

          供了標(biāo)準(zhǔn) Berkeley 函數(shù) gethostbyname 的一

          WSAAsyncGetProtoByNumber()

          種基于消息的異步實(shí)現(xiàn)。

          WSAAsyncGetServByName()

          WSAAsyncGetServByPort()

          WSAAsyncSelect()

          函數(shù) select() 的異步版本

          WSACancelAsyncRequest()

          取消函數(shù) WSAAsyncGetXByY 執(zhí)行中的實(shí)例

          WSACancelBlockingCall()

          取消一個執(zhí)行中的 阻塞 ”API 調(diào)用

          WSACleanup()

          終止使用隱含的 Windows Sockets DLL

          WSAGetLastError()

          獲取 Windows Sockets API 的最近錯誤號

          WSAIsBlocking()

          檢測隱含的 Windows Sockets DLL 是否阻塞了一個當(dāng)前線索的調(diào)用

          WSASetBlockingHook()

          設(shè)置應(yīng)用程序自己的 阻塞 處理函數(shù)

          WSASetLastError()

          設(shè)置 Windows Sockets API 的最近錯誤號

          WSAStartup()

          初始化隱含的 Windows Sockets DLL

          WSAUnhookBlockingHook()

          恢復(fù)原來的 阻塞 處理函數(shù)

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

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

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

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

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

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

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

          ??? (4)出錯處理:

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

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

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

          ??? 在這些函數(shù)中,實(shí)現(xiàn) Windows 網(wǎng)絡(luò)實(shí)時通信的關(guān)鍵是異步選擇函數(shù) WSAAsyncSelect() 的使用 ,其原型如下:

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

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

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

          ??????? FD_READ?????? 希望在套接字 s 收到數(shù)據(jù)(即讀準(zhǔn)備好)時接到通知

          ??????? FD_WRITE????? 希望在套接字 s 可發(fā)送數(shù)據(jù)(即寫準(zhǔn)備好)時接到通知

          ??????? FD_OOB??????? 希望在套接字 s 上有帶外數(shù)據(jù)到達(dá)時接到通知

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

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

          ??????? FD_CLOSE????? 希望在套接字 s 關(guān)閉時接到通知

          ? ????? 表2 .?? 異步選擇網(wǎng)絡(luò)事件表

          ??? 例如,我們要在套接字 s 讀準(zhǔn)備好或?qū)憸?zhǔn)備好時接到通知,可以使用下面的語句:

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

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

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

          [ 1 ]

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

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

          事實(shí)上, UNIX 下的 SOCKET 只支持阻塞模式(現(xiàn)在 UNXI SOCKET 有了一些新的非阻塞特性,不過絕大多數(shù)應(yīng)用仍然使用阻塞模式) 。阻塞模式具有以下幾個比異步模式優(yōu)越的特點(diǎn):

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

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

          [ 2 ]

          Windows Sockets API Microsoft Windows 的網(wǎng)絡(luò)程序設(shè)計(jì)接口,它在繼承了 Berkeley Sockets 主要特征的基礎(chǔ)上,又對它進(jìn)行了重要擴(kuò)充。這些擴(kuò)充主要是提供了一些異步函數(shù),并增加了符合 Windows 消息驅(qū)動特性的網(wǎng)絡(luò)事件異步選擇機(jī)制 。這些擴(kuò)充有利于應(yīng)用程序開發(fā)者編制符合 Windows 編程模式的軟件,它使在 Windows 下開發(fā)高性能的網(wǎng)絡(luò)通信程序成為可能


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


          網(wǎng)站導(dǎo)航:
           
          主站蜘蛛池模板: 兴义市| 泸西县| 屯留县| 正定县| 宁阳县| 汾西县| 始兴县| 佳木斯市| 巴中市| SHOW| 娱乐| 娄烦县| 铜鼓县| 泉州市| 马鞍山市| 德兴市| 霍林郭勒市| 体育| 略阳县| 大方县| 大冶市| 云龙县| 安化县| 盐城市| 阿巴嘎旗| 江华| 年辖:市辖区| 建平县| 井陉县| 长沙县| 海门市| 县级市| 阿拉善左旗| 道孚县| 阳原县| 桐柏县| 石屏县| 鲁甸县| 且末县| 北宁市| 乌拉特前旗|