做.NET應(yīng)用開(kāi)發(fā)肯定會(huì)用到網(wǎng)絡(luò)通信,而進(jìn)程間通信是客戶端開(kāi)發(fā)使用頻率較高的場(chǎng)景。
進(jìn)程間通信方式主要有命名管道、消息隊(duì)列、共享內(nèi)存、Socket通信,個(gè)人使用最多的是Sokcet相關(guān)。
而Socket也有很多使用方式,Socket、WebSocket、TcpClient、UdpClient,是不是很多?HttpClient與TcpClient、WebSocket之間有什么關(guān)系?這里我們分別介紹下這些通信及使用方式
Socket
Socket是傳輸通信協(xié)議么?No,Socket是一種傳輸層和應(yīng)用層之間、用于實(shí)現(xiàn)網(wǎng)絡(luò)通信的編程接口。Socket可以使用各種協(xié)議如TCP、UDP協(xié)議實(shí)現(xiàn)進(jìn)程通信,TCP/UDP才是傳輸通信協(xié)議
Socket位于傳輸層與應(yīng)用層之間,接口在System.Net.Sockets命名空間下。下面是Socket以TCP通信的DEMO:
//創(chuàng)建一個(gè) Socket 實(shí)例
Socket clientSocket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
//連接到服務(wù)器
clientSocket.Connect(new IPEndPoint(IPAddress.Parse("127.0.0.1"), 8000));
//發(fā)送數(shù)據(jù)
string message = "Hello, Server!";
byte[] data = Encoding.ASCII.GetBytes(message);
clientSocket.Send(data);
//接收數(shù)據(jù)
byte[] buffer = new byte[1024];
int bytesRead = clientSocket.Receive(buffer);
Debug.WriteLine(Encoding.ASCII.GetString(buffer, 0, bytesRead));
clientSocket.Close();
TcpClient/UdpClient
TCP/UDP均是位于傳輸層的通信協(xié)議,所以Socket的使用也是位于傳輸層的通信操作
TCP是面向連接,提供可靠、順序的數(shù)據(jù)流傳輸。用于一對(duì)一的通信,即一個(gè)TCP連接只能有一個(gè)發(fā)送方和一個(gè)接收方。詳細(xì)連接方式是,先通過(guò)三次握手建立連接、然后傳輸數(shù)據(jù),傳輸數(shù)據(jù)完再通過(guò)4次揮手關(guān)閉連接。所以適用于需要數(shù)據(jù)完整性和可靠傳輸?shù)膱?chǎng)景
而UDP則是無(wú)連接的,不需要建立和維護(hù)連接狀態(tài),不提供確認(rèn)機(jī)制,也不重傳丟失的數(shù)據(jù)報(bào),但也因此傳輸實(shí)時(shí)性高,適合低延時(shí)、數(shù)據(jù)量小、廣播場(chǎng)景
基于Socket抽象編程接口,TCP、UDP構(gòu)建更高級(jí)別抽象網(wǎng)絡(luò)編程TcpClient、UdpClient,它們用于簡(jiǎn)化TCP網(wǎng)絡(luò)編程中的常見(jiàn)任務(wù)
TcpClient、UdpClient是 .NET 提供的用于方便管理TCP和UDP網(wǎng)絡(luò)通信的類,下面是對(duì)應(yīng)的Demo
Tcp服務(wù)端:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class TcpServerExample
{
public static void Main()
{
TcpListener listener = new TcpListener(“127.0.0.1", 8000);
listener.Start();
Console.WriteLine("Server is listening on port 8000...");
TcpClient client = listener.AcceptTcpClient();
NetworkStream stream = client.GetStream();
byte[] data = new byte[1024];
int bytesRead = stream.Read(data, 0, data.Length);
Console.WriteLine("Received: " + Encoding.ASCII.GetString(data, 0, bytesRead));
byte[] response = Encoding.ASCII.GetBytes("Hello, Client!");
stream.Write(response, 0, response.Length);
stream.Close();
client.Close();
listener.Stop();
}
}
TCP客戶端:
using System;
using System.Net.Sockets;
using System.Text;
class TcpClientExample
{
public static void Main()
{
TcpClient client = new TcpClient("127.0.0.1", 8000);
NetworkStream stream = client.GetStream();
byte[] message = Encoding.ASCII.GetBytes("Hello, Server!");
stream.Write(message, 0, message.Length);
byte[] data = new byte[1024];
int bytesRead = stream.Read(data, 0, data.Length);
Debug.WriteLine("Received: " + Encoding.ASCII.GetString(data, 0, bytesRead));
stream.Close();
client.Close();
}
}
Udp服務(wù)端:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class UdpServerExample
{
public static void Main()
{
UdpClient udpServer = new UdpClient(8000);
IPEndPoint remoteEP = new IPEndPoint(”127.0.0.1“, 0);
Console.WriteLine("Server is listening on port 8000...");
byte[] data = udpServer.Receive(ref remoteEP);
Console.WriteLine("Received: " + Encoding.ASCII.GetString(data));
byte[] response = Encoding.ASCII.GetBytes("Hello, Client!");
udpServer.Send(response, response.Length, remoteEP);
udpServer.Close();
}
}
Udp客戶端:
using System;
using System.Net;
using System.Net.Sockets;
using System.Text;
class UdpClientExample
{
public static void Main()
{
UdpClient udpClient = new UdpClient();
IPEndPoint remoteEP = new IPEndPoint(”127.0.0.1", 8000);
byte[] message = Encoding.ASCII.GetBytes("Hello, Server!");
udpClient.Send(message, message.Length, remoteEP);
byte[] data = udpClient.Receive(ref remoteEP);
Console.WriteLine("Received: " + Encoding.ASCII.GetString(data));
udpClient.Close();
}
}
上面是基本的網(wǎng)絡(luò)通信DEMO,TcpClient用于基于連接、可靠的TCP通信,適用于需要數(shù)據(jù)完整性和可靠傳輸?shù)膱?chǎng)景。Udp用于無(wú)連接、不保證傳輸?shù)腢DP通信,適用于對(duì)實(shí)時(shí)性要求高、允許少量數(shù)據(jù)丟失的場(chǎng)景(如視頻流)。會(huì)議場(chǎng)景下的傳屏軟件適合用這個(gè)協(xié)議,傳屏發(fā)送端固定幀率一直推送,網(wǎng)絡(luò)丟失幾幀問(wèn)題不大,重要的是延時(shí)低了很多。
TcpClient、UdpClient是位于傳輸層的通信類,分別實(shí)現(xiàn)了基于TCP和UDP協(xié)議的通信功能。
HttpClient
講完傳輸層的網(wǎng)絡(luò)通信類,就要說(shuō)下應(yīng)用層的HttpClient,這是專門用于HTTP協(xié)議的通信
Http與TCP/UDP均是網(wǎng)絡(luò)通信協(xié)議,TCP、UDP位于傳輸層,HTTP傳于應(yīng)用層,而且HTTP是基于TCP面向連接的,它是客戶端單向發(fā)起的半雙工協(xié)議。HTTP1.1之后引入持久連接,允許一個(gè)TCP連接進(jìn)行多次請(qǐng)求/響應(yīng)傳輸。HTTP層相比TCP它關(guān)注請(qǐng)求、響應(yīng)的內(nèi)容
HttpClient是Http協(xié)議的通信類,提供了封裝好的、高級(jí)的HTTP功能(如發(fā)起GET, POST請(qǐng)求,處理響應(yīng)等)。
HttpClient可以用于Web接口如Restful API的調(diào)用,我這邊Windows應(yīng)用的WebApi基礎(chǔ)組件庫(kù)就是用HttpClient實(shí)現(xiàn)的。
HttpClient類,在System.Net.Http.HttpClient命名空間下,HttpClient的內(nèi)部實(shí)現(xiàn)是基于Socket的。也就是說(shuō),HttpClient底層使用Socket接口來(lái)建立連接并傳輸數(shù)據(jù),但它隱藏了這些細(xì)節(jié),為開(kāi)發(fā)者提供了一個(gè)更簡(jiǎn)潔的API。
下面是我基于HttpClient實(shí)現(xiàn)的Web服務(wù)各類操作入口代碼,可以簡(jiǎn)單瀏覽下:
/// <summary>
/// 請(qǐng)求/推送數(shù)據(jù)
/// </summary>
/// <typeparam name="TResponse"></typeparam>
/// <param name="request"></param>
/// <returns></returns>
public async Task<TResponse> RequestAsync<TResponse>(HttpRequest request) where TResponse : HttpResponse, new()
{
var requestUrl = request.GetRequestUrl();
try
{
using var client = CreateHttpClient(request);
var requestMethod = request.GetRequestMethod();
switch (requestMethod)
{
case RequestMethod.Get:
{
using var response = await client.GetAsync(requestUrl);
return await response.GetTResponseAsync<TResponse>();
}
case RequestMethod.Post:
{
using var httpContent = request.GetHttpContent();
using var response = await client.PostAsync(requestUrl, httpContent);
return await response.GetTResponseAsync<TResponse>();
}
case RequestMethod.Put:
{
using var httpContent = request.GetHttpContent();
using var response = await client.PutAsync(requestUrl, httpContent);
return await response.GetTResponseAsync<TResponse>();
}
case RequestMethod.Delete:
{
using var response = await client.DeleteAsync(requestUrl);
return await response.GetTResponseAsync<TResponse>();
}
case RequestMethod.PostForm:
{
using var requestMessage = new HttpRequestMessage(HttpMethod.Post, requestUrl);
using var httpContent = request.GetHttpContent();
requestMessage.Content = httpContent;
using var response = await client.SendAsync(requestMessage);
return await response.GetTResponseAsync<TResponse>();
}
}
return new TResponse() { Message = $"不支持的請(qǐng)求類型:{requestMethod}" };
}
catch (ArgumentNullException e)
{
return new TResponse() { Code = NetErrorCodes.ParameterError, Message = e.Message, JsonData = e.StackTrace };
}
catch (TimeoutException e)
{
return new TResponse() { Code = NetErrorCodes.TimeOut, Message = e.Message, JsonData = e.StackTrace };
}
catch (Exception e)
{
return new TResponse() { Message = e.Message, JsonData = e.StackTrace };
}
}
HttpClient封裝后的網(wǎng)絡(luò)基礎(chǔ)組件調(diào)用方式,也比較簡(jiǎn)單。
添加接口請(qǐng)求說(shuō)明,參數(shù)及請(qǐng)求參數(shù)均統(tǒng)一在一個(gè)類文件里定義好:
/// <summary>
/// 內(nèi)網(wǎng)穿透注冊(cè)接口
/// </summary>
[Request("http://frp.supporter.ws.h3c.com/user/register",RequestMethod.Post)]
[DataContract]
internal class RegisterFrpRequest : HttpRequest
{
public RegisterFrpRequest(string sn, string appName)
{
Sn = sn;
SeverNames = new List<RequestServiceName>()
{
new RequestServiceName(appName,"http")
};
}
[DataMember(Name = "sn")]
public string Sn { get; set; }
[DataMember(Name = "localServerNames")]
public List<RequestServiceName> SeverNames { get; set; }
}
再定義請(qǐng)求結(jié)果返回?cái)?shù)據(jù),基類HttpResponse內(nèi)有定義基本參數(shù),狀態(tài)Success、狀態(tài)碼Code、返回描述信息Message:
[DataContract]
class RegisterFrpResponse : HttpResponse
{
[DataMember(Name = "correlationId")]
public string CorrelationId { get; set; }
[DataMember(Name = "data")]
public FrpRegisterData Data { get; set; }
/// <summary>
/// 是否成功
/// </summary>
public bool IsSuccess => Success && Code == 200000 && Data != null;
}
然后,業(yè)務(wù)層可以進(jìn)行簡(jiǎn)潔、高效率的調(diào)用:
var netClient = new NetHttpClient();
var response = await netClient.RequestAsync<RegisterFrpResponse>(new RegisterFrpRequest(sn, appName));
如果僅僅只是Data數(shù)據(jù),可以只定義數(shù)據(jù)類型,然后使用泛型HttpResponse作為返回?cái)?shù)據(jù)。
var response1 = await netClient.RequestAsync<HttpResponse<VersionInfo>>(new AppVersionRequest(appId));
WebSocket
WebSocket也是一個(gè)應(yīng)用層通信,不同于可以實(shí)現(xiàn)倆類協(xié)議TCP/UDP的Socket,WebSocket是以HTTP/HTTPS連接、以TCP傳輸數(shù)據(jù)。
一旦握手成功,客戶端和服務(wù)器之間可以進(jìn)行雙向數(shù)據(jù)傳輸,可以傳輸字節(jié)數(shù)據(jù)也可以傳輸文本內(nèi)容。
持久連接:WebSocket 是持久化連接,除非主動(dòng)關(guān)閉,否則在整個(gè)會(huì)話期間連接保持開(kāi)放。
全雙工通信:客戶端和服務(wù)器可以隨時(shí)發(fā)送數(shù)據(jù),通信不再是單向的。使用System.Net.WebSockets.ClientWebSocket類來(lái)實(shí)現(xiàn)WebSocket通信,通過(guò)減少 HTTP 請(qǐng)求/響應(yīng)的開(kāi)銷、延時(shí)較低。
而WebSocket與HttpClient呢,都用于應(yīng)用層的網(wǎng)絡(luò)通信,但它們的用途和通信協(xié)議是不同的。
HttpClient使用 HTTP 協(xié)議,WebSocket使用WebSocket協(xié)議,該協(xié)議在初始連接時(shí)通過(guò) HTTP/HTTPS握手,然后轉(zhuǎn)換為基于TCP通信的WebSocket協(xié)議。所以雖然都有使用HTTP協(xié)議,但WebSocket后續(xù)就切換至基于TCP的全雙工通信了
HttpClient基于請(qǐng)求/響應(yīng)模式,每次通信由客戶端向服務(wù)器發(fā)起請(qǐng)求。WebSocket提供全雙工通信,客戶端和服務(wù)器都可以主動(dòng)發(fā)送數(shù)據(jù)。
HttpClient主要用于訪問(wèn) RESTful API、下載文件或者發(fā)送HTTP請(qǐng)求。WebSocket主要用于實(shí)現(xiàn)低延遲的實(shí)時(shí)通信,如進(jìn)程間通信、局域網(wǎng)通信等。
我團(tuán)隊(duì)Windows應(yīng)用所使用的進(jìn)程間通信,就是基于WebSocketSharp封裝的。WebSocketSharp是一個(gè)功能全面、易于使用的第三方 WebSocket 庫(kù) GitHub - sta/websocket-sharp
至于為啥不直接使用ClientWebSocket。。。是因?yàn)楫?dāng)時(shí)團(tuán)隊(duì)還未切換.NET,使用的是.NETFramework。
后面團(tuán)隊(duì)使用的局域網(wǎng)通信基礎(chǔ)組件就是用ClientWebSocket了。
下面是我封裝的部分WebSocket通信代碼,事件發(fā)送(廣播)、以及監(jiān)聽(tīng)其它客戶端發(fā)送過(guò)來(lái)的事件消息:
/// <summary>
/// 發(fā)送消息
/// </summary>
/// <typeparam name="TInput">發(fā)送參數(shù)類型</typeparam>
/// <param name="client">目標(biāo)客戶端</param>
/// <param name="innerEvent">事件名</param>
/// <param name="data">發(fā)送參數(shù)</param>
/// <returns></returns>
public async Task<ClientResponse> SendAsync<TInput>(string client, InnerEventItem innerEvent, TInput data)
{
var message = new ChannelSendingMessage(client, new ClientEvent(innerEvent.EventName, innerEvent.EventId, true), _sourceClient);
message.SetData<TInput>(data);
return await SendMessageAsync(ChannelMessageType.ClientCommunication, message);
}
/// <summary>
/// 訂閱消息
/// </summary>
/// <param name="client">目標(biāo)客戶端</param>
/// <param name="innerEvent">事件名稱</param>
/// <param name="func">委托</param>
public ClientSubscribedEvent SubscribeFunc(string client, InnerEventItem innerEvent, Func<ClientResponse, object> func)
{
var eventName = innerEvent?.EventName;
if (string.IsNullOrEmpty(eventName) || func == null)
{
throw new ArgumentNullException($"{nameof(eventName)}或{nameof(func)},參數(shù)不能為空!");
}
var subscribedEvent = new ClientSubscribedEvent(client, innerEvent, func);
SubscribeEvent(subscribedEvent);
return subscribedEvent;
}
/// <summary>
/// 訂閱消息
/// </summary>
/// <param name="client">目標(biāo)客戶端</param>
/// <param name="innerEvent">事件名稱</param>
/// <param name="func">委托</param>
public ClientSubscribedEvent SubscribeFuncTask(string client, InnerEventItem innerEvent, Func<ClientResponse, Task<object>> func)
{
var eventName = innerEvent?.EventName;
if (string.IsNullOrEmpty(eventName) || func == null)
{
throw new ArgumentNullException($"{nameof(eventName)}或{nameof(func)},參數(shù)不能為空!");
}
var subscribedEvent = new ClientSubscribedEvent(client, innerEvent, func);
SubscribeEvent(subscribedEvent);
return subscribedEvent;
}
/// <summary>
/// 訂閱消息
/// </summary>
/// <param name="client">目標(biāo)客戶端</param>
/// <param name="innerEvent">事件名稱</param>
/// <param name="action">委托</param>
public ClientSubscribedEvent Subscribe(string client, InnerEventItem innerEvent, Action<ClientResponse> action)
{
var eventName = innerEvent?.EventName;
if (string.IsNullOrEmpty(eventName) || action == null)
{
throw new ArgumentNullException($"{nameof(eventName)}或{nameof(action)},參數(shù)不能為空!");
}
var subscribedEvent = new ClientSubscribedEvent(client, innerEvent, action);
SubscribeEvent(subscribedEvent);
return subscribedEvent;
}
作者:唐宋元明清2188
出處:http://www.cnblogs.com/kybs0/
本文版權(quán)歸作者和博客園共有,歡迎轉(zhuǎn)載,但未經(jīng)作者同意必須在文章頁(yè)面給出原文連接,否則保留追究法律責(zé)任的權(quán)利。
該文章在 2024/9/5 11:46:49 編輯過(guò)