• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>

            道。道。道

            安全特性不等于安全的特性

               :: 首頁 :: 聯系 :: 聚合  :: 管理

            常用鏈接

            搜索

            •  

            最新評論

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

              一、 引言

              在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函數將其慣穿下來:

              服務器方:

            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_READ|FD_CLOSE);
            return 1;
            }

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

              通過第四個參數注冊應用程序感興取的網絡事件,在這里通過FD_READ|FD_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::modeCreate|CFile::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協議的面向連接的流式套接字網絡通訊程序的設計,并通過異步通訊和多線程等手段提高了程序的運行效率,避免了阻塞的發生。
            posted on 2007-01-31 11:32 獨孤九劍 閱讀(1494) 評論(0)  編輯 收藏 引用 所屬分類: network protocol
            99久久久精品| 青青青国产精品国产精品久久久久 | 香蕉久久夜色精品升级完成| 香蕉99久久国产综合精品宅男自 | 7国产欧美日韩综合天堂中文久久久久 | 久久久久亚洲av综合波多野结衣| 久久久久久国产精品美女| 国内精品久久久久久99| 久久精品无码一区二区三区日韩 | 国产成人综合久久精品红| 亚洲综合伊人久久综合| 青青青伊人色综合久久| 色青青草原桃花久久综合| 久久丫忘忧草产品| 国産精品久久久久久久| 狠狠色婷婷久久一区二区| 91精品国产高清久久久久久国产嫩草| 久久久久99精品成人片三人毛片 | 99久久无码一区人妻a黑| 激情久久久久久久久久| 麻豆一区二区99久久久久| 久久无码国产| 久久久精品午夜免费不卡| 伊人久久大香线蕉综合Av | 青青草国产精品久久久久| 久久SE精品一区二区| 国产精品美女久久久久AV福利| 久久久久成人精品无码中文字幕| 久久精品国产WWW456C0M| 99国产精品久久| 欧美黑人又粗又大久久久| 久久综合久久美利坚合众国| 日本亚洲色大成网站WWW久久| 精品国产热久久久福利| 青青青国产精品国产精品久久久久| 久久亚洲AV成人无码国产| 久久久久无码精品国产不卡| 久久亚洲AV成人无码国产 | 久久久久一区二区三区| 99久久免费国产精精品| 欧美777精品久久久久网|