日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

使用DotNetty编写跨平台网络通信程序

發(fā)布時(shí)間:2023/12/4 编程问答 37 豆豆
生活随笔 收集整理的這篇文章主要介紹了 使用DotNetty编写跨平台网络通信程序 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

長久以來,.Net開發(fā)人員都非常羨慕Java有Netty這樣,高效,穩(wěn)定又易用的網(wǎng)絡(luò)通信基礎(chǔ)框架。終于微軟的Azure團(tuán)隊(duì),使用C#實(shí)現(xiàn)的Netty的版本發(fā)布。不但使用了C#和.Net平臺(tái)的技術(shù)特點(diǎn),并且保留了Netty原來絕大部分的編程接口。讓我們?cè)谑褂脮r(shí),完全可以依照Netty官方的教程來學(xué)習(xí)和使用DotNetty應(yīng)用程序。
DotNetty同時(shí)也是開源的,它的源代碼托管在Github上:https://github.com/azure/dotnetty

0x01 項(xiàng)目預(yù)覽

從github上下載最新的代碼到本地,使用VS2017或者VSCode打開下載好的代碼,可以看到如圖所示的代碼那結(jié)構(gòu),其中源碼部分有9個(gè)項(xiàng)目組成,其中

DotNetty.Common 是公共的類庫項(xiàng)目,包裝線程池,并行任務(wù)和常用幫助類的封裝
DotNetty.Transport 是DotNetty核心的實(shí)現(xiàn)
DotNetty.Buffers 是對(duì)內(nèi)存緩沖區(qū)管理的封裝
DotNetty.Codes 是對(duì)編解碼是封裝,包括一些基礎(chǔ)基類的實(shí)現(xiàn),我們?cè)陧?xiàng)目中自定義的協(xié)議,都要繼承該項(xiàng)目的特定基類和實(shí)現(xiàn)
DotNetty.Handlers 封裝了常用的管道處理器,比如Tls編解碼,超時(shí)機(jī)制,心跳檢查,日志等,如果項(xiàng)目中沒有用到可以不引用,不過一般都會(huì)用到
其他還有對(duì)Redis的編解碼,Mqtt的編解碼,Protobuf2/3的編解碼項(xiàng)目中可根據(jù)實(shí)際情況引用
很遺憾Http協(xié)議和Websocket協(xié)議還沒有實(shí)現(xiàn)。

0x02 快速開始-示例-回聲程序的實(shí)現(xiàn)

從上一步下載的代碼中,看到有一個(gè)sample目錄,有很多例子,都大同小異, 先來看這個(gè)最簡單的Echo服務(wù)的實(shí)現(xiàn)吧.
Echo服務(wù),分為服務(wù)端和客戶端,服務(wù)端使用DotNetty框架啟動(dòng)一個(gè)Socket服務(wù),并等待客戶端鏈接,當(dāng)客戶端鏈接并接收客戶端消息,并將接收到的消息原樣返回給客戶端。而客戶端同樣使用DotNetty框架啟動(dòng)一個(gè)Socket客戶端服務(wù),并鏈接到服務(wù)端,并發(fā)送一條Hello的字符串信息,并等待服務(wù)端返回。如此往復(fù)。

2.1 Echo Server

來一起看一下代碼吧,我把注釋都寫到代碼中:

static async Task RunServerAsync(){ ? ?
//設(shè)置輸出日志到ConsoleExampleHelper.SetConsoleLogger();
?// 主工作線程組,設(shè)置為1個(gè)線程var bossGroup = new MultithreadEventLoopGroup(1); ?
??// 工作線程組,默認(rèn)為內(nèi)核數(shù)*2的線程數(shù)var workerGroup = new MultithreadEventLoopGroup();X509Certificate2 tlsCertificate = null; ?
???if (ServerSettings.IsSsl) //如果使用加密通道
???{ ? ? ? ?
??? ?tlsCertificate = new X509Certificate2(Path.Combine(ExampleHelper.ProcessDirectory, "dotnetty.com.pfx"), "password");} ? ? ? ? ? ?try{ ? ? ? ? ? ? ? ?//聲明一個(gè)服務(wù)端Bootstrap,每個(gè)Netty服務(wù)端程序,都由ServerBootstrap控制,//通過鏈?zhǔn)降姆绞浇M裝需要的參數(shù)var bootstrap = new ServerBootstrap();bootstrap.Group(bossGroup, workerGroup) // 設(shè)置主和工作線程組.Channel<TcpServerSocketChannel>() // 設(shè)置通道模式為TcpSocket.Option(ChannelOption.SoBacklog, 100) // 設(shè)置網(wǎng)絡(luò)IO參數(shù)等,這里可以設(shè)置很多參數(shù),當(dāng)然你對(duì)網(wǎng)絡(luò)調(diào)優(yōu)和參數(shù)設(shè)置非常了解的話,你可以設(shè)置,或者就用默認(rèn)參數(shù)吧.Handler(new LoggingHandler("SRV-LSTN")) //在主線程組上設(shè)置一個(gè)打印日志的處理器.ChildHandler(new ActionChannelInitializer<ISocketChannel>(channel =>{ //工作線程連接器 是設(shè)置了一個(gè)管道,服務(wù)端主線程所有接收到的信息都會(huì)通過這個(gè)管道一層層往下傳輸//同時(shí)所有出棧的消息 也要這個(gè)管道的所有處理器進(jìn)行一步步處理IChannelPipeline pipeline = channel.Pipeline; ? ? ? ? ? ? ? ? ? ? ? ?if (tlsCertificate != null) //Tls的加解密{pipeline.AddLast("tls", TlsHandler.Server(tlsCertificate));} ? ? ? ? ? ? ? ? ? ? ? ?//日志攔截器pipeline.AddLast(new LoggingHandler("SRV-CONN"));//出棧消息,通過這個(gè)handler 在消息頂部加上消息的長度pipeline.AddLast("framing-enc", new LengthFieldPrepender(2));//入棧消息通過該Handler,解析消息的包長信息,并將正確的消息體發(fā)送給下一個(gè)處理Handler,該類比較常用,后面單獨(dú)說明pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(ushort.MaxValue, 0, 2, 0, 2));//業(yè)務(wù)handler ,這里是實(shí)際處理Echo業(yè)務(wù)的Handlerpipeline.AddLast("echo", new EchoServerHandler());})); ? ? ? ? ? ?// bootstrap綁定到指定端口的行為 就是服務(wù)端啟動(dòng)服務(wù),同樣的Serverbootstrap可以bind到多個(gè)端口IChannel boundChannel = await bootstrap.BindAsync(ServerSettings.Port);Console.ReadLine();//關(guān)閉服務(wù)await boundChannel.CloseAsync();} ? ? ? ? ? ?finally{//釋放工作組線程await Task.WhenAll(bossGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)),workerGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)));}}

來看下實(shí)際的業(yè)務(wù)代碼,比較簡單,也就是打印日志,并返回收到的字符串

public class EchoServerHandler : ChannelHandlerAdapter //管道處理基類,較常用{// ?重寫基類的方法,當(dāng)消息到達(dá)時(shí)觸發(fā),這里收到消息后,在控制臺(tái)輸出收到的內(nèi)容,并原樣返回了客戶端public override void ChannelRead(IChannelHandlerContext context, object message) ? ? ? ?{ ? ? ? ? ? ?var buffer = message as IByteBuffer; ? ? ? ? ? ?if (buffer != null){Console.WriteLine("Received from client: " + buffer.ToString(Encoding.UTF8));}context.WriteAsync(message);//寫入輸出流}// 輸出到客戶端,也可以在上面的方法中直接調(diào)用WriteAndFlushAsync方法直接輸出public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush();//捕獲 異常,并輸出到控制臺(tái)后斷開鏈接,提示:客戶端意外斷開鏈接,也會(huì)觸發(fā)public override void ExceptionCaught(IChannelHandlerContext context, Exception exception) ? ? ? ?{Console.WriteLine("Exception: " + exception);context.CloseAsync();}}

2.2 Echo Client

客戶端的代碼和服務(wù)端的代碼相差很少,體現(xiàn)了Netty統(tǒng)一的編程模型。有幾個(gè)不同點(diǎn):

  • 客戶端的Bootstrap不是ServerBootstrap了,

  • 客戶端不需要主線程組,只有工作線程組,消息處理管道也建立在里主線程工作組的攔截通道上。

  • 最后不是bind而是connect

  • static async Task RunClientAsync() ? ? ? ?{ExampleHelper.SetConsoleLogger(); ? ?
    ? ? ? ?var group = new MultithreadEventLoopGroup();X509Certificate2 cert = null; ? ? ?
    ? ? ? ? ? ? string targetHost = null; ? ? ? ?
    ? ? ? ? ? ? if (ClientSettings.IsSsl){cert = new X509Certificate2(Path.Combine(ExampleHelper.ProcessDirectory, "dotnetty.com.pfx"), "password");targetHost = cert.GetNameInfo(X509NameType.DnsName, false);} ? ? ? ? ? ?try{ ? ? ? ? ? ?
    ? ? ? ? ? ? ?? ?var bootstrap = new Bootstrap();bootstrap.Group(group).Channel<TcpSocketChannel>().Option(ChannelOption.TcpNodelay, true).Handler(new ActionChannelInitializer<ISocketChannel>(channel =>{IChannelPipeline pipeline = channel.Pipeline; ? ? ? ? ? ? ? ? ? ? ? ?if (cert != null){pipeline.AddLast("tls", new TlsHandler(stream => new SslStream(stream, true, (sender, certificate, chain, errors) => true), new ClientTlsSettings(targetHost)));}pipeline.AddLast(new LoggingHandler());pipeline.AddLast("framing-enc", new LengthFieldPrepender(2));pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(ushort.MaxValue, 0, 2, 0, 2));pipeline.AddLast("echo", new EchoClientHandler());}));IChannel clientChannel = await bootstrap.ConnectAsync(new IPEndPoint(ClientSettings.Host, ClientSettings.Port));Console.ReadLine(); ? ? ? ? ? ?
    ? ?await clientChannel.CloseAsync();} ? ? ? ? ? ?finally{ ? ? ? ? ? ?
    ? ?? ?await group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1));}}

    業(yè)務(wù)代碼

    // 代碼和服務(wù)端也相差不多,并且繼承了同樣的基類。public class EchoClientHandler : ChannelHandlerAdapter{ ? ?
    ? ?readonly IByteBuffer initialMessage; ?
    ? ?
    ? ? ? ?public EchoClientHandler() ? ? ?
    ? ? ? ? ??
    { ? ?
    ? ? ? ? ?? ? ? ?this.initialMessage = Unpooled.Buffer(ClientSettings.Size); ? ? ? ? ? ?byte[] messageBytes = Encoding.UTF8.GetBytes("Hello world"); ? ? ? ? ? ?this.initialMessage.WriteBytes(messageBytes);} ?
    ? ? ?
    ? ? ? ?//重寫基類方法,當(dāng)鏈接上服務(wù)器后,馬上發(fā)送Hello World消息到服務(wù)端public override void ChannelActive(IChannelHandlerContext context) => context.WriteAndFlushAsync(this.initialMessage); ? ? ? ?public override void ChannelRead(IChannelHandlerContext context, object message) ? ? ? ?{ ? ? ? ? ? ?var byteBuffer = message as IByteBuffer; ? ? ? ? ? ?if (byteBuffer != null){Console.WriteLine("Received from server: " + byteBuffer.ToString(Encoding.UTF8));}context.WriteAsync(message);} ? ?
    ? ? ? ?public override void ChannelReadComplete(IChannelHandlerContext context) =>
    ? ? ? ?context.Flush(); ? ? ?
    ? ? ? ?
    ? ? ? ??public override void ExceptionCaught(IChannelHandlerContext context,
    Exception exception
    ) ? ? ? ?
    {Console.WriteLine("Exception: " + exception);context.CloseAsync();}}

    0x03 常用Handler和基類

    從Echo服務(wù)的例子中,我們可以看到Netty程序不管時(shí)服務(wù)端還是客戶端都通過一個(gè)Bootstrap/ServerBootstrap來啟動(dòng)Socket程序,并通過設(shè)置處理Handler管道來處理出入的消息,管道中常見的攔截器有加解密,日志記錄,編解碼,消息頭處理,業(yè)務(wù)處理等,實(shí)際業(yè)務(wù)中根據(jù)情況可以自行添加自己的業(yè)務(wù)邏輯,同時(shí)很多處理器代碼在服務(wù)端和客戶端是公用的,Netty本身已經(jīng)提供了一些常用處理器和業(yè)務(wù)處理器的基類來簡化實(shí)際開發(fā),我們一起看一下

    3.1 TlsHandler

    Netty支持Tls加密傳輸,TlsHandler類可以在開發(fā)人員無須關(guān)心加密傳輸時(shí)字節(jié)碼的變化,只關(guān)心自己的業(yè)務(wù)代碼即可。在管道處理的第一個(gè)配置該類即可

    3.2 LengthFieldPrepender

    這個(gè)handler 會(huì)在實(shí)際發(fā)送前在將數(shù)據(jù)的長度放置在數(shù)據(jù)前,本例中使用2個(gè)字節(jié)來存儲(chǔ)數(shù)據(jù)的長度。

    3.3 LengthFieldBasedFrameDecoder

    這個(gè)handler比較常用,會(huì)在解碼前用于解析數(shù)據(jù),用于讀取數(shù)據(jù)包的頭信息,特別是包長,并等待數(shù)據(jù)達(dá)到包長后再交由下一個(gè)handler處理。
    參數(shù)說明 以下是Amp協(xié)議的參數(shù)值,并注釋了意義

    InitialBytesToStrip = 0, //讀取時(shí)需要跳過的字節(jié)數(shù)
    LengthAdjustment = -5, //包實(shí)際長度的糾正,如果包長包括包頭和包體,則要減去Length之前的部分
    LengthFieldLength = 4, //長度字段的字節(jié)數(shù) 整型為4個(gè)字節(jié)
    LengthFieldOffset = 1, //長度屬性的起始(偏移)位
    MaxFrameLength = int.MaxValue, // 最大包長

    3.4 ChannelHandlerAdapter和SimpleChannelInboundHandler

    業(yè)務(wù)處理的常用Handler基類,一般客戶端和服務(wù)端的業(yè)務(wù)處理handler 都要繼承這個(gè)這兩個(gè)類,其中SimpleChannelInboundHandler 是ChannelHandlerAdapter的子類,對(duì)其簡單的進(jìn)行封裝,并進(jìn)行了類型檢查。

    3.5 IdleStateHandler 鏈接狀態(tài)檢查handler

    這個(gè)handler一般用于檢查鏈接的狀態(tài),比如寫超時(shí),讀超時(shí)。在實(shí)際項(xiàng)目中一般在客戶端添加它,并用于發(fā)送心跳包。

    以下是DotBPE在客戶端管道中 第一個(gè)添加IdleStateHandler 并設(shè)置觸發(fā)時(shí)間

    var bootstrap = new Bootstrap();bootstrap.Channel<TcpSocketChannel>().Option(ChannelOption.TcpNodelay, true).Option(ChannelOption.ConnectTimeout, TimeSpan.FromSeconds(3)).Group(new MultithreadEventLoopGroup()).Handler(new ActionChannelInitializer<ISocketChannel>(c =>{ ? ? ? ? ? ? ? ? ? ?var pipeline = c.Pipeline;pipeline.AddLast(new LoggingHandler("CLT-CONN"));MessageMeta meta = _msgCodecs.GetMessageMeta(); ? ? ? ? ? ? ? ? ? ?// IdleStateHandlerpipeline.AddLast("timeout", new IdleStateHandler(0, 0,
    meta.HeartbeatInterval / 1000)); ? ? ? ? ? ? ? ? ?
    ??//消息前處理pipeline.AddLast( ? ? ? ? ? ? ?
    ? ? ? ? ?new LengthFieldBasedFrameDecoder(meta.MaxFrameLength,meta.LengthFieldOffset,meta.LengthFieldLength,meta.LengthAdjustment,meta.InitialBytesToStrip));pipeline.AddLast(new ChannelDecodeHandler<TMessage>(_msgCodecs));pipeline.AddLast(new ClientChannelHandlerAdapter<TMessage>(this));})); ? ? ? ? ? ?return bootstrap;

    然后在業(yè)務(wù)處理handler中處理UserEventTriggered事件

    //ChannelHandlerAdapter 重寫UserEventTriggered

    public override void UserEventTriggered(IChannelHandlerContext context, object evt){
    ?if(evt is IdleStateEvent){ ? ?
    ? var eventState = evt as IdleStateEvent;
    ? ? ? if(eventState !=null){ ? ? ?
    ? ? ? ??this._bootstrap.SendHeartbeatAsync(context,eventState);}} }

    更多細(xì)節(jié)可以參考 《Netty 4.x 用戶指南》

    相關(guān)文章:?

    • 基于DotNet Core的RPC框架(一) DotBPE.RPC快速開始

    原文地址:http://www.cnblogs.com/xuanye/p/dotnetty-quickstart.html


    .NET社區(qū)新聞,深度好文,微信中搜索dotNET跨平臺(tái)或掃描二維碼關(guān)注

    總結(jié)

    以上是生活随笔為你收集整理的使用DotNetty编写跨平台网络通信程序的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

    如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。