(DotNetty的框架和實現是怎麼回事,筆者不太清楚,但徹底可參考Netty官方的文檔來學習和使用DotNetty相關的API接口)nginx
1 /* 2 * Netty 是一個半成品,做用是在須要基於自定義協議的基礎上完成本身的通訊封裝 3 * Netty 大大簡化了網絡程序的開發過程好比 TCP 和 UDP 的 socket 服務的開發。 4 * 「快速和簡單」並不意味着應用程序會有難維護和性能低的問題, 5 * Netty 是一個精心設計的框架,它從許多協議的實現中吸取了不少的經驗好比 FTP、SMTP、HTTP、許多二進制和基於文本的傳統協議。 6 * 所以,Netty 已經成功地找到一個方式,在不失靈活性的前提下來實現開發的簡易性,高性能,穩定性。 7 */ 8 9 namespace Echo.Server 10 { 11 using System; 12 using System.Threading.Tasks; 13 using DotNetty.Codecs; 14 using DotNetty.Handlers.Logging; 15 using DotNetty.Transport.Bootstrapping; 16 using DotNetty.Transport.Channels; 17 using DotNetty.Transport.Libuv; 18 using Examples.Common; 19 20 static class Program 21 { 22 static async Task RunServerAsync() 23 { 24 ExampleHelper.SetConsoleLogger(); 25 26 // 申明一個主迴路調度組 27 var dispatcher = new DispatcherEventLoopGroup(); 28 29 /* 30 Netty 提供了許多不一樣的 EventLoopGroup 的實現用來處理不一樣的傳輸。 31 在這個例子中咱們實現了一個服務端的應用,所以會有2個 NioEventLoopGroup 會被使用。 32 第一個常常被叫作‘boss’,用來接收進來的鏈接。第二個常常被叫作‘worker’,用來處理已經被接收的鏈接,一旦‘boss’接收到鏈接,就會把鏈接信息註冊到‘worker’上。 33 如何知道多少個線程已經被使用,如何映射到已經建立的 Channel上都須要依賴於 IEventLoopGroup 的實現,而且能夠經過構造函數來配置他們的關係。 34 */ 35 36 // 主工做線程組,設置爲1個線程 37 IEventLoopGroup bossGroup = dispatcher; // (1) 38 // 子工做線程組,設置爲1個線程 39 IEventLoopGroup workerGroup = new WorkerEventLoopGroup(dispatcher); 40 41 try 42 { 43 // 聲明一個服務端Bootstrap,每一個Netty服務端程序,都由ServerBootstrap控制,經過鏈式的方式組裝須要的參數 44 var serverBootstrap = new ServerBootstrap(); // (2) 45 // 設置主和工做線程組 46 serverBootstrap.Group(bossGroup, workerGroup); 47 48 // 申明服務端通訊通道爲TcpServerChannel 49 serverBootstrap.Channel<TcpServerChannel>(); // (3) 50 51 serverBootstrap 52 // 設置網絡IO參數等 53 .Option(ChannelOption.SoBacklog, 100) // (5) 54 55 // 在主線程組上設置一個打印日誌的處理器 56 .Handler(new LoggingHandler("SRV-LSTN")) 57 58 // 設置工做線程參數 59 .ChildHandler( 60 /* 61 * ChannelInitializer 是一個特殊的處理類,他的目的是幫助使用者配置一個新的 Channel。 62 * 也許你想經過增長一些處理類好比DiscardServerHandler 來配置一個新的 Channel 或者其對應的ChannelPipeline 來實現你的網絡程序。 63 * 當你的程序變的複雜時,可能你會增長更多的處理類到 pipline 上,而後提取這些匿名類到最頂層的類上。 64 */ 65 new ActionChannelInitializer<IChannel>( // (4) 66 channel => 67 { 68 /* 69 * 工做線程鏈接器是設置了一個管道,服務端主線程全部接收到的信息都會經過這個管道一層層往下傳輸, 70 * 同時全部出棧的消息 也要這個管道的全部處理器進行一步步處理。 71 */ 72 IChannelPipeline pipeline = channel.Pipeline; 73 74 // 添加日誌攔截器 75 pipeline.AddLast(new LoggingHandler("SRV-CONN")); 76 77 // 添加出棧消息,經過這個handler在消息頂部加上消息的長度。 78 // LengthFieldPrepender(2):使用2個字節來存儲數據的長度。 79 pipeline.AddLast("framing-enc", new LengthFieldPrepender(2)); 80 81 /* 82 入棧消息經過該Handler,解析消息的包長信息,並將正確的消息體發送給下一個處理Handler 83 1,InitialBytesToStrip = 0, //讀取時須要跳過的字節數 84 2,LengthAdjustment = -5, //包實際長度的糾正,若是包長包括包頭和包體,則要減去Length以前的部分 85 3,LengthFieldLength = 4, //長度字段的字節數 整型爲4個字節 86 4,LengthFieldOffset = 1, //長度屬性的起始(偏移)位 87 5,MaxFrameLength = int.MaxValue, //最大包長 88 */ 89 pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(ushort.MaxValue, 0, 2, 0, 2)); 90 91 // 業務handler 92 pipeline.AddLast("echo", new EchoServerHandler()); 93 })); 94 95 // bootstrap綁定到指定端口的行爲就是服務端啓動服務,一樣的Serverbootstrap能夠bind到多個端口 96 IChannel boundChannel = await serverBootstrap.BindAsync(ServerSettings.Port); // (6) 97 98 Console.WriteLine("wait the client input"); 99 Console.ReadLine(); 100 101 // 關閉服務 102 await boundChannel.CloseAsync(); 103 } 104 finally 105 { 106 // 釋放指定工做組線程 107 await Task.WhenAll( // (7) 108 bossGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)), 109 workerGroup.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)) 110 ); 111 } 112 } 113 114 static void Main() => RunServerAsync().Wait(); 115 } 116 }
上一部分代碼中加粗地方的實現git
1 namespace Echo.Server 2 { 3 using System; 4 using System.Text; 5 using DotNetty.Buffers; 6 using DotNetty.Transport.Channels; 7 8 /// <summary> 9 /// 服務端處理事件函數 10 /// </summary> 11 public class EchoServerHandler : ChannelHandlerAdapter // ChannelHandlerAdapter 業務繼承基類適配器 // (1) 12 { 13 /// <summary> 14 /// 管道開始讀 15 /// </summary> 16 /// <param name="context"></param> 17 /// <param name="message"></param> 18 public override void ChannelRead(IChannelHandlerContext context, object message) // (2) 19 { 20 if (message is IByteBuffer buffer) // (3) 21 { 22 Console.WriteLine("Received from client: " + buffer.ToString(Encoding.UTF8)); 23 } 24 25 context.WriteAsync(message); // (4) 26 } 27 28 /// <summary> 29 /// 管道讀取完成 30 /// </summary> 31 /// <param name="context"></param> 32 public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush(); // (5) 33 34 /// <summary> 35 /// 出現異常 36 /// </summary> 37 /// <param name="context"></param> 38 /// <param name="exception"></param> 39 public override void ExceptionCaught(IChannelHandlerContext context, Exception exception) 40 { 41 Console.WriteLine("Exception: " + exception); 42 context.CloseAsync(); 43 } 44 } 45 }
重點看註釋的地方,其餘地方跟Server端沒有任何區別程序員
1 namespace Echo.Client 2 { 3 using System; 4 using System.Net; 5 using System.Text; 6 using System.Threading.Tasks; 7 using DotNetty.Buffers; 8 using DotNetty.Codecs; 9 using DotNetty.Handlers.Logging; 10 using DotNetty.Transport.Bootstrapping; 11 using DotNetty.Transport.Channels; 12 using DotNetty.Transport.Channels.Sockets; 13 using Examples.Common; 14 15 static class Program 16 { 17 static async Task RunClientAsync() 18 { 19 ExampleHelper.SetConsoleLogger(); 20 21 var group = new MultithreadEventLoopGroup(); 22 23 try 24 { 25 var bootstrap = new Bootstrap(); 26 bootstrap 27 .Group(group) 28 .Channel<TcpSocketChannel>() 29 .Option(ChannelOption.TcpNodelay, true) 30 .Handler( 31 new ActionChannelInitializer<ISocketChannel>( 32 channel => 33 { 34 IChannelPipeline pipeline = channel.Pipeline; 35 pipeline.AddLast(new LoggingHandler()); 36 pipeline.AddLast("framing-enc", new LengthFieldPrepender(2)); 37 pipeline.AddLast("framing-dec", new LengthFieldBasedFrameDecoder(ushort.MaxValue, 0, 2, 0, 2)); 38 39 pipeline.AddLast("echo", new EchoClientHandler()); 40 })); 41 42 IChannel clientChannel = await bootstrap.ConnectAsync(new IPEndPoint(ClientSettings.Host, ClientSettings.Port)); 43 44 // 創建死循環,類同於While(true) 45 for (;;) // (4) 46 { 47 Console.WriteLine("input you data:"); 48 // 根據設置創建緩存區大小 49 IByteBuffer initialMessage = Unpooled.Buffer(ClientSettings.Size); // (1) 50 string r = Console.ReadLine(); 51 // 將數據流寫入緩衝區 52 initialMessage.WriteBytes(Encoding.UTF8.GetBytes(r ?? throw new InvalidOperationException())); // (2) 53 // 將緩衝區數據流寫入到管道中 54 await clientChannel.WriteAndFlushAsync(initialMessage); // (3) 55 if(r.Contains("bye")) 56 break; 57 } 58 59 Console.WriteLine("byebye"); 60 61 62 await clientChannel.CloseAsync(); 63 } 64 finally 65 { 66 await group.ShutdownGracefullyAsync(TimeSpan.FromMilliseconds(100), TimeSpan.FromSeconds(1)); 67 } 68 } 69 70 static void Main() => RunClientAsync().Wait(); 71 } 72 }
1 namespace Echo.Client 2 { 3 using System; 4 using System.Text; 5 using DotNetty.Buffers; 6 using DotNetty.Transport.Channels; 7 8 public class EchoClientHandler : ChannelHandlerAdapter 9 { 10 readonly IByteBuffer initialMessage; 11 12 public override void ChannelActive(IChannelHandlerContext context) => context.WriteAndFlushAsync(this.initialMessage); 13 14 public override void ChannelRead(IChannelHandlerContext context, object message) 15 { 16 if (message is IByteBuffer byteBuffer) 17 { 18 Console.WriteLine("Received from server: " + byteBuffer.ToString(Encoding.UTF8)); 19 } 20 } 21 22 public override void ChannelReadComplete(IChannelHandlerContext context) => context.Flush(); 23 24 public override void ExceptionCaught(IChannelHandlerContext context, Exception exception) 25 { 26 Console.WriteLine("Exception: " + exception); 27 context.CloseAsync(); 28 } 29 } 30 }
雖然DotNetty官方沒有提供任何技術文檔,但官方卻提供了詳細的調試記錄,不少時候,咱們學習者其實也能夠經過調試記錄來分析某一個功能的實現流程。咱們能夠經過將DotNetty的內部輸入輸出記錄打印到控制檯上。github
InternalLoggerFactory.DefaultFactory.AddProvider(new ConsoleLoggerProvider((s, level) => true, false));
能夠看到服務端的打印記錄一下多出來了許多許多,有大部分是屬於DotNetty內部調試時的打印記錄,咱們只着重看以下的部分。web
dbug: SRV-LSTN[0] [id: 0x3e8afca1] HANDLER_ADDED dbug: SRV-LSTN[0] [id: 0x3e8afca1] REGISTERED (1) dbug: SRV-LSTN[0] [id: 0x3e8afca1] BIND: 0.0.0.0:8007 (2) wait the client input dbug: SRV-LSTN[0] [id: 0x3e8afca1, 0.0.0.0:8007] ACTIVE (3) dbug: SRV-LSTN[0] [id: 0x3e8afca1, 0.0.0.0:8007] READ (4) dbug: SRV-LSTN[0] [id: 0x3e8afca1, 0.0.0.0:8007] RECEIVED: [id: 0x7bac2775, 127.0.0.1:64073 :> 127.0.0.1:8007] (5) dbug: SRV-LSTN[0] [id: 0x3e8afca1, 0.0.0.0:8007] RECEIVED_COMPLETE (6) dbug: SRV-LSTN[0] [id: 0x3e8afca1, 0.0.0.0:8007] READ (7) dbug: SRV-CONN[0] [id: 0x7bac2775, 127.0.0.1:64073 => 127.0.0.1:8007] HANDLER_ADDED (8) dbug: SRV-CONN[0] [id: 0x7bac2775, 127.0.0.1:64073 => 127.0.0.1:8007] REGISTERED (9) dbug: SRV-CONN[0] [id: 0x7bac2775, 127.0.0.1:64073 => 127.0.0.1:8007] ACTIVE (10) dbug: SRV-CONN[0] [id: 0x7bac2775, 127.0.0.1:64073 => 127.0.0.1:8007] READ (11) dbug: DotNetty.Buffers.AbstractByteBuffer[0] (12) -Dio.netty.buffer.bytebuf.checkAccessible: True dbug: SRV-CONN[0] [id: 0x7bac2775, 127.0.0.1:64073 => 127.0.0.1:8007] RECEIVED: 14B (13) +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |100000000| 00 0C 68 65 6C 6C 6F 20 77 6F 72 6C 64 21 |..hello world! | +--------+-------------------------------------------------+----------------+ Received from client: hello world! dbug: SRV-CONN[0] (14) [id: 0x7bac2775, 127.0.0.1:64073 => 127.0.0.1:8007] WRITE: 2B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |100000000| 00 0C |.. | +--------+-------------------------------------------------+----------------+ dbug: SRV-CONN[0] (15) [id: 0x7bac2775, 127.0.0.1:64073 => 127.0.0.1:8007] WRITE: 12B +-------------------------------------------------+ | 0 1 2 3 4 5 6 7 8 9 a b c d e f | +--------+-------------------------------------------------+----------------+ |100000000| 68 65 6C 6C 6F 20 77 6F 72 6C 64 21 |hello world! | +--------+-------------------------------------------------+----------------+ dbug: SRV-CONN[0] (16) [id: 0x7bac2775, 127.0.0.1:64073 => 127.0.0.1:8007] RECEIVED_COMPLETE dbug: SRV-CONN[0] (17) [id: 0x7bac2775, 127.0.0.1:64073 => 127.0.0.1:8007] FLUSH dbug: SRV-CONN[0] (18) [id: 0x7bac2775, 127.0.0.1:64073 => 127.0.0.1:8007] READ
咋一看,有18個操做,好像有點太多了,其實否則,還有不少不少的內部調試細節並沒打印到控制檯上。編程