LOGO OA教程 ERP教程 模切知识交流 PMS教程 CRM教程 开发文档 其他文档  
 
网站管理员

.NET中的TCP、UDP、Socket、WebSocket

freeflydom
2024年9月5日 11:46 本文热度 803

做.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服务端: 

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服务端: 

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();

    }

}

上面是基本的网络通信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服务各类操作入口代码,可以简单浏览下: 

/// <summary>

        /// 请求/推送数据

        /// </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 = $"不支持的请求类型:{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封装后的网络基础组件调用方式,也比较简单。

添加接口请求说明,参数及请求参数均统一在一个类文件里定义好: 

/// <summary>

/// 内网穿透注册接口

/// </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; }

}

再定义请求结果返回数据,基类HttpResponse内有定义基本参数,状态Success、状态码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;

}

然后,业务层可以进行简洁、高效率的调用:

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通信代码,事件发送(广播)、以及监听其它客户端发送过来的事件消息:

/// <summary>

    /// 发送消息

    /// </summary>

    /// <typeparam name="TInput">发送参数类型</typeparam>

    /// <param name="client">目标客户端</param>

    /// <param name="innerEvent">事件名</param>

    /// <param name="data">发送参数</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">目标客户端</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)},参数不能为空!");

        }


        var subscribedEvent = new ClientSubscribedEvent(client, innerEvent, func);

        SubscribeEvent(subscribedEvent);

        return subscribedEvent;

    }

    /// <summary>

    /// 订阅消息

    /// </summary>

    /// <param name="client">目标客户端</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)},参数不能为空!");

        }


        var subscribedEvent = new ClientSubscribedEvent(client, innerEvent, func);

        SubscribeEvent(subscribedEvent);

        return subscribedEvent;

    }


    /// <summary>

    /// 订阅消息

    /// </summary>

    /// <param name="client">目标客户端</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)},参数不能为空!");

        }


        var subscribedEvent = new ClientSubscribedEvent(client, innerEvent, action);

        SubscribeEvent(subscribedEvent);

        return subscribedEvent;

    }


作者:唐宋元明清2188
出处:http://www.cnblogs.com/kybs0/
本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须在文章页面给出原文连接,否则保留追究法律责任的权利。



该文章在 2024/9/5 11:46:49 编辑过
关键字查询
相关文章
正在查询...
点晴ERP是一款针对中小制造业的专业生产管理软件系统,系统成熟度和易用性得到了国内大量中小企业的青睐。
点晴PMS码头管理系统主要针对港口码头集装箱与散货日常运作、调度、堆场、车队、财务费用、相关报表等业务管理,结合码头的业务特点,围绕调度、堆场作业而开发的。集技术的先进性、管理的有效性于一体,是物流码头及其他港口类企业的高效ERP管理信息系统。
点晴WMS仓储管理系统提供了货物产品管理,销售管理,采购管理,仓储管理,仓库管理,保质期管理,货位管理,库位管理,生产管理,WMS管理系统,标签打印,条形码,二维码管理,批号管理软件。
点晴免费OA是一款软件和通用服务都免费,不限功能、不限时间、不限用户的免费OA协同办公管理系统。
Copyright 2010-2024 ClickSun All Rights Reserved