日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

非阻塞模式WinSock编程入门

發(fā)布時間:2024/4/11 编程问答 29 豆豆
生活随笔 收集整理的這篇文章主要介紹了 非阻塞模式WinSock编程入门 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.
介紹

WinSockWindows提供的包含了一系列網(wǎng)絡編程接口的套接字程序庫。在這篇文章中,我們將介紹如何把它的非阻塞模式引入到應用程序中。文章中所討論的通信均為面向連接的通信(TCP),為清晰起見,文章對代碼中的一些細枝末節(jié)進行了刪減,大家可以依照文末的鏈接下載完整的工程源碼來獲取這部分內(nèi)容。

?

阻塞模式WinSock

?????????下述偽代碼給出了阻塞模式下WinSock的使用方式。

view plaincopy to clipboardprint?
  • //--------------------------------------- ??
  • //?服務器 ??
  • //---------------------------------------- ??
  • //?WinSock初始化 ??
  • WSAStartup(); ??
  • ??
  • //?創(chuàng)建服務器套接字 ??
  • SOCKET?server?=?socket(); ??
  • ??
  • //?綁定到本機端口 ??
  • bind(server);? ??
  • ??
  • //?開始監(jiān)聽 ??
  • listen(server);? ??
  • ??
  • //?接收到客戶端連接,分配一個客戶端套接字 ??
  • SOCKET?client?=?accept(server);? ??
  • ??
  • //?使用新分配的客戶端套接字進行消息收發(fā) ??
  • send(client);? ??
  • recv(client); ??
  • ??
  • //?關(guān)閉客戶端套接字 ??
  • closesocket(client);? ??
  • ??
  • //?關(guān)閉服務器套接字 ??
  • closesocket(server); ??
  • ??
  • //?卸載WinSock ??
  • WSACleanup();??
  • ??

    ?

    view plaincopy to clipboardprint?
  • //--------------------------------------- ??
  • //?客戶端 ??
  • //--------------------------------------- ??
  • WSAStartup(); ??
  • ??
  • //?創(chuàng)建客戶端套接字 ??
  • SOCKET?client?=?socket(); ??
  • ??
  • //?綁定本機端口 ??
  • bind(client); ??
  • ??
  • //?連接到服務器 ??
  • ServerAddress?server; ??
  • connect(client,?server); ??
  • ??
  • //?確立連接后收發(fā)消息 ??
  • recv(client); ??
  • send(client); ??
  • ??
  • //?關(guān)閉客戶端套接字 ??
  • closesocket(client); ??
  • ??
  • WSACleanup();??
  • ??

    ?

    ?????????代碼中,服務器端的accept(),客戶端的connect(),以及服務器和客戶端中共同的recv()、send()函數(shù)均會產(chǎn)生阻塞。

    服務器在調(diào)用accept()后不會返回,直到接收到客戶端的連接請求;

    客戶端在調(diào)用connect()后不會返回,直到對服務器連接成功或者失敗;

    服務器和客戶端在調(diào)用recv()后不會返回,直到接收到并讀取完一條消息;

    服務器和客戶端在調(diào)用send()后不會返回,直到發(fā)送完待發(fā)送的消息。

    如果這兩段代碼被放在Windows程序的主線程中,你會發(fā)現(xiàn)消息循環(huán)被阻塞,程序不再響應用戶輸入及重繪請求。為了解決這個問題,你可能會想到開辟另外一個線程來運行這些代碼。這是可行的,但是考慮到每個SOCKET都不應該被其他SOCKET的操作所阻塞,是不是需要為每個SOCKET開辟一個線程?再考慮到同一SOCKET的一個讀寫操作也不應該被另外一個讀寫操作所阻塞,是不是應該再為每個SOCKET的讀和寫分別開辟一個線程?一般來說,這種自實現(xiàn)的多線程解決方案帶來的諸多線程管理方面的問題,是你絕對不會想要遇到的。

    ?

    非阻塞模式WinSock

    ?????????所幸的是,WinSock同時提供了非阻塞模式,并提出了幾種I/O模型。最常見的I/O模型有select模型、WSAAsyncSelect模型及WSAEventSelect模型,下面選擇其中的WSAAsyncSelect模型進行介紹。

    ?????????使用WSAAsyncSelect模型將非阻塞模式引入到應用程序中的過程看起來很簡單,事實上你只需要多添加一個函數(shù)就夠了。

    int WSAAsyncSelect(SOCKET s, HWND hWnd, unsigned int wMsg, long lEvent);

    該函數(shù)會自動將套接字設置為非阻塞模式,并且把發(fā)生在該套接字上且是你所感興趣的事件,以Windows消息的形式發(fā)送到指定的窗口,你需要做的就是在傳統(tǒng)的消息處理函數(shù)中處理這些事件。參數(shù)hWnd表示指定接受消息的窗口句柄;參數(shù)wMsg表示消息碼值(這意味著你需要自定義一個Windows消息碼);參數(shù)IEvent表示你希望接受的網(wǎng)絡事件的集合,它可以是如下值的任意組合:

    FD_READ, FD_WRITE, FD_OOB, FD_ACCEPT, FD_CONNECT, FD_CLOSE

    ?????????之后,就可以在我們熟知的Windows消息處理函數(shù)中處理這些事件。如果在某一套接字s上發(fā)生了一個已命名的網(wǎng)絡事件,應用程序窗口hWnd會接收到消息wMsg。參數(shù)wParam即為該事件相關(guān)的套接字s;參數(shù)lParam的低字段指明了發(fā)生的網(wǎng)絡事件,lParam的高字段則含有一個錯誤碼,事件和錯誤碼可以通過下面的宏從lParam中取出:

    #define WSAGETSELECTEVENT(lParam) LOWORD(lParam)

    #define WSAGETSELECTERROR(lParam) HIWORD(lParam)

    ?

    下面繼續(xù)使用偽代碼來幫助闡述如何將上一節(jié)的阻塞模式WinSock應用升級到非阻塞模式。

    首先自定義一個Windows消息碼,用于標識我們的網(wǎng)絡消息。

    view plaincopy to clipboardprint?
  • #define?WM_CUSTOM_NETWORK_MSG?(WM_USER?+?100)??
  • ?

    ?

    服務器端,在監(jiān)聽之前,將監(jiān)聽套接字置為非阻塞模式,并且標明其感興趣的事件為FD_ACCEPT。

    view plaincopy to clipboardprint?
  • … ??
  • WSAAsyncSelect(server,?wnd,?WM_CUSTOM_NETWORK_MSG,?FD_ACCEPT); ??
  • ??
  • //?開始監(jiān)聽 ??
  • listen(server);??
  • ??

    ?

    客戶端,在連接之前,將套接字置為非阻塞模式,并標明其感興趣的事件為FD_CONNECT

    view plaincopy to clipboardprint?
  • … ??
  • WSAAsyncSelect(client,?wnd,?WM_CUSTOM_NETWORK_MSG,?FD_CONNECT); ??
  • ??
  • //?連接到服務器 ??
  • ServerAddress?server; ??
  • connect(client,?server);??
  • ??

    ?

    接著,在Windows消息處理函數(shù)中,我們將處理監(jiān)聽事件、連接事件、及讀寫事件,方便起見,這里將服務器和客戶端的處理代碼放在了一起。

    view plaincopy to clipboardprint?
  • LRESULT?CALLBACK?WndProc(HWND?hWnd,?UINT?message,?WPARAM?wParam,?LPARAM?lParam) ??
  • { ??
  • ????switch?(message) ??
  • ????{ ??
  • ????… ??
  • ????case?WM_CUSTOM_NETWORK_MSG:?//?自定義的網(wǎng)絡消息碼 ??
  • ????????{ ??
  • ????????????SOCKET?socket?=?(SOCKET)wParam;?//?發(fā)生網(wǎng)絡事件的套接字 ??
  • ????????????long?event?=?WSAGETSELECTEVENT(lParam);?//?事件 ??
  • ????????????int?error?=?WSAGETSELECTERROR(lParam);?//?錯誤碼 ??
  • ??
  • ????????????switch?(event) ??
  • ????????????{ ??
  • ????????????case?FD_ACCEPT:?//?服務器收到新客戶端的連接請求 ??
  • ????????????????{ ??
  • ????????????????????//?接收到客戶端連接,分配一個客戶端套接字 ??
  • ????????????????????SOCKET?client?=?accept(socket);? ??
  • ????????????????????//?將新分配的客戶端套接字置為非阻塞模式,并標明其感興趣的事件為讀、寫及關(guān)閉 ??
  • ????????????????????WSAAsyncSelect(client,?hWnd,?message,?FD_READ?|?FD_WRITE?|?FD_CLOSE); ??
  • ????????????????} ??
  • ????????????????break; ??
  • ????????????case?FD_CONNECT:?//?客戶端連接到服務器的操作返回結(jié)果 ??
  • ????????????????{ ??
  • ????????????????????//?成功連接到服務器,將客戶端套接字置為非阻塞模式,并標明其感興趣的事件為讀、寫及關(guān)閉 ??
  • ????????????????????WSAAsyncSelect(socket,?hWnd,?message,?FD_READ?|?FD_WRITE?|?FD_CLOSE); ??
  • ????????????????} ??
  • ????????????????break; ??
  • ????????????case?FD_READ:?//?收到網(wǎng)絡包,需要讀取 ??
  • ????????????????{ ??
  • ????????????????????//?使用套接字讀取網(wǎng)絡包 ??
  • ????????????????????recv(socket); ??
  • ????????????????} ??
  • ????????????????break; ??
  • ????????????case?FD_WRITE: ??
  • ????????????????{ ??
  • ????????????????????//?FD_WRITE的處理后面會具體討論 ??
  • ????????????????} ??
  • ????????????????break; ??
  • ????????????case?FD_CLOSE:?//?套接字的連接方(而非本地socket)關(guān)閉消息 ??
  • ????????????????{ ??
  • ????????????????} ??
  • ????????????????break; ??
  • ????????????default: ??
  • ????????????????break; ??
  • ????????????} ??
  • ????????} ??
  • ????????break; ??
  • ????… ??
  • ????} ??
  • ????… ??
  • }??
  • ??

    ?

    以上就是非阻塞模式WinSock的應用框架,WSAAsyncSelect模型將套接字和Windows消息機制很好地粘合在一起,為用戶異步SOCKET應用提供了一種較優(yōu)雅的解決方案。

    ?

    擴展討論

    ???????? WinSock在系統(tǒng)底層為套接字收發(fā)網(wǎng)絡數(shù)據(jù)各提供一個緩沖區(qū),接收到的網(wǎng)絡數(shù)據(jù)會緩存在這里等待應用程序讀取,待發(fā)送的網(wǎng)絡數(shù)據(jù)也會先寫進這里之后通過網(wǎng)絡發(fā)送。

    相關(guān)的,針對FD_READFD_WRITE事件的讀寫處理,因涉及的內(nèi)容稍微復雜而容易使人困惑,這里需要特別進行討論。

    ?????????FD_READ事件中,使用recv()函數(shù)讀取網(wǎng)絡包數(shù)據(jù)時,由于事先并不知道完整網(wǎng)絡包的大小,所以需要多次讀取直到讀完整個緩沖區(qū)。這就需要類似如下代碼的調(diào)用:

    view plaincopy to clipboardprint?
  • void*?buf?=?0; ??
  • int?size?=?0; ??
  • while?(true) ??
  • { ??
  • ????char?tmp[128]; ??
  • ????int?bytes?=?recv(socket,?tmp,?128,?0); ??
  • ????if?(bytes?<=?0) ??
  • ????????break; ??
  • ????else??
  • ????{ ??
  • ????????int?new_size?=?size?+?bytes; ??
  • ????????buf?=?realloc(buf,?new_size); ??
  • ????????memcpy((void*)(((char*)buf)?+?size),?tmp,?bytes); ??
  • ????????size?=?new_size; ??
  • ????} ??
  • } ??
  • //?此時數(shù)據(jù)已經(jīng)從緩沖區(qū)全部拷貝到buf中,你可以在這里對buf做一些操作 ??
  • … ??
  • free(buf);??
  • ??

    ?

    ?????????這一切看起來都沒有什么問題,但是如果程序運行起來,你會收到比預期多出許多的FD_READ事件。如MSDN所述,正常的情況下,應用程序應當為每一個FD_READ消息僅調(diào)用一次recv()函數(shù)。如果一個應用程序需要在一個FD_READ事件處理中調(diào)用多次recv(),那么它將會收到多個FD_READ消息,因為每次未讀完緩沖區(qū)的recv()調(diào)用,都會重新觸發(fā)一個FD_READ消息。針對這種情況,我們需要在讀取網(wǎng)絡包前關(guān)閉掉FD_READ消息通知,讀取完這后再進行恢復,關(guān)閉FD_READ消息的方法很簡單,只需要調(diào)用WSAAsyncSelect時參數(shù)lEventFD_READ字段不予設置即可。

    view plaincopy to clipboardprint?
  • //?關(guān)閉FD_READ事件通知 ??
  • WSAAsyncSelect(socket,?hWnd,?message,?FD_WRITE?|?FD_CLOSE); ??
  • //?讀取網(wǎng)絡包 ??
  • … ??
  • //?再次打開FD_READ事件通知 ??
  • WSAAsyncSelect(socket,?hWnd,?message,?FD_WRITE?|?FD_CLOSE?|?FD_READ);??
  • ??

    ?

    ?????????第二個需要討論的是FD_WRITE事件。這個事件指明緩沖區(qū)已經(jīng)準備就緒,有了多出的空位可以讓應用程序?qū)懭霐?shù)據(jù)以供發(fā)送。該事件僅在兩種情況下被觸發(fā):

    1.?套接字剛建立連接時,表明準備就緒可以立即發(fā)送數(shù)據(jù)。

    2.?一次失敗的send()調(diào)用后緩沖區(qū)再次可用時。如果系統(tǒng)緩沖區(qū)已經(jīng)被填滿,那么此時調(diào)用send()發(fā)送數(shù)據(jù),將返回SOCKET_ERROR,使用WSAGetLastError()會得到錯誤碼WSAEWOULDBLOCK表明被阻塞。這種情況下當緩沖區(qū)重新整理出可用空間后,會向應用程序發(fā)送FD_WRITE消息,示意其可以繼續(xù)發(fā)送數(shù)據(jù)了。

    所以說收到FD_WRITE消息并不單純地等同于這是使用send()的唯一時機。一般來說,如果需要發(fā)送消息,直接調(diào)用send()發(fā)送即可。如果該次調(diào)用返回值為SOCKET_ERRORWSAGetLastError()得到錯誤碼WSAEWOULDBLOCK,這意味著緩沖區(qū)已滿暫時無法發(fā)送,此刻我們需要將待發(fā)數(shù)據(jù)保存起來,等到系統(tǒng)發(fā)出FD_WRITE消息后嘗試重新發(fā)送。也就是說,你需要針對FD_WRITE構(gòu)建一套數(shù)據(jù)重發(fā)的機制,文末的工程源碼里包含有這套機制以供大家參考,這里不再贅述。

    ?

    結(jié)語

    ?????????至此,如何在非阻塞模式下使用WinSock進行編程介紹完畢,這個框架可以滿足大多數(shù)網(wǎng)絡游戲客戶端及部分服務器的通信需求。更多應用層面上的問題(如TCP粘包等)這里沒有討論,或許會在以后的文章中給出。

    ?????????文章相關(guān)工程源碼請移步此處下載http://download.csdn.net/source/2852485。該源碼展示了采用非阻塞模式編程的服務器和客戶端,建立連接后,在服務器窗口輸入空格會向所有客戶端發(fā)送一條字符串消息。源碼中對網(wǎng)絡通信部分做了簡單封裝,所以代碼結(jié)構(gòu)會和文中的偽代碼稍有不同。

    謝謝您的閱讀!

    總結(jié)

    以上是生活随笔為你收集整理的非阻塞模式WinSock编程入门的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。