Netty 聊天小程序
这节讲解基于 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 }
三、运行效果
先运行服务端程序,然后在运行两次客户端程序,如下:
服务端输出:
首先连接的客户端输出:
随便选个客户端在控制台输出信息并回车,如下:
自身输出:
另一客户端输出:
以上……