在過去幾年的工做和學習中,比較關注高層次的應用開發,對底層探究較少。實現Web應用的開發,主要依賴Tomcat、Apache等應用服務器,程序員無需瞭解底層協議,但一樣限制了應用的性能和效率。如今開始探究網絡編程,Netty是一個很是重要的技術。會持續更新有關Netty學習的相關文章,共勉。html
Netty is a NIO client server framework which enables quick and easy development of network applications such as protocol servers and clients. It greatly simplifies and streamlines network programming such as TCP and UDP socket server.java
'Quick and easy' doesn't mean that a resulting application will suffer from a maintainability or a performance issue. Netty has been designed carefully with the experiences earned from the implementation of a lot of protocols such as FTP, SMTP, HTTP, and various binary and text-based legacy protocols. As a result, Netty has succeeded to find a way to achieve ease of development, performance, stability, and flexibility without a compromise.程序員
1、實現Discard協議。顧名思義,Discard協議是指接受並放棄全部請求,不作任何迴應。編程
實現Handlerpromise
** * 服務端處理通道.這裏只是打印一下請求的內容,並不對請求進行任何的響應 DiscardServerHandler 繼承自 * ChannelHandlerAdapter, 這個類實現了ChannelHandler接口, ChannelHandler提供了許多事件處理的接口方法, * 而後你能夠覆蓋這些方法。 如今僅僅只須要繼承ChannelHandlerAdapter類而不是你本身去實現接口方法。 * */ public class DiscardServerHandler extends ChannelInboundHandlerAdapter { /** * 這裏咱們覆蓋了chanelRead()事件處理方法。 每當從客戶端收到新的數據時, 這個方法會在收到消息時被調用, * 這個例子中,收到的消息的類型是ByteBuf * * @param ctx * 通道處理的上下文信息 * @param msg * 接收的消息 */ @Override public void channelRead(ChannelHandlerContext ctx, Object msg) { try { ByteBuf in = (ByteBuf) msg; // 打印客戶端輸入,傳輸過來的的字符 System.out.print(in.toString(CharsetUtil.UTF_8)); } finally { /** * ByteBuf是一個引用計數對象,這個對象必須顯示地調用release()方法來釋放。 * 請記住處理器的職責是釋放全部傳遞處處理器的引用計數對象。 */ // 拋棄收到的數據 ReferenceCountUtil.release(msg); } } /*** * 這個方法會在發生異常時觸發 * * @param ctx * @param cause */ @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { /** * exceptionCaught() 事件處理方法是當出現 Throwable 對象纔會被調用,即當 Netty 因爲 IO * 錯誤或者處理器在處理事件時拋出的異常時。在大部分狀況下,捕獲的異常應該被記錄下來 而且把關聯的 channel * 給關閉掉。然而這個方法的處理方式會在遇到不一樣異常的狀況下有不 同的實現,好比你可能想在關閉鏈接以前發送一個錯誤碼的響應消息。 */ // 出現異常就關閉 cause.printStackTrace(); ctx.close(); } }
實現DsicardServer服務器
/** * 丟棄任何進入的數據 啓動服務端的DiscardServerHandler */ public class DiscardServer { private int port; public DiscardServer(int port) { super(); this.port = port; } public void run() throws Exception { /*** * NioEventLoopGroup 是用來處理I/O操做的多線程事件循環器, * Netty提供了許多不一樣的EventLoopGroup的實現用來處理不一樣傳輸協議。 在這個例子中咱們實現了一個服務端的應用, * 所以會有2個NioEventLoopGroup會被使用。 第一個常常被叫作‘boss’,用來接收進來的鏈接。 * 第二個常常被叫作‘worker’,用來處理已經被接收的鏈接, 一旦‘boss’接收到鏈接,就會把鏈接信息註冊到‘worker’上。 * 如何知道多少個線程已經被使用,如何映射到已經建立的Channels上都須要依賴於EventLoopGroup的實現, * 而且能夠經過構造函數來配置他們的關係。 * */ EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); System.out.println("準備運行端口:" + port); try { /** * ServerBootstrap 是一個啓動NIO服務的輔助啓動類 你能夠在這個服務中直接使用Channel */ ServerBootstrap b = new ServerBootstrap(); /** * 這一步是必須的,若是沒有設置group將會報java.lang.IllegalStateException: group not * set異常 */ b = b.group(bossGroup, workerGroup); /*** * ServerSocketChannel以NIO的selector爲基礎進行實現的,用來接收新的鏈接 * 這裏告訴Channel如何獲取新的鏈接. */ b = b.channel(NioServerSocketChannel.class); /*** * 這裏的事件處理類常常會被用來處理一個最近的已經接收的Channel。 ChannelInitializer是一個特殊的處理類, * 他的目的是幫助使用者配置一個新的Channel。 * 也許你想經過增長一些處理類好比NettyServerHandler來配置一個新的Channel * 或者其對應的ChannelPipeline來實現你的網絡程序。 當你的程序變的複雜時,可能你會增長更多的處理類到pipline上, * 而後提取這些匿名類到最頂層的類上。 */ b = b.childHandler(new ChannelInitializer<SocketChannel>() { // (4) @Override public void initChannel(SocketChannel ch) throws Exception { ch.pipeline().addLast(new DiscardServerHandler());// demo1.discard // ch.pipeline().addLast(new // ResponseServerHandler());//demo2.echo // ch.pipeline().addLast(new // TimeServerHandler());//demo3.time } }); /*** * 你能夠設置這裏指定的通道實現的配置參數。 咱們正在寫一個TCP/IP的服務端, * 所以咱們被容許設置socket的參數選項好比tcpNoDelay和keepAlive。 * 請參考ChannelOption和詳細的ChannelConfig實現的接口文檔以此能夠對ChannelOptions的有一個大概的認識。 */ b = b.option(ChannelOption.SO_BACKLOG, 128); /*** * option()是提供給NioServerSocketChannel用來接收進來的鏈接。 * childOption()是提供給由父管道ServerChannel接收到的鏈接, * 在這個例子中也是NioServerSocketChannel。 */ b = b.childOption(ChannelOption.SO_KEEPALIVE, true); /*** * 綁定端口並啓動去接收進來的鏈接 */ ChannelFuture f = b.bind(port).sync(); /** * 這裏會一直等待,直到socket被關閉 */ f.channel().closeFuture().sync(); } finally { /*** * 關閉 */ workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); } } //將規則跑起來 public static void main(String[] args) throws Exception { int port; if (args.length > 0) { port = Integer.parseInt(args[0]); } else { port = 8080; } new DiscardServer(port).run(); System.out.println("server:run()"); } }
測試方法:網絡
telnet 127.0.0.1 8080 Windows啓動Telnet Client,參考 https://jingyan.baidu.com/article/54b6b9c0813a362d593b4775.html多線程
測試發現,只要在telnet命令行,輸入內容,便可在服務器收到並打印。有時單個字母輸出,有時多個字母一塊兒輸出,這是爲何呢?app
2、實現Echo,以輸入內容做爲應答。socket
其實,咱們只需修改DiscardServerHandler 的channelRead函數便可,以下所示。
@Override public void channelRead(ChannelHandlerContext ctx, Object msg) { try { ByteBuf in = (ByteBuf) msg; // 打印客戶端輸入,傳輸過來的的字符 System.out.print(in.toString(CharsetUtil.UTF_8)); ctx.write(msg); ctx.flush(); } finally { /** * ByteBuf是一個引用計數對象,這個對象必須顯示地調用release()方法來釋放。 * 請記住處理器的職責是釋放全部傳遞處處理器的引用計數對象。 */ } }
總結:經過DiscardServerHandler和DiscardServer兩個類,咱們搭建起了簡單的TCP Server。