Windows Sockets API實現網絡異步通訊

[文章信息] 作者:信息産業部電子第二十二研究所青島分所郎銳

[文章導讀] 本文對如何使用面向連接的流式套接字實現對網卡的編程以及如何實現異步網絡通訊等問題進行了討論與闡述

摘要: 本文對如何使用面向連接的流式套接字實現對網卡的編程以及如何實現異步網絡通訊等問題進行了討論與闡述。

一、 引言

在80年代初,美國加利福尼亞大學伯克利分校的研究人員爲TCP/ip網絡通信開發了一個專門用于網絡通訊開發的API。這個API就是Socket接口(套接字)--當今在TCP/IP網絡最爲通用的一種API,也是在互聯網上進行應用開發最爲通用的一種API。在微軟聯合其它幾家公司共同制定了一套Windows下的網絡編程接口Windows Sockets規範後,由于在其規範中引入了一些異步函數,增加了對網絡事件異步選擇機制,因此更加符合Windows的消息驅動特性,使網絡開發人員可以更加方便的進行高性能網絡通訊程序的設計。本文接下來就針對Windows Sockets API進行面向連接的流式套接字編程以及對異步網絡通訊的編程實現等問題展開討論。

二、 面向連接的流式套接字編程模型的設計

本文在方案選擇上采用了在網絡編程中最常用的一種模型--客戶機/服務器模型。這種客戶/服務器模型是一種非對稱式編程模式。該模式的基本思想是把集中在一起的應用劃分成爲功能不同的兩個部分,分別在不同的計算機上運行,通過它們之間的分工合作來實現一個完整的功能。對于這種模式而言其中一部分需要作爲服務器,用來響應並爲客戶提供固定的服務;另一部分則作爲客戶機程序用來向服務器提出請求或要求某種服務。

本文選取了基于TCP/IP的客戶機/服務器模型和面向連接的流式套接字。其通信原理爲:服務器端和客戶端都必須建立通信套接字,而且服務器端應先進入監聽狀態,然後客戶端套接字發出連接請求,服務器端收到請求後,建立另一個套接字進行通信,原來負責監聽的套接字仍進行監聽,假如有其它客戶發來連接請求,則再建立一個套接字。默認狀態下最多可同時接收5個客戶的連接請求,並與之建立通信關系。因此本程序的設計流程應當由服務器首先啓動,然後在某一時刻啓動客戶機並使其與服務器建立連接。服務器與客戶機開始都必須調用Windows Sockets API函數socket()建立一個套接字sockets,然後服務器方調用bind()將套接字與一個本地網絡地址捆紮在一起,再調用listen()使套接字處于一種被動的預備接收狀態,同時規定它的請求隊列長度。在此之後服務器就可以通過調用accept()來接收客戶機的連接。

相對于服務器,客戶端的工作就顯得比較簡單了,當客戶端打開套接字之後,便可通過調用connect()和服務器建立連接。連接建立之後,客戶和服務器之間就可以通過連接發送和接收資料。最後資料傳送結束,雙方調用closesocket()關閉套接字來結束這次通訊。整個通訊過程的具體流程框圖可大致用下面的流程圖來表示:

(圖片較大,請拉動滾動條觀看)

面向連接的流式套接字編程流程示意圖

Windows Sockets API實現網絡異步通訊
更多內容請看Windows操作系統安全集 Windows操作系統安裝 Windows頻道專題,或 三、 軟件設計要點以及異步通訊的實現

根據前面設計的程序流程,可將程序劃分爲兩部分:服務器端和客戶端。而且整個實現過程可以大致用以下幾個非常要害的Windows Sockets API函數將其慣穿下來:

服務器方:

socket()-> bind()-> listen-> accept()-> recv()/send()-> closesocket()

客戶機方:

socket()-> connect()-> send()/recv()-> closesocket()

有鑒于以上幾個函數在整個網絡編程中的重要性,有必要結合程序實例對其做較深入的剖析。服務器端應用程序在使用套接字之前,首先必須擁有一個Socket,系統調用socket()函數向應用程序提供創建套接字的手段。該套接字實際上是在計算機中提供了一個通信埠,可以通過這個埠與任何一個具有套接字接口的計算機通信。應用程序在網絡上傳輸、接收的信息都通過這個套接字接口來實現的。在應用開發中如同使用文件句柄一樣,可以對套接字句柄進行讀寫操作:

sock=socket(AF_INET,SOCK_STREAM,0);

函數的第一個參數用于指定地址族,在Windows下僅支持AF_INET(TCP/IP地址);第二個參數用于描述套接字的類型,對于流式套接字提供有SOCK_STREAM;最後一個參數指定套接字使用的協議,一般爲0。該函數的返回值保存了新套接字的句柄,在程序退出前可以用 closesocket(sock);函數來將其釋放。服務器方一旦獲取了一個新的套接字後應通過bind()將該套接字與本機上的一個端口相關聯:

sockin.sin_family=AF_INET;

sockin.sin_addr.s_addr=0;

sockin.sin_port=htons(USERPORT);

bind(sock,(LPSOCKADDR)&sockin,sizeof(sockin)));

該函數的第二個參數是一個指向包含有本機IP地址和端口信息的sockaddr_in結構類型的指針,其成員描述了本地端口號和本地主機地址,經過bind()將服務器進程在網絡上標識出來。需要注重的是由于1024以內的埠號都是保留的埠號因此如無非凡需要一般不能將sockin.sin_port的埠號設置爲1024以內的值。然後調用listen()函數開始偵聽,再通過accept()調用等待接收連接以完成連接的建立:

//連接請求隊列長度爲1,即只答應有一個請求,若有多個請求,

//則出現錯誤,給出錯誤代碼WSAECONNREFUSED。

listen(sock,1);

//開啓線程避免主程序的阻塞

AfxBeginThread(Server,NULL);

……

UINT Server(LPVOID lpVoid)

{

……

int nLen=sizeof(SOCKADDR);

pView-> newskt=accept(pView-> sock,(LPSOCKADDR)& pView-> sockin,(LPINT)& nLen);

……

WSAAsyncSelect(pView-> newskt,pView-> m_hWnd,WM_SOCKET_MSG,FD_READFD_CLOSE);

return 1;

}

這裏之所以把accept()放到一個線程中去是因爲在執行到該函數時如沒有客戶連接服務器的請求到來,服務器就會停在accept語句上等待連接請求的到來,這勢必會引起程序的阻塞,雖然也可以通過設置套接字爲非阻塞方式使在沒有客戶等待時可以使accept()函數調用立即返回,但這種輪詢套接字的方式會使CPU處于忙等待方式,從而降低程序的運行效率大大浪費系統資源。考慮到這種情況,將套接字設置爲阻塞工作方式,並爲其單獨開辟一個子線程,將其阻塞控制在子線程範圍內而不會造成整個應用程序的阻塞。對于網絡事件的響應顯然要采取異步選擇機制,只有采取這種方式才可以在由網絡對方所引起的不可預知的網絡事件發生時能馬上在進程中做出及時的響應處理,而在沒有網絡事件到達時則可以處理其他事件,這種效率是很高的,而且完全符合Windows所標榜的消息觸發原則。前面那段代碼中的WSAAsyncSelect()函數便是實現網絡事件異步選擇的核心函數。

通過第四個參數注冊應用程序感興取的網絡事件,在這裏通過FD_READFD_CLOSE指定了網絡讀和網絡斷開兩種事件,當這種事件發生時變會發出由第三個參數指定的自定義消息WM_SOCKET_MSG,接收該消息的窗口通過第二個參數指定其句柄。在消息處理函數中可以通過對消息參數低字節進行判定而區別出發生的是何種網絡事件:

void CNetServerView::OnSocket(WPARAM wParam,LPARAM lParam)

{

int iReadLen=0;

int message=lParam & 0x0000FFFF;

switch(message)

{

case FD_READ://讀事件發生。此時有字符到達,需要進行接收處理

char cDataBuffer[MTU*10];

//通過套接字接收信息

iReadLen = recv(newskt,cDataBuffer,MTU*10,0);

//將信息保存到文件

if(!file.Open("ServerFile.txt",CFile::modeReadWrite))

file.Open("E:ServerFile.txt",CFile::modeCreateCFile::modeReadWrite);

file.SeekToEnd();

file.Write(cDataBuffer,iReadLen);

file.Close();

break;

case FD_CLOSE://網絡斷開事件發生。此時客戶機關閉或退出。

……//進行相應的處理

break;

default:

break;

}

}

在這裏需要實現對自定義消息WM_SOCKET_MSG的響應,需要在頭文件和實現文件中分別添加其消息映射關系:

頭文件:

//{{AFX_MSG(CNetServerView)

//}}AFX_MSG

void OnSocket(WPARAM wParam,LPARAM lParam);

DECLARE_MESSAGE_MAP()

實現文件:

BEGIN_MESSAGE_MAP(CNetServerView, CView)

//{{AFX_MSG_MAP(CNetServerView)

//}}AFX_MSG_MAP

ON_MESSAGE(WM_SOCKET_MSG,OnSocket)

END_MESSAGE_MAP()

在進行異步選擇使用WSAAsyncSelect()函數時,有以下幾點需要引起非凡的注重:

1. 連續使用兩次WSAAsyncSelect()函數時,只有第二次設置的事件有效,如:

WSAAsyncSelect(s,hwnd,wMsg1,FD_READ);

WSAAsyncSelect(s,hwnd,wMsg2,FD_CLOSE);

這樣只有當FD_CLOSE事件發生時才會發送wMsg2消息。

2.可以在設置過異步選擇後通過再次調用WSAAsyncSelect(s,hwnd,0,0);的形式取消在套接字上所設置的異步事件。

3.Windows Sockets DLL在一個網絡事件發生後,通常只會給相應的應用程序發送一個消息,而不能發送多個消息。但通過使用一些函數隱式地答應重發此事件的消息,這樣就可能再次接收到相應的消息。

4.在調用過closesocket()函數關閉套接字之後不會再發生FD_CLOSE事件。

以上基本完成了服務器方的程序設計,下面對于客戶端的實現則要簡單多了,在用socket()創建完套接字之後只需通過調用connect()完成同服務器的連接即可,剩下的工作同服務器完全一樣:用send()/recv()發送/接收收據,用closesocket()關閉套接字:

sockin.sin_family=AF_INET; //地址族

sockin.sin_addr.S_un.S_addr=IPaddr; //指定服務器的IP地址

sockin.sin_port=m_Port; //指定連接的端口號

int nConnect=connect(sock,(LPSOCKADDR)&sockin,sizeof(sockin));

本文采取的是可靠的面向連接的流式套接字。在數據發送上有write()、writev()和send()等三個函數可供選擇,其中前兩種分別用于緩沖發送和集中發送,而send()則爲可控緩沖發送,並且還可以指定傳輸控制標志爲MSG_OOB進行帶外數據的發送或是爲MSG_DONTROUTE尋徑控制選項。在信宿地址的網絡號部分指定數據發送需要經過的網絡接口,使其可以不經過本地尋徑機制直接發送出去。這也是其同write()函數的真正區別所在。由于接收數據系統調用和發送數據系統調用是一一對應的,因此對于數據的接收,在此不再贅述,相應的三個接收函數分別爲:read()、readv()和recv()。由于後者功能上的全面,本文在實現上選擇了send()-recv()函數對,在具體編程中應當視具體情況的不同靈活選擇適當的發送-接收函數對。

小結: TCP/IP協議是目前各網絡操作系統主要的通訊協議,也是 Internet的通訊協議,本文通過Windows Sockets API實現了對基于TCP/IP協議的面向連接的流式套接字網絡通訊程序的設計,並通過異步通訊和多線程等手段提高了程序的運行效率,避免了阻塞的發生。

Windows Sockets API實現網絡異步通訊
更多內容請看Windows操作系統安全集 Windows操作系統安裝 Windows頻道專題,或

Windows Sockets API實現網絡異步通訊
[文章信息] 作者:信息産業部電子第二十二研究所青島分所郎銳[文章導讀] 本文對如何使用面向連接的流式套接字實現對網卡的編程以及如何實現異步網絡通訊等問題進行了討論與闡述 ...查看完整版>>Windows Sockets API實現網絡異步通訊
 
用VC++6.0的Sockets API實現聊天室
陸爾東、鄧利平  1.VC++網絡編程及Windows Sockets API簡介  VC++對網絡編程的支持有socket支持,WinInet支持,MAPI和ISAPI支持等。其中,Windows Sockets API是TCP/IP網絡環境裏,也是Internet上進行開發最爲通...查看完整版>>用VC++6.0的Sockets API實現聊天室
 
用VC++6.0 Sockets API實現聊天室程序
用VC++6.0 Sockets API實現聊天室程序 1.VC++網絡編程及Windows Sockets API簡介VC++對網絡編程的支持有socket支持,WinInet支持,MAPI和ISAPI支持等。其中,Windows Sockets API是TCP/IP網絡環境裏,也是Internet上...查看完整版>>用VC++6.0 Sockets API實現聊天室程序
 
Windows Sockets 網絡編程(三)
來源:Windows Sockets 網絡編程(三) —— WINDOWS SOCKETS 1.1 程序設計作者: 一、簡介WINDOWS SOCKETS 是從 Berkeley Sockets 擴展而來的,其在繼承 Berkeley Sockets 的基礎上,又進行了新的擴充。這些...查看完整版>>Windows Sockets 網絡編程(三)
 
用VC++6.0的Sockets API實現一個聊天室程序
1.VC++網絡編程及Windows Sockets API簡介...查看完整版>>用VC++6.0的Sockets API實現一個聊天室程序
 
在C#中使用異步Socket編程實現TCP網絡服務的C/S的通訊構架(二)----使用方法
一.TcpSvr的使用方法A.測試程序:using System;using Ibms.Net.TcpCSFramework;using System.Collections;using System.Net.Sockets;namespace Ibms.Test{ /// <summary> /// 測試TcpSvr的類 /// </summary&...查看完整版>>在C#中使用異步Socket編程實現TCP網絡服務的C/S的通訊構架(二)----使用方法
 
在C#中使用異步Socket編程實現TCP網絡服務的C/S的通訊構架(一)----基礎類庫部分
///////////////////////////////////////////////////////////////////////////////////////////*標題:在C#中使用異步Socket編程實現TCP網絡服務的C/S的通訊構架(一)----基礎類庫部分當看到.NET中TcpListener和Tcp...查看完整版>>在C#中使用異步Socket編程實現TCP網絡服務的C/S的通訊構架(一)----基礎類庫部分
 
Windows Sockets 網絡編程(三) —— WINDOWS SOCKETS 1.1 程序設計
Windows Sockets 網絡編程(三) —— WINDOWS SOCKETS 1.1 程序設計 作者: 一、簡介 WINDOWS SOCKETS 是從 Berkeley Sockets 擴展而來的,其在繼承 Berkeley Sockets 的基礎上...查看完整版>>Windows Sockets 網絡編程(三) —— WINDOWS SOCKETS 1.1 程序設計
 
用API函數實現串行通訊
  以往的DOS系統是通過DOS中斷和BIOS中斷向用戶提供串行接口的通訊能力。在Windows環境下,C++的開發工具既沒有提供象DOS和BIOS中那樣專門的串行通訊控制方法,也不答應用戶直接控制串口的中斷。爲了保證資源共享,...查看完整版>>用API函數實現串行通訊
 
 
回到王朝網路首頁