Netty 是一個 Java NIO 客戶端服務器框架,使用它能夠快速簡單地開發網絡應用程序,好比服務器和客戶端的協議。Netty 大大簡化了網絡程序的開發過程好比 TCP 和 UDP 的 socket 服務的開發。更多關於 Netty 的知識,能夠參閱《Netty 4.x 用戶指南》https://github.com/waylau/netty-4-user-guidehtml
下面,就基於 Netty 快速實現一個聊天小程序。java
讓咱們從 handler (處理器)的實現開始,handler 是由 Netty 生成用來處理 I/O 事件的。git
public class SimpleChatServerHandler extends SimpleChannelInboundHandler<String> { // (1) public static ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { // (2) Channel incoming = ctx.channel(); for (Channel channel : channels) { channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入\n"); } channels.add(ctx.channel()); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { // (3) Channel incoming = ctx.channel(); for (Channel channel : channels) { channel.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 離開\n"); } channels.remove(ctx.channel()); } @Override protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception { // (4) Channel incoming = ctx.channel(); for (Channel channel : channels) { if (channel != incoming){ channel.writeAndFlush("[" + incoming.remoteAddress() + "]" + s + "\n"); } else { channel.writeAndFlush("[you]" + s + "\n"); } } } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // (5) Channel incoming = ctx.channel(); System.out.println("SimpleChatClient:"+incoming.remoteAddress()+"在線"); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { // (6) Channel incoming = ctx.channel(); System.out.println("SimpleChatClient:"+incoming.remoteAddress()+"掉線"); } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) { // (7) Channel incoming = ctx.channel(); System.out.println("SimpleChatClient:"+incoming.remoteAddress()+"異常"); // 當出現異常就關閉鏈接 cause.printStackTrace(); ctx.close(); } }
1.SimpleChatServerHandler 繼承自 SimpleChannelInboundHandler,這個類實現了ChannelInboundHandler接口,ChannelInboundHandler 提供了許多事件處理的接口方法,而後你能夠覆蓋這些方法。如今僅僅只須要繼承 SimpleChannelInboundHandler 類而不是你本身去實現接口方法。github
2.覆蓋了 handlerAdded() 事件處理方法。每當從服務端收到新的客戶端鏈接時,客戶端的 Channel 存入ChannelGroup列表中,並通知列表中的其餘客戶端 Channelbootstrap
3.覆蓋了 handlerRemoved() 事件處理方法。每當從服務端收到客戶端斷開時,客戶端的 Channel 移除 ChannelGroup 列表中,並通知列表中的其餘客戶端 Channel小程序
4.覆蓋了 channelRead0() 事件處理方法。每當從服務端讀到客戶端寫入信息時,將信息轉發給其餘客戶端的 Channel。其中若是你使用的是 Netty 5.x 版本時,須要把 channelRead0() 重命名爲messageReceived()api
5.覆蓋了 channelActive() 事件處理方法。服務端監聽到客戶端活動服務器
6.覆蓋了 channelInactive() 事件處理方法。服務端監聽到客戶端不活動網絡
7.exceptionCaught() 事件處理方法是當出現 Throwable 對象纔會被調用,即當 Netty 因爲 IO 錯誤或者處理器在處理事件時拋出的異常時。在大部分狀況下,捕獲的異常應該被記錄下來而且把關聯的 channel 給關閉掉。然而這個方法的處理方式會在遇到不一樣異常的狀況下有不一樣的實現,好比你可能想在關閉鏈接以前發送一個錯誤碼的響應消息。多線程
SimpleChatServerInitializer 用來增長多個的處理類到 ChannelPipeline 上,包括編碼、解碼、SimpleChatServerHandler 等。
public class SimpleChatServerInitializer extends ChannelInitializer<SocketChannel> { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); pipeline.addLast("decoder", new StringDecoder()); pipeline.addLast("encoder", new StringEncoder()); pipeline.addLast("handler", new SimpleChatServerHandler()); System.out.println("SimpleChatClient:"+ch.remoteAddress() +"鏈接上"); } }
編寫一個 main() 方法來啓動服務端。
public class SimpleChatServer { private int port; public SimpleChatServer(int port) { this.port = port; } public void run() throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); // (1) EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); // (2) b.group(bossGroup, workerGroup) .channel(NioServerSocketChannel.class) // (3) .childHandler(new SimpleChatServerInitializer()) //(4) .option(ChannelOption.SO_BACKLOG, 128) // (5) .childOption(ChannelOption.SO_KEEPALIVE, true); // (6) System.out.println("SimpleChatServer 啓動了"); // 綁定端口,開始接收進來的鏈接 ChannelFuture f = b.bind(port).sync(); // (7) // 等待服務器 socket 關閉 。 // 在這個例子中,這不會發生,但你能夠優雅地關閉你的服務器。 f.channel().closeFuture().sync(); } finally { workerGroup.shutdownGracefully(); bossGroup.shutdownGracefully(); System.out.println("SimpleChatServer 關閉了"); } } public static void main(String[] args) throws Exception { int port; if (args.length > 0) { port = Integer.parseInt(args[0]); } else { port = 8080; } new SimpleChatServer(port).run(); } }
1.NioEventLoopGroup是用來處理I/O操做的多線程事件循環器,Netty 提供了許多不一樣的EventLoopGroup的實現用來處理不一樣的傳輸。在這個例子中咱們實現了一個服務端的應用,所以會有2個 NioEventLoopGroup 會被使用。第一個常常被叫作‘boss’,用來接收進來的鏈接。第二個常常被叫作‘worker’,用來處理已經被接收的鏈接,一旦‘boss’接收到鏈接,就會把鏈接信息註冊到‘worker’上。如何知道多少個線程已經被使用,如何映射到已經建立的 Channel上都須要依賴於 EventLoopGroup 的實現,而且能夠經過構造函數來配置他們的關係。
2.ServerBootstrap是一個啓動 NIO 服務的輔助啓動類。你能夠在這個服務中直接使用 Channel,可是這會是一個複雜的處理過程,在不少狀況下你並不須要這樣作。
3.這裏咱們指定使用NioServerSocketChannel類來舉例說明一個新的 Channel 如何接收進來的鏈接。
4.這裏的事件處理類常常會被用來處理一個最近的已經接收的 Channel。SimpleChatServerInitializer 繼承自ChannelInitializer是一個特殊的處理類,他的目的是幫助使用者配置一個新的 Channel。也許你想經過增長一些處理類好比 SimpleChatServerHandler 來配置一個新的 Channel 或者其對應的ChannelPipeline來實現你的網絡程序。當你的程序變的複雜時,可能你會增長更多的處理類到 pipline 上,而後提取這些匿名類到最頂層的類上。
5.你能夠設置這裏指定的 Channel 實現的配置參數。咱們正在寫一個TCP/IP 的服務端,所以咱們被容許設置 socket 的參數選項好比tcpNoDelay 和 keepAlive。請參考ChannelOption和詳細的ChannelConfig實現的接口文檔以此能夠對ChannelOption 的有一個大概的認識。
6.option() 是提供給NioServerSocketChannel用來接收進來的鏈接。childOption() 是提供給由父管道ServerChannel接收到的鏈接,在這個例子中也是 NioServerSocketChannel。
7.咱們繼續,剩下的就是綁定端口而後啓動服務。這裏咱們在機器上綁定了機器全部網卡上的 8080 端口。固然如今你能夠屢次調用 bind() 方法(基於不一樣綁定地址)。
恭喜!你已經完成了基於 Netty 聊天服務端程序。
客戶端的處理類比較簡單,只須要將讀到的信息打印出來便可
public class SimpleChatClientHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception { System.out.println(s); } }
與服務端相似
public class SimpleChatClientInitializer extends ChannelInitializer<SocketChannel> { @Override public void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("framer", new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); pipeline.addLast("decoder", new StringDecoder()); pipeline.addLast("encoder", new StringEncoder()); pipeline.addLast("handler", new SimpleChatClientHandler()); } }
編寫一個 main() 方法來啓動客戶端。
public class SimpleChatClient { public static void main(String[] args) throws Exception{ new SimpleChatClient("localhost", 8080).run(); } private final String host; private final int port; public SimpleChatClient(String host, int port){ this.host = host; this.port = port; } public void run() throws Exception{ EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap() .group(group) .channel(NioSocketChannel.class) .handler(new SimpleChatClientInitializer()); Channel channel = bootstrap.connect(host, port).sync().channel(); BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); while(true){ channel.writeAndFlush(in.readLine() + "\r\n"); } } catch (Exception e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } } }
先運行 SimpleChatServer,再能夠運行多個 SimpleChatClient,控制檯輸入文本繼續測試
見https://github.com/waylau/netty-4-user-guide-demos中 simplechat
Netty 4.x 用戶指南https://github.com/waylau/netty-4-user-guide