這節講解基於 Netty 快速實現一個聊天小程序。
一、服務端
1. SimpleChatServerHandler(處理器類)
該類主要實現了接收來自客戶端的消息並轉發給其他客戶端。
1 /** 2 * 服務端處理器 3 */ 4 public class SimpleChatServerHandler extends SimpleChannelInboundHandler<String> { 5 public static ChannelGroup channels 6 = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); 7 8 /** 9 * 收到新的客戶端連接時調用 10 * 將客戶端channel存入列表,並廣播消息 11 */ 12 @Override 13 public void handlerAdded(ChannelHandlerContext ctx) throws Exception { 14 Channel incoming = ctx.channel(); 15 // 廣播加入消息 16 channels.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入\n"); 17 channels.add(incoming); // 存入列表 18 } 19 20 /** 21 * 客戶端連接斷開時調用 22 * 廣播消息 23 */ 24 @Override 25 public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { 26 Channel incoming = ctx.channel(); 27 // 廣播離開消息 28 channels.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 離開\n"); 29 // channel會自動從ChannelGroup中刪除 30 } 31 32 /** 33 * 收到消息時調用 34 * 將消息轉發給其他客戶端 35 */ 36 @Override 37 protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 38 Channel incoming = ctx.channel(); 39 for(Channel channel : channels) { // 遍歷所有連接的客戶端 40 if(channel != incoming) { // 其他客戶端 41 channel.writeAndFlush("[" + incoming.remoteAddress() + "] " + msg + "\n" ); 42 } else { // 自己 43 channel.writeAndFlush("[you] " + msg + "\n" ); 44 } 45 } 46 } 47 48 /** 49 * 監聽到客戶端活動時調用 50 */ 51 @Override 52 public void channelActive(ChannelHandlerContext ctx) throws Exception { 53 Channel incoming = ctx.channel(); 54 System.out.println("SimpleChatClient: " + incoming.remoteAddress() + " 在線"); 55 } 56 57 /** 58 * 監聽到客戶端不活動時調用 59 */ 60 @Override 61 public void channelInactive(ChannelHandlerContext ctx) throws Exception { 62 Channel incoming = ctx.channel(); 63 System.out.println("SimpleChatClient: " + incoming.remoteAddress() + " 掉線"); 64 } 65 66 /** 67 * 當Netty由於IO錯誤或者處理器在處理事件拋出異常時調用 68 * 關閉連接 69 */ 70 @Override 71 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 72 Channel incoming = ctx.channel(); 73 System.out.println("SimpleChatClient: " + incoming.remoteAddress() + " 異常"); 74 } 75 }
2. SimpleChatServerInitializer(配置 Channel 類)
該類添加分隔符協議處理類,解碼、編碼器還有自定義處理器。
1 /** 2 * 服務器配置初始化 3 * 添加多個處理器 4 */ 5 public class SimpleChatServerInitializer extends ChannelInitializer<SocketChannel> { 6 7 @Override 8 protected void initChannel(SocketChannel ch) throws Exception { 9 ChannelPipeline pipeline = ch.pipeline(); 10 // 添加處理類 11 // 使用'\r''\n'分割幀 12 pipeline.addLast("framer", 13 new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); 14 // 解碼、編碼器 15 pipeline.addLast("decoder", new StringDecoder()); 16 pipeline.addLast("encoder", new StringEncoder()); 17 // 處理器 18 pipeline.addLast("handler", new SimpleChatServerHandler()); 19 20 System.out.println("SimpleChatClient: " + ch.remoteAddress() + "連接上"); 21 } 22 23 }
3. SimpleChatServer(服務端主程序)
啟動服務端。
1 /** 2 * 服務端 main 啟動 3 */ 4 public class SimpleChatServer { 5 private int port; // 端口 6 7 public SimpleChatServer(int port) { 8 this.port = port; 9 } 10 11 // 配置並開啟服務器 12 public void run() throws Exception { 13 EventLoopGroup bossGroup = new NioEventLoopGroup(); // 用來接收進來的連接 14 EventLoopGroup workerGroup = new NioEventLoopGroup(); // 用來處理已接收的連接 15 16 try { 17 ServerBootstrap sb = new ServerBootstrap(); // 啟動NIO服務的輔助啟動類 18 sb.group(bossGroup, workerGroup) 19 .channel(NioServerSocketChannel.class) // 設置如何接受連接 20 .childHandler(new SimpleChatServerInitializer()) // 配置Channel 21 .option(ChannelOption.SO_BACKLOG, 128) // 設置緩沖區 22 .childOption(ChannelOption.SO_KEEPALIVE, true); // 啟用心跳機制 23 24 System.out.println("SimpleChatServer 啟動了"); 25 ChannelFuture future = sb.bind(port).sync(); // 綁定端口,開始接收連接 26 future.channel().closeFuture().sync(); // 等待關閉服務器(不會發生) 27 } finally { 28 workerGroup.shutdownGracefully(); 29 bossGroup.shutdownGracefully(); 30 System.out.println("SimpleChatServer 關閉了"); 31 } 32 } 33 34 public static void main(String[] args) throws Exception { 35 int port = 8080; 36 new SimpleChatServer(port).run(); // 開啟服務器 37 } 38 }
二、客戶端
1. SimpleChatClientHandler(處理器類)
直接輸出收到的消息。
1 /** 2 * 客戶端處理類 3 * 直接輸出收到的消息 4 */ 5 public class SimpleChatClientHandler extends SimpleChannelInboundHandler<String> { 6 7 @Override 8 protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { 9 System.out.println(msg); // 直接輸出消息 10 } 11 12 }
2. SimpleChatClientInitializer(配置 Channel 類)
與服務端類似。
1 /** 2 * 客戶端配置初始化 3 * 與服務端類似 4 */ 5 public class SimpleChatClientInitializer extends ChannelInitializer<SocketChannel> { 6 7 @Override 8 protected void initChannel(SocketChannel ch) throws Exception { 9 ChannelPipeline pipeline = ch.pipeline(); 10 // 添加處理類 11 // 使用'\r''\n'分割幀 12 pipeline.addLast("framer", 13 new DelimiterBasedFrameDecoder(8192, Delimiters.lineDelimiter())); 14 // 解碼、編碼器 15 pipeline.addLast("decoder", new StringDecoder()); 16 pipeline.addLast("encoder", new StringEncoder()); 17 // 處理器 18 pipeline.addLast("handler", new SimpleChatClientHandler()); 19 } 20 21 }
3. SimpleChatClient(客戶端主程序)
接收來自控制台的消息,每幀以 "\r\n" 結尾,再發給服務端。
1 /** 2 * 客戶端 3 * 開啟客戶端,接收控制台輸入並發送給服務端 4 */ 5 public class SimpleChatClient { 6 private final String host; // IP 7 private final int port; // 端口 8 9 public SimpleChatClient(String host, int port) { 10 this.host = host; 11 this.port = port; 12 } 13 14 // 配置並運行客戶端 15 public void run() throws Exception { 16 EventLoopGroup group = new NioEventLoopGroup(); 17 try { 18 Bootstrap b = new Bootstrap(); // 客戶端輔助啟動類 19 b.group(group) // 客戶端只需要一個用來接收並處理連接 20 .channel(NioSocketChannel.class) // 設置如何接受連接 21 .handler(new SimpleChatClientInitializer());// 配置 channel 22 // 連接服務器 23 Channel channel = b.connect(host, port).sync().channel(); 24 // 讀取控制台輸入字符 25 BufferedReader in = new BufferedReader(new InputStreamReader(System.in)); 26 while(true) { 27 // 每行成一幀輸出,以"\r\n"結尾 28 channel.writeAndFlush(in.readLine() + "\r\n"); 29 } 30 } catch (Exception e) { 31 e.printStackTrace(); // 輸出異常 32 } finally { 33 group.shutdownGracefully(); // 關閉 34 } 35 } 36 37 public static void main(String[] args) throws Exception { 38 new SimpleChatClient("localhost", 8080).run(); // 啟動客戶端 39 } 40 41 }
三、運行效果
先運行服務端程序,然后在運行兩次客戶端程序,如下:
服務端輸出:
首先連接的客戶端輸出:
隨便選個客戶端在控制台輸出信息並回車,如下:
自身輸出:
另一客戶端輸出:
以上……