C# 优雅的处理 TCP 数据

前言

Tcp是一个面向连接的流数据传输协议,用人话说就是传输是一个已经建立好连接的管道,数据都在管道里像流水一样流淌到对端。

那么数据必然存在几个问题,比如数据如何持续的读取,数据包的边界等。

Nagle's算法

Nagle 算法的核心思想是,在一个 TCP 连接上,最多只能有一个未被确认的小数据包(小于 MSS,即最大报文段大小)

优势

减少网络拥塞:通过合并小数据包,减少了网络中的数据包数量,降低了拥塞的可能性。

提高网络效率:在低速网络中,Nagle 算法可以显著提高传输效率。

劣势

增加延迟:在交互式应用中,Nagle 算法可能导致显著的延迟,因为它等待 ACK 或合并数据包。

C#中如何配置?

var _socket = new Socket(IPAddress.Any.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
_serverSocket.NoDelay = _options.NoDelay;

连接超时

在调用客户端Socket连接服务器的时候,可以设置连接超时机制,具体可以传入一个任务的取消令牌,并且设置超时时间。

CancellationTokenSource connectTokenSource = new CancellationTokenSource();
connectTokenSource.CancelAfter(3000); //3秒
await _socket.ConnectAsync(RemoteEndPoint, connectTokenSource.Token);

SSL加密传输

TCP使用SSL加密传输,通过非对称加密的方式,利用证书,保证双方使用了安全的密钥加密了报文。在C#中如何配置?

服务端配置

//创建证书对象
var _certificate  = _certificate = new X509Certificate2(_options.PfxCertFilename, _options.PfxPassword);//与客户端进行验证
if (allowingUntrustedSSLCertificate) //是否允许不受信任的证书
{SslStream = new SslStream(NetworkStream, false,(obj, certificate, chain, error) => true);
}
else
{SslStream = new SslStream(NetworkStream, false);
}try
{//serverCertificate:用于对服务器进行身份验证的 X509Certificate//clientCertificateRequired:一个 Boolean 值,指定客户端是否必须为身份验证提供证书//checkCertificateRevocation:一个 Boolean 值,指定在身份验证过程中是否检查证书吊销列表await SslStream.AuthenticateAsServerAsync(new SslServerAuthenticationOptions(){ServerCertificate = x509Certificate,ClientCertificateRequired = mutuallyAuthenticate,CertificateRevocationCheckMode = checkCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck}, cancellationToken).ConfigureAwait(false);if (!SslStream.IsEncrypted || !SslStream.IsAuthenticated){return false;}if (mutuallyAuthenticate && !SslStream.IsMutuallyAuthenticated){return false;}
}
catch (Exception)
{throw;
}//完成验证后,通过SslStream传输数据
int readCount = await SslStream.ReadAsync(buffer, _lifecycleTokenSource.Token).ConfigureAwait(false);
客户端配置
var _certificate = new X509Certificate2(_options.PfxCertFilename, _options.PfxPassword);if (_options.IsSsl) //如果使用ssl加密传输
{if (_options.AllowingUntrustedSSLCertificate)//是否允许不受信任的证书{_sslStream = new SslStream(_networkStream, false,(obj, certificate, chain, error) => true);}else{_sslStream = new SslStream(_networkStream, false);}_sslStream.ReadTimeout = _options.ReadTimeout;_sslStream.WriteTimeout = _options.WriteTimeout;await _sslStream.AuthenticateAsClientAsync(new SslClientAuthenticationOptions(){TargetHost = RemoteEndPoint.Address.ToString(),EnabledSslProtocols = System.Security.Authentication.SslProtocols.Tls12,CertificateRevocationCheckMode = _options.CheckCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck,ClientCertificates = new X509CertificateCollection() { _certificate }}, connectTokenSource.Token).ConfigureAwait(false);if (!_sslStream.IsEncrypted || !_sslStream.IsAuthenticated ||(_options.MutuallyAuthenticate && !_sslStream.IsMutuallyAuthenticated)){throw new InvalidOperationException("SSL authenticated faild!");}
}
KeepAlive

keepAlive不是TCP协议中的,而是各个操作系统本身实现的功能,主要是防止一些Socket突然断开后没有被感知到,导致一直浪费资源的情况。

其基本原理是在此机制开启时,当长连接无数据交互一定时间间隔时,连接的一方会向对方发送保活探测包,如连接仍正常,对方将对此确认回应

C#中如何调用操作系统的KeepAlive?

/// <summary>
/// 开启Socket的KeepAlive
/// 设置tcp协议的一些KeepAlive参数
/// </summary>
/// <param name="socket"></param>
/// <param name="tcpKeepAliveInterval">没有接收到对方确认,继续发送KeepAlive的发送频率</param>
/// <param name="tcpKeepAliveTime">KeepAlive的空闲时长,或者说每次正常发送心跳的周期</param>
/// <param name="tcpKeepAliveRetryCount">KeepAlive之后设置最大允许发送保活探测包的次数,到达此次数后直接放弃尝试,并关闭连接</param>
internal static void SetKeepAlive(this Socket socket, int tcpKeepAliveInterval, int tcpKeepAliveTime, int tcpKeepAliveRetryCount)
{socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true);socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveInterval, tcpKeepAliveInterval);socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveTime, tcpKeepAliveTime);socket.SetSocketOption(SocketOptionLevel.Tcp, SocketOptionName.TcpKeepAliveRetryCount, tcpKeepAliveRetryCount);
}

具体的开启,还需要看操作系统的版本以及不同操作系统的支持。

粘包断包处理

Pipe & ReadOnlySequence

上图来自微软官方博客:https://devblogs.microsoft.com/dotnet/system-io-pipelines-high-performance-io-in-net/

TCP面向应用是流式数据传输,所以接收端接到的数据是像流水一样从管道中传来,每次取到的数据取决于应用设置的缓冲区大小,以及套接字本身缓冲区待读取字节数

C#中提供的Pipe就如上图一样,是一个管道Pipe有两个对象成员,一个是PipeWriter,一个是PipeReader,可以理解为一个是生产者,专门往管道里灌输数据流,即字节流,一个是消费者,专门从管道里获取字节流进行处理。

可以看到Pipe中的数据包是用链表关联的,但是这个数据包是从Socke缓冲区每次取到的数据包,它不一定是一个完整的数据包,所以这些数据包连接起来后形成了一个C#提供的另外一个抽象的对象ReadOnlySequence。

但是这里还是没有提供太好的处理断包和粘包的办法,因为断包粘包的处理需要两方面

1、业务数据包的定义

2、数据流切割出一个个完整的数据包

假设业务已经定义好了数据包,那么我们如何从Pipe中这些数据包根据业务定义来从不同的数据包中切割出一个完整的包,那么就需要ReadOnlySequence,它提供的操作方法,非常方便我们去切割数据,主要是头尾数据包的切割。

假设我们业务层定义了一个数据包结构,数据包是不定长的,包体长度每次都写在包头里,我们来实现一个数据包过滤器。

//收到消息while (!_receiveDataTokenSource.Token.IsCancellationRequested){try{//从pipe中获取缓冲区Memory<byte> buffer = _pipeWriter.GetMemory(_options.BufferSize);int readCount = 0;readCount = await _sslStream.ReadAsync(buffer, _lifecycleTokenSource.Token).ConfigureAwait(false);if (readCount > 0){var data = buffer.Slice(0, readCount);//告知消费者,往Pipe的管道中写入了多少字节数据_pipeWriter.Advance(readCount);}else{if (IsDisconnect()){await DisConnectAsync();}throw new SocketException();}FlushResult result = await _pipeWriter.FlushAsync().ConfigureAwait(false);if (result.IsCompleted){break;}}catch (IOException){//TODO logbreak;}catch (SocketException){//TODO logbreak;}catch (TaskCanceledException){//TODO logbreak;}}_pipeWriter.Complete();
//消费者处理数据while (!_lifecycleTokenSource.Token.IsCancellationRequested){ReadResult result = await _pipeReader.ReadAsync();ReadOnlySequence<byte> buffer = result.Buffer;ReadOnlySequence<byte> data;do{//通过过滤器得到一个完整的包data = _receivePackageFilter.ResolvePackage(ref buffer);if (!data.IsEmpty){OnReceivedData?.Invoke(this, new ClientDataReceiveEventArgs(data.ToArray()));}}while (!data.IsEmpty && buffer.Length > 0);_pipeReader.AdvanceTo(buffer.Start);}_pipeReader.Complete();
/// <summary>
/// 解析数据包
/// 固定报文头解析协议
/// </summary>
/// <param name="headerSize">数据报文头的大小</param>
/// <param name="bodyLengthIndex">数据包大小在报文头中的位置</param>
/// <param name="bodyLengthBytes">数据包大小在报文头中的长度</param>
/// <param name="IsLittleEndian">数据报文大小端。windows中通常是小端,unix通常是大端模式</param>
/// </summary>
/// <param name="sequence">一个完整的业务数据包</param>
public override ReadOnlySequence<byte> ResolvePackage(ref ReadOnlySequence<byte> sequence)
{var len = sequence.Length;if (len < _bodyLengthIndex) return default;var bodyLengthSequence = sequence.Slice(_bodyLengthIndex, _bodyLengthBytes);byte[] bodyLengthBytes = ArrayPool<byte>.Shared.Rent(_bodyLengthBytes);try{int index = 0;foreach (var item in bodyLengthSequence){Array.Copy(item.ToArray(), 0, bodyLengthBytes, index, item.Length);index += item.Length;}long bodyLength = 0;int offset = 0;if (!_isLittleEndian){offset = bodyLengthBytes.Length - 1;foreach (var bytes in bodyLengthBytes){bodyLength += bytes << (offset * 8);offset--;}}else{foreach (var bytes in bodyLengthBytes){bodyLength += bytes << (offset * 8);offset++;}}if (sequence.Length < _headerSize + bodyLength)return default;var endPosition = sequence.GetPosition(_headerSize + bodyLength);var data = sequence.Slice(0, endPosition);//得到完整数据包sequence = sequence.Slice(endPosition);//缓冲区中去除取到的完整包return data;}finally{ArrayPool<byte>.Shared.Return(bodyLengthBytes);}
}

以上就是实现了固定数据包头实现粘包断包处理的部分代码。

关于TCP的连接还有一些,比如客户端连接限制,空闲连接关闭等。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/803715.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

电商技术揭秘十五:数据挖掘与用户行为分析

相关系列文章 电商技术揭秘一&#xff1a;电商架构设计与核心技术 电商技术揭秘二&#xff1a;电商平台推荐系统的实现与优化 电商技术揭秘三&#xff1a;电商平台的支付与结算系统 电商技术揭秘四&#xff1a;电商平台的物流管理系统 电商技术揭秘五&#xff1a;电商平台…

vue2 使用vue-org-tree demo

1.安装 npm i vue2-org-tree npm install -D less-loader less安装 less-loader出错解决办法&#xff0c;直接在package.json》devDependencies下面加入less和less-loader版本&#xff0c;然后执行npm i &#xff0c;我用的nodejs版本是 16.18.0&#xff0c;“webpack”: “^4…

ubuntu 18.04 安装 OpenSSL libssl.so.1.1

在 Ubuntu 18.04 上安装 OpenSSL 的 libssl.so.1.1 库&#xff0c;通常意味着您需要安装 OpenSSL 1.1.x 版本或更高版本&#xff0c;因为 libssl.so.1.1 是 OpenSSL 1.1.x 系列的一部分。以下是安装 OpenSSL 1.1.x 并确保 libssl.so.1.1 可用的步骤&#xff1a; 1. 更新软件包…

群集服务器与主机托管区别

1、首先什么群集服务器? 通俗的来说,它是指很多台服务器把它们集中在一起来进行同一种服务&#xff0c;而在我们在客户端看&#xff0c;却只能看见一个服务器;集群服务器也可以由很多个的计算机并行去计算&#xff0c;这样可以获得非常高的计算速度;同时也可以用很多个计算机来…

Tomcat SSL/TLS Configuration

see https://tomcat.apache.org/tomcat-9.0-doc/ssl-howto.html//1:use jdk keytool A:Generate Keystore 01: C:\Users\User>keytool -genkey -alias tomcat -keyalg RSA -keystore d:/ks/tomcatKeyStore //也可参考:keytool -genkeypair -alias "tomcat" -k…

maven的settings.xml、pom.xml配置文件

1、配置文件 maven的配置文件主要有 settings.xml 和pom.xml 两个文件。 其中在maven安装目录下的settings.xml&#xff0c;如&#xff1a;D:\Program Files\apache-maven-3.6.3\conf\settings.xml 是全局配置文件 用户目录的.m2子目录下的settings.xml&#xff0c;如&#…

【Livox激光MID-360】调试记录

官方git安装Livox-SDK2和ROS Driver 2。 修改驱动的雷达ip 打开ROS Driver2工程&#xff0c;修改livox_ros_driver2/config/MID360_config.json文件内的参数中ip部分&#xff0c;cmd_data_ip改为192.168.1.50&#xff0c;下面的ip改为雷达的ip&#xff0c;192.168.1.1** 最后…

微服务12要素

"微服务12要素"是一组指导原则&#xff0c;旨在帮助组织设计、构建和部署微服务架构。这些原则由Martin Fowler和James Lewis在他们的文章中提出&#xff0c;以帮助开发人员更好地理解和应用微服务架构。 以下是这12个要素的简要概述&#xff1a; 基于业务能力组织…

番茄 abogus rpc调用

声明: 本文章中所有内容仅供学习交流使用&#xff0c;不用于其他任何目的&#xff0c;抓包内容、敏感网址、数据接口等均已做脱敏处理&#xff0c;严禁用于商业用途和非法用途&#xff0c;否则由此产生的一切后果均与作者无关&#xff01;wx a15018601872 本文章…

如何将h5网页打包成iOS苹果IPA文件

哈喽&#xff0c;大家好呀&#xff0c;淼淼又来和大家见面啦&#xff0c;最近有很多小伙伴都被难住了&#xff0c;是什么问题给他们都难住了呢&#xff0c;许多小伙伴都说想要把h5网页打包成iOS苹果IPA文件&#xff0c;但是却不知道具体怎么操作&#xff0c;是怎么样的一个流程…

探秘大模型:《提示工程:技巧、方法与行业应用》背后的故事

提示工程是一种新兴的利用人工智能的技术&#xff0c;它通过设计提示引导生成式 AI 模型产生预期的输出&#xff0c;来提升人与 AI 的互动质量&#xff0c;激发 AI 模型的潜力&#xff0c;提升AI的应用水平。 为了让每一个人都拥有驱动大模型的能力&#xff0c;以微软全球副总裁…

题目 2915: 接水问题

题目描述: 学校里有一个水房&#xff0c;水房里一共装有 m 个龙头可供同学们打开水&#xff0c;每个龙头每秒钟的供水量相等&#xff0c;均为 1。 现在有 n 名同学准备接水&#xff0c;他们的初始接水顺序已经确定。将这些同学按接水顺序从 1 到 n 编号&#xff0c;i号同学的…

练习题(2024/4/9)

1 下一个排列 整数数组的一个 排列 就是将其所有成员以序列或线性顺序排列。 例如&#xff0c;arr [1,2,3] &#xff0c;以下这些都可以视作 arr 的排列&#xff1a;[1,2,3]、[1,3,2]、[3,1,2]、[2,3,1] 。 整数数组的 下一个排列 是指其整数的下一个字典序更大的排列。更…

2.Go的基本语法-指针、结构体、Map

1.指针 1.1.常规定义 func test24() {var a int 10var b *intb &afmt.Printf("a 的 值%d\n", a)fmt.Printf("a 的 指针地址%x\n", &a)fmt.Printf("b 的 值%d\n", *b)fmt.Printf("b 的 指针地址%x\n", b)打印var c *string…

常见通信方式之Websocket

官方文档 背景 在没有websocket之前&#xff0c;如果需要不断更新页面的某一部分信息&#xff0c;通常是采用基于http请求的单向轮询。但是轮询的话&#xff0c;一方面&#xff0c;我们又不希望过于频繁的请求服务器&#xff0c;一方面&#xff0c;又希望能够能快的收到最新的…

基于HASM模型的高精度建模matlab仿真

目录 1.程序功能描述 2.测试软件版本以及运行结果展示 3.核心程序 4.本算法原理 4.1HASM模型概述 4.2 HASM模型的数学表述 5.完整程序 1.程序功能描述 本课题主要使用HASM进行高精度建模&#xff0c;主要对HASM模型进行介绍以及在实际中如何进行简化实现的。HASM原始的模…

SQL注入利用学习 - 延时盲注

延时盲注原理 无法利用页面显示结果判断SQL注入是否执行成功&#xff0c;此时可以利用 SQL语句执行的延时 判断SQL是 否执行成功。 只要可以执行延时&#xff0c;那么就可以利用该注入技术。 sql时间类型的盲注本质是利用插入的SQL语句执行造成时间延迟&#xff0c;插入的SQ…

gpt科普1 GPT与搜索引擎的对比

GPT&#xff08;Generative Pre-trained Transformer&#xff09;是一种基于Transformer架构的自然语言处理模型。它通过大规模的无监督学习来预训练模型&#xff0c;在完成这个阶段后&#xff0c;可以用于各种NLP任务&#xff0c;如文本生成、机器翻译、文本分类等。 以下是关…

【图论】Leetcode 207. 课程表【中等】

课程表 你这个学期必须选修 numCourses 门课程&#xff0c;记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。 先修课程按数组 prerequisites 给出&#xff0c;其中 prerequisites[i] [ai, bi] &#xff0c;表示如果要学习课程 ai 则 必须 先学习课程 bi 。…

git-es6-promisem面试

git 工作区 暂存区 仓库区 git config --global user.name xxx git config --global user.email xxx验证是否配置成功 输入命令&#xff1a; git config --list在 c:/用户/用户名 下有 .gitconfig文件&#xff0c;存放的就是git配置的用户信息 新项目git管理步骤 初始化 g…