做.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 請求/響應的開銷、延時較低。
而WebSocket
與HttpClient
呢,都用於應用層的網路通信,但它們的用途和通信協議是不同的。
-
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/ 本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須在文章頁面給出原文連接,否則保留追究法律責任的權利。