.NET TCP、UDP、Socket、WebSocket

来源:https://www.cnblogs.com/kybs0/p/18312434
-Advertisement-
Play Games

做.NET應用開發肯定會用到網路通信,而進程間通信是客戶端開發使用頻率較高的場景。 進程間通信方式主要有命名管道、消息隊列、共用記憶體、Socket通信,個人使用最多的是Sokcet相關。 而Socket也有很多使用方式,Socket、WebSocket、TcpClient、UdpClient,是不是 ...


做.NET應用開發肯定會用到網路通信,而進程間通信是客戶端開發使用頻率較高的場景。

進程間通信方式主要有命名管道、消息隊列、共用記憶體、Socket通信,個人使用最多的是Sokcet相關。

而Socket也有很多使用方式,Socket、WebSocket、TcpClient、UdpClient,是不是很多?HttpClient與TcpClient、WebSocket之間有什麼關係?這裡我們分別介紹下這些通信及使用方式

Socket

Socket是傳輸通信協議麽?No,Socket是一種傳輸層和應用層之間、用於實現網路通信的編程介面。Socket可以使用各種協議如TCP、UDP協議實現進程通信,TCP/UDP才是傳輸通信協議

Socket位於傳輸層與應用層之間,介面在System.Net.Sockets命名空間下。下麵是Socket以TCP通信的DEMO:

    //創建一個 Socket 實例
    Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
    
    //連接到伺服器
    clientSocket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8000));
    
    //發送數據
    string message = "Hello, Server!";
    byte[] data = Encoding.ASCII.GetBytes(message);
    clientSocket.Send(data);
    
    //接收數據
    byte[] buffer = new byte[1024];
    int bytesRead = clientSocket.Receive(buffer);
    Debug.WriteLine(Encoding.ASCII.GetString(buffer, 0, bytesRead));
    
    clientSocket.Close();

TcpClient/UdpClient

TCP/UDP均是位於傳輸層的通信協議,所以Socket的使用也是位於傳輸層的通信操作

TCP是面向連接,提供可靠、順序的數據流傳輸。用於一對一的通信,即一個TCP連接只能有一個發送方和一個接收方。詳細連接方式是,先通過三次握手建立連接、然後傳輸數據,傳輸數據完再通過4次揮手關閉連接。所以適用於需要數據完整性和可靠傳輸的場景

而UDP則是無連接的,不需要建立和維護連接狀態,不提供確認機制,也不重傳丟失的數據報,但也因此傳輸實時性高,適合低延時、數據量小、廣播場景

基於Socket抽象編程介面,TCP、UDP構建更高級別抽象網路編程TcpClient、UdpClient,它們用於簡化TCP網路編程中的常見任務

TcpClient、UdpClient是 .NET 提供的用於方便管理TCP和UDP網路通信的類,下麵是對應的Demo

Tcp服務端:

 1 using System;
 2 using System.Net;
 3 using System.Net.Sockets;
 4 using System.Text;
 5 
 6 class TcpServerExample
 7 {
 8     public static void Main()
 9     {
10         TcpListener listener = new TcpListener(“127.0.0.1", 8000);
11         listener.Start();
12         Console.WriteLine("Server is listening on port 8000...");
13 
14         TcpClient client = listener.AcceptTcpClient();
15         NetworkStream stream = client.GetStream();
16         
17         byte[] data = new byte[1024];
18         int bytesRead = stream.Read(data, 0, data.Length);
19         Console.WriteLine("Received: " + Encoding.ASCII.GetString(data, 0, bytesRead));
20 
21         byte[] response = Encoding.ASCII.GetBytes("Hello, Client!");
22         stream.Write(response, 0, response.Length);
23 
24         stream.Close();
25         client.Close();
26         listener.Stop();
27     }
28 }

TCP客戶端:

 1 using System;
 2 using System.Net.Sockets;
 3 using System.Text;
 4 
 5 class TcpClientExample
 6 {
 7     public static void Main()
 8     {
 9         TcpClient client = new TcpClient("127.0.0.1", 8000);
10         NetworkStream stream = client.GetStream();
11 
12         byte[] message = Encoding.ASCII.GetBytes("Hello, Server!");
13         stream.Write(message, 0, message.Length);
14 
15         byte[] data = new byte[1024];
16         int bytesRead = stream.Read(data, 0, data.Length);
17         Debug.WriteLine("Received: " + Encoding.ASCII.GetString(data, 0, bytesRead));
18 
19         stream.Close();
20         client.Close();
21     }
22 }

Udp服務端:

 1 using System;
 2 using System.Net;
 3 using System.Net.Sockets;
 4 using System.Text;
 5 
 6 class UdpServerExample
 7 {
 8     public static void Main()
 9     {
10         UdpClient udpServer = new UdpClient(8000);
11         IPEndPoint remoteEP = new IPEndPoint(”127.0.0.1“, 0);
12 
13         Console.WriteLine("Server is listening on port 8000...");
14 
15         byte[] data = udpServer.Receive(ref remoteEP);
16         Console.WriteLine("Received: " + Encoding.ASCII.GetString(data));
17 
18         byte[] response = Encoding.ASCII.GetBytes("Hello, Client!");
19         udpServer.Send(response, response.Length, remoteEP);
20 
21         udpServer.Close();
22     }
23 }

Udp客戶端:

 1 using System;
 2 using System.Net;
 3 using System.Net.Sockets;
 4 using System.Text;
 5 
 6 class UdpClientExample
 7 {
 8     public static void Main()
 9     {
10         UdpClient udpClient = new UdpClient();
11         IPEndPoint remoteEP = new IPEndPoint(”127.0.0.1", 8000);
12 
13         byte[] message = Encoding.ASCII.GetBytes("Hello, Server!");
14         udpClient.Send(message, message.Length, remoteEP);
15 
16         byte[] data = udpClient.Receive(ref remoteEP);
17         Console.WriteLine("Received: " + Encoding.ASCII.GetString(data));
18 
19         udpClient.Close();
20     }
21 }

上面是基本的網路通信DEMO,TcpClient用於基於連接、可靠的TCP通信,適用於需要數據完整性和可靠傳輸的場景。Udp用於無連接、不保證傳輸的UDP通信,適用於對實時性要求高、允許少量數據丟失的場景(如視頻流)。會議場景下的傳屏軟體適合用這個協議,傳屏發送端固定幀率一直推送,網路丟失幾幀問題不大,重要的是延時低了很多。

TcpClient、UdpClient是位於傳輸層的通信類,分別實現了基於TCP和UDP協議的通信功能。

HttpClient

講完傳輸層的網路通信類,就要說下應用層的HttpClient,這是專門用於HTTP協議的通信

Http與TCP/UDP均是網路通信協議,TCP、UDP位於傳輸層,HTTP傳於應用層,而且HTTP是基於TCP面向連接的,它是客戶端單向發起的半雙工協議。HTTP1.1之後引入持久連接,允許一個TCP連接進行多次請求/響應傳輸。HTTP層相比TCP它關註請求、響應的內容

HttpClient是Http協議的通信類,提供了封裝好的、高級的HTTP功能(如發起GET, POST請求,處理響應等)。

HttpClient可以用於Web介面如Restful API的調用,我這邊Windows應用的WebApi基礎組件庫就是用HttpClient實現的。

HttpClient類,在System.Net.Http.HttpClient命名空間下,HttpClient的內部實現是基於Socket的。也就是說,HttpClient底層使用Socket介面來建立連接並傳輸數據,但它隱藏了這些細節,為開發者提供了一個更簡潔的API。

下麵是我基於HttpClient實現的Web服務各類操作入口代碼,可以簡單瀏覽下:

 1         /// <summary>
 2         /// 請求/推送數據
 3         /// </summary>
 4         /// <typeparam name="TResponse"></typeparam>
 5         /// <param name="request"></param>
 6         /// <returns></returns>
 7         public async Task<TResponse> RequestAsync<TResponse>(HttpRequest request) where TResponse : HttpResponse, new()
 8         {
 9             var requestUrl = request.GetRequestUrl();
10             try
11             {
12                 using var client = CreateHttpClient(request);
13                 var requestMethod = request.GetRequestMethod();
14                 switch (requestMethod)
15                 {
16                     case RequestMethod.Get:
17                         {
18                             using var response = await client.GetAsync(requestUrl);
19                             return await response.GetTResponseAsync<TResponse>();
20                         }
21                     case RequestMethod.Post:
22                         {
23                             using var httpContent = request.GetHttpContent();
24                             using var response = await client.PostAsync(requestUrl, httpContent);
25                             return await response.GetTResponseAsync<TResponse>();
26                         }
27                     case RequestMethod.Put:
28                         {
29                             using var httpContent = request.GetHttpContent();
30                             using var response = await client.PutAsync(requestUrl, httpContent);
31                             return await response.GetTResponseAsync<TResponse>();
32                         }
33                     case RequestMethod.Delete:
34                         {
35                             using var response = await client.DeleteAsync(requestUrl);
36                             return await response.GetTResponseAsync<TResponse>();
37                         }
38                     case RequestMethod.PostForm:
39                         {
40                             using var requestMessage = new HttpRequestMessage(HttpMethod.Post, requestUrl);
41                             using var httpContent = request.GetHttpContent();
42                             requestMessage.Content = httpContent;
43                             using var response = await client.SendAsync(requestMessage);
44                             return await response.GetTResponseAsync<TResponse>();
45                         }
46                 }
47                 return new TResponse() { Message = $"不支持的請求類型:{requestMethod}" };
48             }
49             catch (ArgumentNullException e)
50             {
51                 return new TResponse() { Code = NetErrorCodes.ParameterError, Message = e.Message, JsonData = e.StackTrace };
52             }
53             catch (TimeoutException e)
54             {
55                 return new TResponse() { Code = NetErrorCodes.TimeOut, Message = e.Message, JsonData = e.StackTrace };
56             }
57             catch (Exception e)
58             {
59                 return new TResponse() { Message = e.Message, JsonData = e.StackTrace };
60             }
61         }

HttpClient封裝後的網路基礎組件調用方式,也比較簡單。

添加介面請求說明,參數及請求參數均統一在一個類文件里定義好:

 1 /// <summary>
 2 /// 內網穿透註冊介面
 3 /// </summary>
 4 [Request("http://frp.supporter.ws.h3c.com/user/register",RequestMethod.Post)]
 5 [DataContract]
 6 internal class RegisterFrpRequest : HttpRequest
 7 {
 8     public RegisterFrpRequest(string sn, string appName)
 9     {
10         Sn = sn;
11         SeverNames = new List<RequestServiceName>()
12         {
13             new RequestServiceName(appName,"http")
14         };
15     }
16     [DataMember(Name = "sn")]
17     public string Sn { get; set; }
18 
19     [DataMember(Name = "localServerNames")]
20     public List<RequestServiceName> SeverNames { get; set; }
21 }

再定義請求結果返回數據,基類HttpResponse內有定義基本參數,狀態Success、狀態碼Code、返回描述信息Message:

 1 [DataContract]
 2 class RegisterFrpResponse : HttpResponse
 3 {
 4 
 5     [DataMember(Name = "correlationId")]
 6     public string CorrelationId { get; set; }
 7 
 8     [DataMember(Name = "data")]
 9     public FrpRegisterData Data { get; set; }
10 
11     /// <summary>
12     /// 是否成功
13     /// </summary>
14     public bool IsSuccess => Success && Code == 200000 && Data != null;
15 }

然後,業務層可以進行簡潔、高效率的調用:

var netClient = new NetHttpClient();

var response = await netClient.RequestAsync<RegisterFrpResponse>(new RegisterFrpRequest(sn, appName));

如果僅僅只是Data數據,可以只定義數據類型,然後使用泛型HttpResponse作為返回數據。

var response1 = await netClient.RequestAsync<HttpResponse<VersionInfo>>(new AppVersionRequest(appId));

WebSocket

WebSocket也是一個應用層通信,不同於可以實現倆類協議TCP/UDP的Socket,WebSocket是以HTTP/HTTPS連接、以TCP傳輸數據。

一旦握手成功,客戶端和伺服器之間可以進行雙向數據傳輸,可以傳輸位元組數據也可以傳輸文本內容。

  • 持久連接:WebSocket 是持久化連接,除非主動關閉,否則在整個會話期間連接保持開放。

  • 全雙工通信:客戶端和伺服器可以隨時發送數據,通信不再是單向的。使用System.Net.WebSockets.ClientWebSocket類來實現WebSocket通信,通過減少 HTTP 請求/響應的開銷、延時較低。

WebSocketHttpClient呢,都用於應用層的網路通信,但它們的用途和通信協議是不同的。

  • HttpClient使用 HTTP 協議,WebSocket使用WebSocket協議,該協議在初始連接時通過 HTTP/HTTPS握手,然後轉換為基於TCP通信的WebSocket協議。所以雖然都有使用HTTP協議,但WebSocket後續就切換至基於TCP的全雙工通信了

  • HttpClient基於請求/響應模式,每次通信由客戶端向伺服器發起請求。WebSocket提供全雙工通信,客戶端和伺服器都可以主動發送數據。

  • HttpClient主要用於訪問 RESTful API、下載文件或者發送HTTP請求。WebSocket主要用於實現低延遲的實時通信,如進程間通信、區域網通信等。

我團隊Windows應用所使用的進程間通信,就是基於WebSocketSharp封裝的。WebSocketSharp是一個功能全面、易於使用的第三方 WebSocket 庫 GitHub - sta/websocket-sharp

至於為啥不直接使用ClientWebSocket。。。是因為當時團隊還未切換.NET,使用的是.NETFramework。

後麵團隊使用的區域網通信基礎組件就是用ClientWebSocket了。

下麵是我封裝的部分WebSocket通信代碼,事件發送(廣播)、以及監聽其它客戶端發送過來的事件消息:

 1     /// <summary>
 2     /// 發送消息
 3     /// </summary>
 4     /// <typeparam name="TInput">發送參數類型</typeparam>
 5     /// <param name="client">目標客戶端</param>
 6     /// <param name="innerEvent">事件名</param>
 7     /// <param name="data">發送參數</param>
 8     /// <returns></returns>
 9     public async Task<ClientResponse> SendAsync<TInput>(string client, InnerEventItem innerEvent, TInput data)
10     {
11         var message = new ChannelSendingMessage(client, new ClientEvent(innerEvent.EventName, innerEvent.EventId, true), _sourceClient);
12         message.SetData<TInput>(data);
13         return await SendMessageAsync(ChannelMessageType.ClientCommunication, message);
14     }
15 
16     /// <summary>
17     /// 訂閱消息
18     /// </summary>
19     /// <param name="client">目標客戶端</param>
20     /// <param name="innerEvent">事件名稱</param>
21     /// <param name="func">委托</param>
22     public ClientSubscribedEvent SubscribeFunc(string client, InnerEventItem innerEvent, Func<ClientResponse, object> func)
23     {
24         var eventName = innerEvent?.EventName;
25         if (string.IsNullOrEmpty(eventName) || func == null)
26         {
27             throw new ArgumentNullException($"{nameof(eventName)}或{nameof(func)},參數不能為空!");
28         }
29 
30         var subscribedEvent = new ClientSubscribedEvent(client, innerEvent, func);
31         SubscribeEvent(subscribedEvent);
32         return subscribedEvent;
33     }
34     /// <summary>
35     /// 訂閱消息
36     /// </summary>
37     /// <param name="client">目標客戶端</param>
38     /// <param name="innerEvent">事件名稱</param>
39     /// <param name="func">委托</param>
40     public ClientSubscribedEvent SubscribeFuncTask(string client, InnerEventItem innerEvent, Func<ClientResponse, Task<object>> func)
41     {
42         var eventName = innerEvent?.EventName;
43         if (string.IsNullOrEmpty(eventName) || func == null)
44         {
45             throw new ArgumentNullException($"{nameof(eventName)}或{nameof(func)},參數不能為空!");
46         }
47 
48         var subscribedEvent = new ClientSubscribedEvent(client, innerEvent, func);
49         SubscribeEvent(subscribedEvent);
50         return subscribedEvent;
51     }
52 
53     /// <summary>
54     /// 訂閱消息
55     /// </summary>
56     /// <param name="client">目標客戶端</param>
57     /// <param name="innerEvent">事件名稱</param>
58     /// <param name="action">委托</param>
59     public ClientSubscribedEvent Subscribe(string client, InnerEventItem innerEvent, Action<ClientResponse> action)
60     {
61         var eventName = innerEvent?.EventName;
62         if (string.IsNullOrEmpty(eventName) || action == null)
63         {
64             throw new ArgumentNullException($"{nameof(eventName)}或{nameof(action)},參數不能為空!");
65         }
66 
67         var subscribedEvent = new ClientSubscribedEvent(client, innerEvent, action);
68         SubscribeEvent(subscribedEvent);
69         return subscribedEvent;
70     }

關鍵詞:TCP/UDP,HTTP,Socket,TcpClient/UdpClient,HttpClient,WebSocket

作者:唐宋元明清2188 出處:http://www.cnblogs.com/kybs0/ 本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須在文章頁面給出原文連接,否則保留追究法律責任的權利。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 多線程編程是每一個開發必知必會的技能,在實際項目中,為了避免頻繁創建和銷毀線程,我們通常使用池化的思想,用線程池進行多線程開發。 線程池在開發中使用頻率非常高,也包含不少知識點,是一個高頻面試題,本篇總結線程池的使用經驗和需要註意的問題,更好的應對日常開發和麵試。 如有更多知識點,歡迎補充~ 異常處 ...
  • Command,即命令,具體而言,指的是實現了 ICommand 介面的對象。此介面要求實現者包含這些成員: 1、CanExecute 方法:確定該命令是否可以執行,若可,返回 true;若不可,返回 false; 2、CanExecuteChanged 事件:發送命令(命令源)的控制項可以訂閱此事件 ...
  • 概述 由於工作需要,需要通過數據類型和方法名控制方法走向 用到的數據類型有8種(string,Int16,Int32,Int64,Boolean,Byte,Single,Double) 讀取的方法(參數一致,但是數據不同的泛型方法,返回值也是泛型)暫時只有11種,但肯定的是,後續一定會增加 原本計劃 ...
  • 前言 預計在 2024 年 11 月,C# 13 將與 .NET 9 一起正式發佈。今年的 C# 更新主要集中在 ref struct 上進行了許多改進,並添加了許多有助於進一步提高生產力的便利功能。 本文將介紹預計將在 C# 13 中添加的功能。 註意:目前 C# 13 還未正式發佈,因此以下內容 ...
  • Windows應用開發有很多場景需要動態獲取控制項顯示的圖像,即控制項轉圖片,用於其它界面的顯示、傳輸圖片數據流、保存為本地圖片等用途。 下麵分別介紹下一些實現方式以及主要使用場景 RenderTargetBitmap 控制項轉圖片BitmapImage/BitmapSource,在WPF中可以使用Ren ...
  • 一晃距C# 9發佈已經4年了,對於record關鍵字想必大家都不陌生了,不過呢發現還是有很多同學不屑於使用這個語法糖,確實,本質上 record 就是 class 的封裝,能用 record 書寫的類,那100%都是可以自己手擼出來的,但是呢有沒有考慮 別人可能一分鐘寫好的代碼你可能會需要數分鐘才能 ...
  • C#進階之WebAPI(一) 那麼首先第一點:什麼是WebAPI? 首先我們瞭解一下.net framework 的框架構成: 可以看到,WebAPI和mvc同屬於B/S模板框架的一種,官方對於WebApi的定義是:WebAPI是一個框架,可以輕鬆構建HTTP服務,覆蓋廣泛的客戶端,包括瀏覽器和移動 ...
  • 最近有需求從第三方獲取到ofd文件後,需要轉pdf,1.目前看的有一個免費的插件,需要安裝程式包 FreeSpire.PDF 安裝後,直接引用 // odf文件地址 string path = @"D:\OFD\20240725\吳天.ofd"; OfdConverter converter = n ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...