C#網路編程的圖文代碼詳解

來源:互聯網
上載者:User
在現今軟體開發中,網路編程是非常重要的一部分,本文簡要介紹下網路編程的概念和實踐,需要的朋友可以參考下

閱讀目錄:

基礎
Socket編程
多線程並發
阻塞式同步IO

基礎
在現今軟體開發中,網路編程是非常重要的一部分,本文簡要介紹下網路編程的概念和實踐。
Socket是一種網路編程介面,它是對傳輸層TCP、UDP通訊協定的一層封裝,通過友好的API暴露出去,方便在進程或多台機器間進行網路通訊。

Socket編程

在網路編程中分用戶端和服務端兩種角色,比如通過開啟瀏覽器訪問到掛在Web軟體上的網頁,從程式角度上來看,即用戶端(瀏覽器)發起了一個Socket請求到伺服器端,伺服器把網頁內容返回到瀏覽器解析後展示。在用戶端和服務端資料通訊前,會進行三次確認才會正式建立串連,也即是三向交握。

  1. 用戶端發送訊息詢問服務端是否準備好

  2. 服務端回應我準備好了,你呢準備好了嗎

  3. 用戶端回應服務端我也準備好了,可以通訊了

TCP/IP協議是網路間通訊的基礎協議,在不同程式設計語言及不同作業系統下暴露的Socket介面用法也大同小異,僅是其內部實現有所不同,比如Linux下的epoll和windows下的IOCP。

服務端
  • 執行個體化Socket

  • 把公用地址連接埠綁定作業系統上

  • 開始監聽綁定的連接埠

  • 等待用戶端串連

IPEndPoint ip = new IPEndPoint(IPAddress.Any, 6389);      Socket listenSocket = new Socket(ip.AddressFamily, SocketType.Stream, ProtocolType.Tcp);      listenSocket.Bind(ip);      listenSocket.Listen(100);      listenSocket.Accept();

listen函數中有個int型別參數,它表示最大等待處理串連的數量,表示已建立串連但還未處理的數量,每調用Accept函數一下即從這個等待隊列中拿出一個串連。 通常服務端要服務多個用戶端請求的串連,所以會迴圈從等待隊列中拿出串連,進行接收發送。

while (true)       {         var accept= listenSocket.Accept();        accept.Receive();         accept.Send();       }

多線程並發
上面的服務端程式處理接收和發送訊息都是在當前線程下完成的,這意味著要處理完一個用戶端串連後才能去處理下一個串連,如果當前串連是進行資料庫或者檔案讀取寫入等IO操作,那會極大浪費伺服器的CPU資源,降低了伺服器輸送量。

while (true)      {        var accept = listenSocket.Accept();        ThreadPool.QueueUserWorkItem((obj) =>        {          byte[] receive = new byte[100];          accept.Receive(receive);          byte[] send = new byte[100];          accept.Send(receive);        });      }

如例子中,當監聽到有新串連請求過來時,調用Accept()取出當前串連的socket,使用新的線程去處理接收和發送資訊,這樣服務端就能實現並發處理多個用戶端了。 上述代碼中,在高並發下其實是有問題的,如果用戶端串連請求成千上萬個,那線程數量也會有這麼多,每個線程的棧空間都需要消耗部分記憶體,再加上線程環境切換,容易導致伺服器負載過高,輸送量大大下降,嚴重時會引起宕機。 當前例子中使用系統ThreadPool的話,線程數量會固定在一個數量上,預設是1000,不會無限制開線程,會把處理超出線程數量的請求放到線程池中的隊列上面。
在unix下類似的實現有2種:

fork一個新進程去處理用戶端的串連:

var connfd = Accept(listenfd,(struct sockaddr *)&cliaddr,&cliaddr_len); var m = fork(); if(m == 0) { //do something }

建立一個新的線程處理限流:

var *clientsockfd = accept(serversockfd,(struct sockaddr *)&clientaddress, (socklent *)&clientlen); if(pthreadcreate(&thread, NULL, recdata, clientsockfd)!=0) { //do something }

阻塞式同步IO
上述例子中使用的即是該模型,使用起來簡單方便。

while (true)      {        var accept = listenSocket.Accept();        byte[] receive = new byte[100];        accept.Receive(receive);        byte[] send = new byte[100];        accept.Send(receive);      }

從調用Receive函數起到接受到用戶端發過來的資料期間,該函數會一直阻塞等待著,這個阻塞期間處理流程如下:

  1. 用戶端發送資料

  2. 通過廣域網路區域網路發送到服務端機器網卡緩衝區上

  3. 網卡驅動對CPU發送中斷指令

  4. CPU把資料拷貝到核心緩衝區

  5. CPU再把核心緩衝區的資料拷貝使用者緩衝區,上面的receive位元組數組。

至此處理成功,開始處理下一個串連請求。 調用發送函數同樣會阻塞在當前,然後把使用者緩衝區(send位元組數組)資料拷貝到核心中TCP發送緩衝區中。 TCP的發送緩衝區也有一定的大小限制,如果發送的資料大於該限制,send函數會一直等待發送緩衝區有空閑時完全拷貝完才會返回,繼續處理後續串連請求。

非同步IO
上篇提到用多執行緒多個阻塞同步IO而實現並發服務端,這種模式在串連數量比較小的時候非常適合,一旦串連過多,效能會急速下降。 在大多數服務端網路軟體中會採用一種非同步IO的方式來提高效能。

同步IO方式:串連Receive請求->等待->等待->接收成功
非同步IO方式:串連Receive請求->立即返回->事件或回調通知
採用非同步IO方式,意味著單線程可以處理多個請求了,串連發起一個Receive請求後,當前線程可以立即去做別的事情,當資料接收完畢通知線程處理即可。
其資料接收分2部分:

資料從別的機器發送核心緩衝區
核心緩衝區拷貝到使用者緩衝區
第二部分範例程式碼:

byte[] msg = new byte[256]; socket.Receive(msg);

介紹這2部分的目的是方便區分其他幾種方式。 對於使用者程式來說,同步IO和非同步IO的區別在於第二部分是否需要等待。

非阻塞式同步IO
非阻塞式同步IO,由同步IO延伸出來,把這個名詞拆分成2部分描述:

  • 非阻塞式,指的是上節"資料從別的機器發送核心緩衝區"部分是非阻塞的。

  • 同步IO,指的是上節"核心緩衝區拷貝到使用者緩衝區"部分是等待的。

既然是第一部分是非阻塞的,那就需要一種方法得知什麼時候核心緩衝區是OK的。 設定非阻塞模式後,在串連調用Receive方法時,會立即返回一個標記,告知使用者程式核心緩衝區有沒有資料,如果有資料開始進行第二部分操作,從核心緩衝區拷貝到使用者程式緩衝區。 由於系統會返回個標記,那可以通過輪詢方式來判斷核心緩衝區是否OK。

設定非阻塞模式參考代碼:

SocketInformation sif=new SocketInformation();sif.Options=SocketInformationOptions.NonBlocking;sif.ProtocolInformation = new byte[24];Socket socket = new Socket(sif);

輪詢參考代碼:

while(true) {byte[] msg = new byte[256];var temp = socket.Receive(msg);if (temp=="OK"){//do something}else{ continue }}

這種方式近乎淘汰了,瞭解即可。

基於回調的非同步IO
上面介紹過:

非同步IO方式:串連Receive請求->立即返回->事件或回調通知
當回調到執行時,資料已經在使用者程式緩衝區已經準備好了,在回調代碼中對這部分資料進行相應的邏輯即可。

發出接收請求:

static byte[] msg = new byte[256]; var temp = socket.BeginReceive(msg, 0, msg.Length, 0, new AsyncCallback(ReadCallback), socket);

回呼函數中對資料做處理:

public static void ReadCallback(IAsyncResult ar) { var socket = (Socket)ar.AsyncState; int read = socket.EndReceive(ar);DoSomething(msg); socket.BeginReceive(msg, 0, msg.Length, 0, new AsyncCallback(Read_Callback), socket);}

當回呼函數執行時,表示資料已經準備好,需要先結束接收請求EndReceive,以便第二次發出接收請求。 在服務端程式中要處理多個用戶端的接收,再次發出BeginReceive接收資料請求即可。

這裡的回呼函數是在另外一個線程的觸發,必要時要對資料加鎖防止資料競爭:

Console.WriteLine(Thread.CurrentThread.ManagedThreadId);
相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.