C# 处理 TCP 数据

news2024/11/19 20:18:10

前言

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 log
         break;
     }
     catch (SocketException)
     {
         //TODO log
         break;
     }
     catch (TaskCanceledException)
     {
         //TODO log
         break;
     }
 }

 _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.coloradmin.cn/o/1811201.html

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

相关文章

app开发--Android平台签名证书(.keystore .jks)生成和常见错误总结

app开发后&#xff0c;最后需要打包发布。在Android平台打包发布apk应用&#xff0c;需要使用数字证书&#xff08;.keystore文件&#xff09;进行签名&#xff0c;用于表明开发者身份。下面总结一下。 一&#xff1a;环境安装 安装JRE环境&#xff0c;如已安装可跳过。这里简…

携手华为,微想科技正式启动“720云”鸿蒙原生应用开发

5月31日&#xff0c;"创兴汇聚&#xff0c;干帆领航”——“走进华为”系列活动北京站&#xff0c;在华为北京研究所举行&#xff0c;华为战略研究院、华为开发者联盟承办。微想科技创始人兼CEO刘博受邀出席此次会议&#xff0c;并在鸿蒙原生系统启动仪式上与华为达成合作…

展厅型材设计的关键要点

1、材质选择 在展厅型材设计中&#xff0c;材质选择是至关重要的一步。不同的材质会给人带来不同的视觉感受和触感体验。常见的展厅型材材质包括木材、金属、玻璃、塑料等&#xff0c;设计师需要根据展览的主题和风格选择合适的材质进行搭建&#xff0c;以达到最佳的展示效果。…

FullCalendar日历组件集成实战(9)

背景 有一些应用系统或应用功能&#xff0c;如日程管理、任务管理需要使用到日历组件。虽然Element Plus也提供了日历组件&#xff0c;但功能比较简单&#xff0c;用来做数据展现勉强可用。但如果需要进行复杂的数据展示&#xff0c;以及互动操作如通过点击添加事件&#xff0…

成都石室中学学子游汶鑫展现新时代好少年风采 拾金不昧获表彰

在繁华的都市中,每天都有无数的故事在上演,而其中的一些故事,却以其独特的温暖和正能量,深深打动着我们的心灵。近日,成都石室中学初中学校的一名学生游汶鑫同学,就用他的实际行动,诠释了新时代好少年的风采,展现了中华民族传统美德在当代青少年身上的生动体现。 成都石室中学初…

Sui公布第四批学术研究奖结果,新增100万美金资金支持

Sui基金会很高兴宣布第四批Sui学术研究奖的获奖者&#xff0c;该计划资助推动Web3发展的前沿研究&#xff0c;特别关注区块链技术、智能合约编程和基于Sui的平台产品。 在这一批中&#xff0c;我们接受了来自加州大学伯克利分校、耶鲁大学、纽约大学、洛桑联邦理工学院和新加坡…

【Arduino】ADC模拟量输入

目录 1、模拟信号 2、ADC输入 analogRead()函数 analogReadMillivolts analogReadResolution 3、示例 1、模拟信号 生活中&#xff0c;接触到的大多数信号都是模拟信号&#xff0c;如声音、温度的变化等。如图1所示&#xff0c;模拟信号是用连续变化的物理量表示的信息&…

【QT】记录一次QT程序发布exe过程

记录一次QT程序发布exe过程 使用windeploy与enigma发布独立的QT程序第一步 QT编译输出 **release** 版本第二步 QT 自带 windepoyqt 补全链接库第三步 enigma virtual box压缩打包为单一exe最后【2024-06-07 17】- 【补充】 贴一个自己用的bat脚本【**QtDeploy2exe.bat**】半自…

操作系统复习-Linux的文件系统

文件系统概述 FAT FAT(File Allocation Table)FAT16、FAT32等&#xff0c;微软Dos/Windows使用的文件系统使用一张表保存盘块的信息 NTFS NTFS (New Technology File System)WindowsNT环境的文件系统NTFS对FAT进行了改进&#xff0c;取代了日的文件系统 EXT EXT(Extended…

SAP 服务提供者 (Services Provider)接口测试笔记

文章目录 SAP 服务提供者 &#xff08;Services Provider&#xff09;接口测试笔记设置Content-Type授权SAP接口测试-SoapUI参数配置 SAP 服务提供者 &#xff08;Services Provider&#xff09;接口测试笔记 现在我在SAP里面公布了一些查询接口&#xff0c;现在就是要用SoapU…

Unity3D测量面积和角度实现方法(二)

系列文章目录 unity工具 文章目录 系列文章目录&#x1f449;前言&#x1f449;一、unity测量面积&#x1f449;1-1 视频效果&#x1f449;1-2 先创建预制体&#x1f449;1-3 在创建LineRenderer预制体&#x1f449;1-4 代码如下 &#x1f449;二、测量平面和测量空间切换&…

AIGC绘画设计基础:AI-MidJourney关键词大全+万能架构+保姆级教程(建议收藏!)

随着 ChatGPT 的热度席卷全网&#xff0c;越来越多人开始关注 AIGC&#xff08;AI Generated Content&#xff09;的相关应用。 其中Midjourney 作为一款强大的 AI 图像生成工具&#xff0c;与其他AI图像生成相比&#xff0c;Midjourney学习成本更低&#xff0c;生成速度更快&a…

Unity年中大促618活动又来了3折模板特效角色动画插件工具FPS生存建造模板RPG和2D素材优惠码UNITY6182024限时20240611

独立游戏开发需要找各种美术资源和模板&#xff0c;可以在低价时看看&#xff0c;节省开发时间。 Unity年中大促618活动又来了3折模板特效角色动画插件工具FPS生存建造模板RPG和2D素材优惠码UNITY6182024限时202406111104 300 款Unity引擎适配资源 3 折特惠&#xff0c;结账时输…

拥抱开源,构建未来:王嘉树与 TDengine 的开源之旅

在当代的技术浪潮中&#xff0c;开源文化不仅催生了无数创新技术&#xff0c;也为广大技术爱好者提供了一个展示才华、相互学习的平台。我们今天采访到的这位北京邮电大学电子工程学院的研究生&#xff0c;就是在这样的背景下&#xff0c;通过开源活动不断探索、学习并实现自我…

耐用充电宝有哪些?优质充电宝到底选哪个?良心推荐!

在电量即生产力的现今时代&#xff0c;如何为移动设备寻找一位最佳的伴侣呢&#xff1f;一款耐用、优质的充电宝无疑是你的不二之选。今天我们将带您揭开市场隐藏的一面&#xff0c;揭示哪些充电宝品牌真正代表了耐用与品质的标杆。让我们一起深入了解并选购最适合自己的充电宝…

合法二叉搜索树

题目链接 合法二叉搜索树 题目描述 注意点 无 解答思路 第一个思路是将中序遍历&#xff0c;并将遍历到的节点的值存储到队列中&#xff0c;根据队列先进先出的特点将每次弹出的元素与其前面的值进行比较&#xff0c;如果队列是按照从小到大进行排序的&#xff0c;说明该树…

CATIA入门操作案例——创成式曲面设计案例,吹风机的绘制,多截面曲面的绘制,曲面偏移和修剪

目录 引出画吹风机吹风机壳体多截面曲面吹风机后壳桥接曲面吹风机把手多截面曲面进行曲面的修剪绘制把手的后盖绘制内凹的圆曲面进入零件工作台&#xff0c;定义厚曲面绘制进气凹槽 总结异形弹簧新建几何体草图编辑&#xff0c;画一条样条线进行扫掠&#xff0c;圆心和半径画出…

基于机器学习的电池剩余使用寿命RUL预测

​代码较为简单。 import numpy as np # linear algebraimport pandas as pd # data processing, CSV file I/O (e.g. pd.read_csv)import os import matplotlib.pyplot as pltimport seaborn as sns%matplotlib inline​import warningswarnings.filterwarnings(ignore) df…

2024年6月8日,骑行杨柳冲峡谷:一场心灵与自然的交响曲

引言&#xff1a;寻找生活的节奏在这个快节奏的时代&#xff0c;我们常常迷失在都市的喧嚣中&#xff0c;忘记了如何聆听内心的声音。2024年6月8日&#xff0c;我与一群志同道合的校卡骑行群骑友&#xff0c;踏上了前往杨柳冲峡谷的旅程&#xff0c;这不仅仅是一次简单的户外活…

C++ BFS相关题目

目录 图像渲染 岛屿数量 图像渲染 733. 图像渲染 vis就是标记1有没有被用过 符合条件的都放到队列里&#xff0c;每次出队列一个&#xff0c;判四个&#xff0c; 如果要改的值与当前的值相同直接返回 注意&#xff1a;image[x][y] prev要放在坐标判断的后面&#xff…