最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

C#優(yōu)雅處理TCP數(shù)據(jù)的實(shí)現(xiàn)

 更新時(shí)間:2025年09月08日 14:19:17   作者:子丶不語(yǔ)  
本文主要介紹了C#優(yōu)雅處理TCP數(shù)據(jù)的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧

前言

Tcp是一個(gè)面向連接的流數(shù)據(jù)傳輸協(xié)議,用人話說(shuō)就是傳輸是一個(gè)已經(jīng)建立好連接的管道,數(shù)據(jù)都在管道里像流水一樣流淌到對(duì)端。

那么數(shù)據(jù)必然存在幾個(gè)問(wèn)題,比如數(shù)據(jù)如何持續(xù)的讀取,數(shù)據(jù)包的邊界等。

Nagle's算法

Nagle 算法的核心思想是,在一個(gè) TCP 連接上,最多只能有一個(gè)未被確認(rèn)的小數(shù)據(jù)包(小于 MSS,即最大報(bào)文段大小)

優(yōu)勢(shì)

減少網(wǎng)絡(luò)擁塞:通過(guò)合并小數(shù)據(jù)包,減少了網(wǎng)絡(luò)中的數(shù)據(jù)包數(shù)量,降低了擁塞的可能性。

提高網(wǎng)絡(luò)效率:在低速網(wǎng)絡(luò)中,Nagle 算法可以顯著提高傳輸效率。

劣勢(shì)

增加延遲:在交互式應(yīng)用中,Nagle 算法可能導(dǎo)致顯著的延遲,因?yàn)樗却?ACK 或合并數(shù)據(jù)包。

C#中如何配置?

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

連接超時(shí)

在調(diào)用客戶端Socket連接服務(wù)器的時(shí)候,可以設(shè)置連接超時(shí)機(jī)制,具體可以傳入一個(gè)任務(wù)的取消令牌,并且設(shè)置超時(shí)時(shí)間。

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

SSL加密傳輸

TCP使用SSL加密傳輸,通過(guò)非對(duì)稱加密的方式,利用證書(shū),保證雙方使用了安全的密鑰加密了報(bào)文。在C#中如何配置?

服務(wù)端配置

//創(chuàng)建證書(shū)對(duì)象
var _certificate  = _certificate = new X509Certificate2(_options.PfxCertFilename, _options.PfxPassword);

//與客戶端進(jìn)行驗(yàn)證
if (allowingUntrustedSSLCertificate) //是否允許不受信任的證書(shū)
{
    SslStream = new SslStream(NetworkStream, false,
        (obj, certificate, chain, error) => true);
}
else
{
    SslStream = new SslStream(NetworkStream, false);
}

try
{
    //serverCertificate:用于對(duì)服務(wù)器進(jìn)行身份驗(yàn)證的 X509Certificate
    //clientCertificateRequired:一個(gè) Boolean 值,指定客戶端是否必須為身份驗(yàn)證提供證書(shū)
    //checkCertificateRevocation:一個(gè) Boolean 值,指定在身份驗(yàn)證過(guò)程中是否檢查證書(shū)吊銷列表
    await SslStream.AuthenticateAsServerAsync(new SslServerAuthenticationOptions()
    {
        ServerCertificate = x509Certificate,
        ClientCertificateRequired = mutuallyAuthenticate,
        CertificateRevocationCheckMode = checkCertificateRevocation ? X509RevocationMode.Online : X509RevocationMode.NoCheck
    }, cancellationToken).ConfigureAwait(false);

    if (!SslStream.IsEncrypted || !SslStream.IsAuthenticated)
    {
        returnfalse;
    }

    if (mutuallyAuthenticate && !SslStream.IsMutuallyAuthenticated)
    {
        returnfalse;
    }
}
catch (Exception)
{
    throw;
}

//完成驗(yàn)證后,通過(guò)SslStream傳輸數(shù)據(jù)
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)//是否允許不受信任的證書(shū)
    {
        _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))
    {
        thrownew InvalidOperationException("SSL authenticated faild!");
    }
}

KeepAlive

keepAlive不是TCP協(xié)議中的,而是各個(gè)操作系統(tǒng)本身實(shí)現(xiàn)的功能,主要是防止一些Socket突然斷開(kāi)后沒(méi)有被感知到,導(dǎo)致一直浪費(fèi)資源的情況。

其基本原理是在此機(jī)制開(kāi)啟時(shí),當(dāng)長(zhǎng)連接無(wú)數(shù)據(jù)交互一定時(shí)間間隔時(shí),連接的一方會(huì)向?qū)Ψ桨l(fā)送?;钐綔y(cè)包,如連接仍正常,對(duì)方將對(duì)此確認(rèn)回應(yīng)

C#中如何調(diào)用操作系統(tǒng)的KeepAlive?

/// <summary>
/// 開(kāi)啟Socket的KeepAlive
/// 設(shè)置tcp協(xié)議的一些KeepAlive參數(shù)
/// </summary>
/// <param name="socket"></param>
/// <param name="tcpKeepAliveInterval">沒(méi)有接收到對(duì)方確認(rèn),繼續(xù)發(fā)送KeepAlive的發(fā)送頻率</param>
/// <param name="tcpKeepAliveTime">KeepAlive的空閑時(shí)長(zhǎng),或者說(shuō)每次正常發(fā)送心跳的周期</param>
/// <param name="tcpKeepAliveRetryCount">KeepAlive之后設(shè)置最大允許發(fā)送?;钐綔y(cè)包的次數(shù),到達(dá)此次數(shù)后直接放棄嘗試,并關(guān)閉連接</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);
}

具體的開(kāi)啟,還需要看操作系統(tǒng)的版本以及不同操作系統(tǒng)的支持。

粘包斷包處理

Pipe & ReadOnlySequence

上圖來(lái)自微軟官方博客:https://devblogs.microsoft.com/dotnet/system-io-pipelines-high-performance-io-in-net/

TCP面向應(yīng)用是流式數(shù)據(jù)傳輸,所以接收端接到的數(shù)據(jù)是像流水一樣從管道中傳來(lái),每次取到的數(shù)據(jù)取決于應(yīng)用設(shè)置的緩沖區(qū)大小,以及套接字本身緩沖區(qū)待讀取字節(jié)數(shù)。

C#中提供的Pipe就如上圖一樣,是一個(gè)管道Pipe有兩個(gè)對(duì)象成員,一個(gè)是PipeWriter,一個(gè)是PipeReader,可以理解為一個(gè)是生產(chǎn)者,專門往管道里灌輸數(shù)據(jù)流,即字節(jié)流,一個(gè)是消費(fèi)者,專門從管道里獲取字節(jié)流進(jìn)行處理。

可以看到Pipe中的數(shù)據(jù)包是用鏈表關(guān)聯(lián)的,但是這個(gè)數(shù)據(jù)包是從Socke緩沖區(qū)每次取到的數(shù)據(jù)包,它不一定是一個(gè)完整的數(shù)據(jù)包,所以這些數(shù)據(jù)包連接起來(lái)后形成了一個(gè)C#提供的另外一個(gè)抽象的對(duì)象ReadOnlySequence。

但是這里還是沒(méi)有提供太好的處理斷包和粘包的辦法,因?yàn)閿喟嘲奶幚硇枰獌煞矫?/p>

1、業(yè)務(wù)數(shù)據(jù)包的定義

2、數(shù)據(jù)流切割出一個(gè)個(gè)完整的數(shù)據(jù)包

假設(shè)業(yè)務(wù)已經(jīng)定義好了數(shù)據(jù)包,那么我們?nèi)绾螐腜ipe中這些數(shù)據(jù)包根據(jù)業(yè)務(wù)定義來(lái)從不同的數(shù)據(jù)包中切割出一個(gè)完整的包,那么就需要ReadOnlySequence,它提供的操作方法,非常方便我們?nèi)デ懈顢?shù)據(jù),主要是頭尾數(shù)據(jù)包的切割。

假設(shè)我們業(yè)務(wù)層定義了一個(gè)數(shù)據(jù)包結(jié)構(gòu),數(shù)據(jù)包是不定長(zhǎng)的,包體長(zhǎng)度每次都寫(xiě)在包頭里,我們來(lái)實(shí)現(xiàn)一個(gè)數(shù)據(jù)包過(guò)濾器。

//收到消息
 while (!_receiveDataTokenSource.Token.IsCancellationRequested)
 {
     try
     {
        //從pipe中獲取緩沖區(qū)
         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);
             //告知消費(fèi)者,往Pipe的管道中寫(xiě)入了多少字節(jié)數(shù)據(jù)
             _pipeWriter.Advance(readCount);
         }
         else
         {
             if (IsDisconnect())
             {
                 await DisConnectAsync();
             }

             thrownew 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();
//消費(fèi)者處理數(shù)據(jù)
while (!_lifecycleTokenSource.Token.IsCancellationRequested)
 {
     ReadResult result = await _pipeReader.ReadAsync();
     ReadOnlySequence<byte> buffer = result.Buffer;
     ReadOnlySequence<byte> data;
     do
     {
        //通過(guò)過(guò)濾器得到一個(gè)完整的包
         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>
/// 解析數(shù)據(jù)包
/// 固定報(bào)文頭解析協(xié)議
/// </summary>
/// <param name="headerSize">數(shù)據(jù)報(bào)文頭的大小</param>
/// <param name="bodyLengthIndex">數(shù)據(jù)包大小在報(bào)文頭中的位置</param>
/// <param name="bodyLengthBytes">數(shù)據(jù)包大小在報(bào)文頭中的長(zhǎng)度</param>
/// <param name="IsLittleEndian">數(shù)據(jù)報(bào)文大小端。windows中通常是小端,unix通常是大端模式</param>
/// </summary>
/// <param name="sequence">一個(gè)完整的業(yè)務(wù)數(shù)據(jù)包</param>
public override ReadOnlySequence<byte> ResolvePackage(ref ReadOnlySequence<byte> sequence)
{
    var len = sequence.Length;
    if (len < _bodyLengthIndex) returndefault;
    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)
            returndefault;

        var endPosition = sequence.GetPosition(_headerSize + bodyLength);
        var data = sequence.Slice(0, endPosition);//得到完整數(shù)據(jù)包
        sequence = sequence.Slice(endPosition);//緩沖區(qū)中去除取到的完整包

        return data;
    }
    finally
    {
        ArrayPool<byte>.Shared.Return(bodyLengthBytes);
    }
}

以上就是實(shí)現(xiàn)了固定數(shù)據(jù)包頭實(shí)現(xiàn)粘包斷包處理的部分代碼。

關(guān)于TCP的連接還有一些,比如客戶端連接限制,空閑連接關(guān)閉等。

如果大家對(duì)于完整代碼感興趣,可以看我剛寫(xiě)的一個(gè)TCP庫(kù)

EasyTcp4Net

https://github.com/BruceQiu1996/EasyTcp4Net

到此這篇關(guān)于C#優(yōu)雅處理TCP數(shù)據(jù)的實(shí)現(xiàn)的文章就介紹到這了,更多相關(guān)C# TCP數(shù)據(jù)內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

您可能感興趣的文章:

相關(guān)文章

  • C#使用NPOI設(shè)置Excel下拉選項(xiàng)

    C#使用NPOI設(shè)置Excel下拉選項(xiàng)

    這篇文章主要為大家詳細(xì)介紹了C#使用NPOI設(shè)置Excel下拉選項(xiàng),文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-02-02
  • C#?Timer控件學(xué)習(xí)之使用Timer解決按鈕冪等性問(wèn)題

    C#?Timer控件學(xué)習(xí)之使用Timer解決按鈕冪等性問(wèn)題

    Timer控件又稱定時(shí)器控件或計(jì)時(shí)器控件,該控件的主要作用是按一定的時(shí)間間隔周期性地觸發(fā)一個(gè)名為Tick的事件,因此在該事件的代碼中可以放置一些需要每隔一段時(shí)間重復(fù)執(zhí)行的程序段,這篇文章主要介紹了關(guān)于C#使用Timer解決按鈕冪等性問(wèn)題的相關(guān)資料,需要的朋友可以參考下
    2022-10-10
  • C#實(shí)現(xiàn)隨機(jī)洗牌的方法

    C#實(shí)現(xiàn)隨機(jī)洗牌的方法

    這篇文章主要介紹了C#實(shí)現(xiàn)隨機(jī)洗牌的方法,涉及隨機(jī)數(shù)操作技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下
    2015-02-02
  • 再議C#中的裝箱與拆箱的問(wèn)題詳解

    再議C#中的裝箱與拆箱的問(wèn)題詳解

    本篇文章再次介紹了C#中的裝箱與拆箱,這次們看下使用泛型和不使用泛型引發(fā)裝箱拆箱的情況
    2013-05-05
  • C#實(shí)現(xiàn)十字鏈表的使用示例

    C#實(shí)現(xiàn)十字鏈表的使用示例

    十字鏈表是一種將數(shù)據(jù)存儲(chǔ)在節(jié)點(diǎn)中的數(shù)據(jù)結(jié)構(gòu),每個(gè)節(jié)點(diǎn)包含兩個(gè)指針,分別指向下一個(gè)節(jié)點(diǎn)和上一個(gè)節(jié)點(diǎn),通過(guò)定義節(jié)點(diǎn)類和鏈表類,實(shí)現(xiàn)十字鏈表的創(chuàng)建、遍歷、插入和刪除等操作,本文就來(lái)實(shí)現(xiàn)一下
    2023-11-11
  • C# CancellationToken和CancellationTokenSource的用法詳解

    C# CancellationToken和CancellationTokenSource的用法詳解

    做了.net core之后,發(fā)現(xiàn)CancellationToken用的越來(lái)越平凡了。這也難怪,原來(lái).net framework使用異步的不是很多,而.net core首推異步編程,到處可以看到Task的影子,而CancellationToken正好是異步Task的一個(gè)控制器,所以花點(diǎn)時(shí)間做個(gè)筆記
    2021-06-06
  • 詳解c# PLINQ中的分區(qū)

    詳解c# PLINQ中的分區(qū)

    這篇文章主要介紹了c# PLINQ中的分區(qū)的相關(guān)資料,文中示例代碼非常詳細(xì),幫助大家更好的理解和學(xué)習(xí),感興趣的朋友可以了解下
    2020-07-07
  • C#輕松實(shí)現(xiàn)增加和刪除PowerPoint幻燈片

    C#輕松實(shí)現(xiàn)增加和刪除PowerPoint幻燈片

    在批量生成報(bào)告、動(dòng)態(tài)拼接演示文稿或自動(dòng)化處理 PPT 模板時(shí),通過(guò)代碼動(dòng)態(tài)增刪幻燈片幾乎是繞不開(kāi)的需求,本文將以免費(fèi)庫(kù) Free Spire.Presentation for .NET 為例,提供一個(gè)可直接落地的技術(shù)方案,希望對(duì)大家有所幫助
    2026-05-05
  • C#的this關(guān)鍵字的2種用法

    C#的this關(guān)鍵字的2種用法

    這篇文章主要給大家分享的是C#的this關(guān)鍵字的2種用法,在使用C#的過(guò)程中,發(fā)現(xiàn)this關(guān)鍵是比較少用的,但是在下面這二個(gè)場(chǎng)合下是必須要使用的,不使用它是解決不了問(wèn)題。下面我們就來(lái)看看文章的具體內(nèi)容吧
    2021-10-10
  • Python設(shè)計(jì)模式編程中的備忘錄模式與對(duì)象池模式示例

    Python設(shè)計(jì)模式編程中的備忘錄模式與對(duì)象池模式示例

    這篇文章主要介紹了Python設(shè)計(jì)模式編程中的備忘錄模式與對(duì)象池模式,文中分別舉了表單和線程的相關(guān)示例,需要的朋友可以參考下
    2016-02-02

最新評(píng)論

灌云县| 华宁县| 清苑县| 江达县| 奉贤区| 泌阳县| 新河县| 瑞金市| 炉霍县| 隆林| 姜堰市| 新郑市| 尉犁县| 太谷县| 平阳县| 竹溪县| 黄冈市| 文昌市| 乐昌市| 灌阳县| 崇州市| 商南县| 盐池县| 九寨沟县| 晋中市| 中卫市| 武隆县| 利津县| 滁州市| 朝阳市| 大厂| 皋兰县| 泰宁县| 兴城市| 那坡县| 霍林郭勒市| 东山县| 仲巴县| 高青县| 理塘县| 南涧|