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

歡迎訪問 生活随笔!

生活随笔

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

编程问答

非阻塞模式WinSock编程入门

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

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

?

阻塞模式WinSock

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

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

    ?

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

    ?

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

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

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

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

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

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

    ?

    非阻塞模式WinSock

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

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

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

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

    FD_READ, FD_WRITE, FD_OOB, FD_ACCEPT, FD_CONNECT, FD_CLOSE

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

    #define WSAGETSELECTEVENT(lParam) LOWORD(lParam)

    #define WSAGETSELECTERROR(lParam) HIWORD(lParam)

    ?

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

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

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

    ?

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

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

    ?

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

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

    ?

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

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

    ?

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

    ?

    擴展討論

    ???????? WinSock在系統底層為套接字收發網絡數據各提供一個緩沖區,接收到的網絡數據會緩存在這里等待應用程序讀取,待發送的網絡數據也會先寫進這里之后通過網絡發送。

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

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

    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; ??
  • ????} ??
  • } ??
  • //?此時數據已經從緩沖區全部拷貝到buf中,你可以在這里對buf做一些操作 ??
  • … ??
  • free(buf);??
  • ??

    ?

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

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

    ?

    ?????????第二個需要討論的是FD_WRITE事件。這個事件指明緩沖區已經準備就緒,有了多出的空位可以讓應用程序寫入數據以供發送。該事件僅在兩種情況下被觸發:

    1.?套接字剛建立連接時,表明準備就緒可以立即發送數據。

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

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

    ?

    結語

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

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

    謝謝您的閱讀!

    總結

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

    如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。