.NET TCP、UDP、Socket、WebSocket

做.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连接只能有一个发送方和一个接收方。所以适用于需要数据完整性和可靠传输的场景

而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面向连接的。HttpClient则是Http协议的通信类,提供了封装好的、高级的HTTP功能(如发起GET, POST请求,处理响应等)。

HttpClient可以用于Web接口的调用,我这边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));

WebSocket

WebSocket也是一个应用层通信,不同于可以实现俩类协议TCP/UDP的Socket,WebSocket只依赖于HTTP/HTTPS连接。

一旦握手成功,客户端和服务器之间可以进行双向数据传输,可以传输字节数据也可以传输文本内容。

  • 持久连接: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 
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 = new ClientSubscribedEvent(client, innerEvent, func);
50             SubscribeEvent(subscribedEvent);
51             return subscribedEvent;
52         }

关键词:TCP/UDP,HTTP,Socket,TcpClient/UdpClient,HttpClient,WebSocket

相关推荐
张璐月41 分钟前
[eCapture] OpenSSL 文件 Hook 机制
网络·ebpf·ecapture
亚空间仓鼠1 小时前
Docker 容器技术入门与实践 (四):Docker存储与网络
网络·docker·容器
a***72891 小时前
SQL 注入漏洞原理以及修复方法
网络·数据库·sql
MAXrxc1 小时前
简单园区网实验
网络·智能路由器
埃伊蟹黄面2 小时前
应用层HTTP协议
linux·网络·网络协议·http
IMPYLH2 小时前
【无标题】
linux·运维·服务器·网络·bash
woohu1232 小时前
沃虎10G及以上速率连接器与变压器如何解锁下一代高速互联的潜能
网络
PinTrust SSL证书2 小时前
Sectigo(Comodo)企业型OV通配符SSL
网络·网络协议·网络安全·小程序·https·ssl
Black蜡笔小新2 小时前
国标GB28181视频监控平台EasyCVR赋能平安乡村建设,构筑乡村治理“数字防线”
java·网络·音视频
优秀是一种习惯啊2 小时前
DPDK 学习第一天
网络·dpdk