做.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 = newSocket(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 usingSystem;2 usingSystem.Net;3 usingSystem.Net.Sockets;4 usingSystem.Text;5
6 classTcpServerExample7 {8 public static voidMain()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 usingSystem;2 usingSystem.Net.Sockets;3 usingSystem.Text;4
5 classTcpClientExample6 {7 public static voidMain()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 usingSystem;2 usingSystem.Net;3 usingSystem.Net.Sockets;4 usingSystem.Text;5
6 classUdpServerExample7 {8 public static voidMain()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(refremoteEP);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 usingSystem;2 usingSystem.Net;3 usingSystem.Net.Sockets;4 usingSystem.Text;5
6 classUdpClientExample7 {8 public static voidMain()9 {10 UdpClient udpClient = newUdpClient();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(refremoteEP);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面向连接的。TCPHTTP1.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 caseRequestMethod.Get:17 {18 using var response = awaitclient.GetAsync(requestUrl);19 return await response.GetTResponseAsync<TResponse>();20 }21 caseRequestMethod.Post:22 {23 using var httpContent =request.GetHttpContent();24 using var response = awaitclient.PostAsync(requestUrl, httpContent);25 return await response.GetTResponseAsync<TResponse>();26 }27 caseRequestMethod.Put:28 {29 using var httpContent =request.GetHttpContent();30 using var response = awaitclient.PutAsync(requestUrl, httpContent);31 return await response.GetTResponseAsync<TResponse>();32 }33 caseRequestMethod.Delete:34 {35 using var response = awaitclient.DeleteAsync(requestUrl);36 return await response.GetTResponseAsync<TResponse>();37 }38 caseRequestMethod.PostForm:39 {40 using var requestMessage = newHttpRequestMessage(HttpMethod.Post, requestUrl);41 using var httpContent =request.GetHttpContent();42 requestMessage.Content =httpContent;43 using var response = awaitclient.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 classRegisterFrpRequest : HttpRequest7 {8 public RegisterFrpRequest(string sn, stringappName)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 classRegisterFrpResponse : HttpResponse3 {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));
WebSocket
WebSocket也是一个应用层通信,不同于可以实现俩类协议TCP/UDP的Socket,WebSocket只依赖于HTTP/HTTPS连接。
一旦握手成功,客户端和服务器之间可以进行双向数据传输,可以传输字节数据也可以传输文本内容。
而
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>(stringclient, 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 awaitSendMessageAsync(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 = newClientSubscribedEvent(client, innerEvent, func);31 SubscribeEvent(subscribedEvent);32 returnsubscribedEvent;33 }34
35 /// <summary>
36 ///订阅消息37 /// </summary>
38 /// <param name="client">目标客户端</param>
39 /// <param name="innerEvent">事件名称</param>
40 /// <param name="func">委托</param>
41 public ClientSubscribedEvent SubscribeFuncTask(string client, InnerEventItem innerEvent, Func<ClientResponse, Task<object>>func)42 {43 var eventName = innerEvent?.EventName;44 if (string.IsNullOrEmpty(eventName) || func == null)45 {46 throw new ArgumentNullException($"{nameof(eventName)}或{nameof(func)},参数不能为空!");47 }48
49 var subscribedEvent = newClientSubscribedEvent(client, innerEvent, func);50 SubscribeEvent(subscribedEvent);51 returnsubscribedEvent;52 }
关键词:TCP/UDP,HTTP,Socket,TcpClient/UdpClient,HttpClient,WebSocket