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 }

 

 

三、运行效果 

   先运行服务端程序,然后在运行两次客户端程序,如下:

  服务端输出:

  

  首先连接的客户端输出:

  

 

  随便选个客户端在控制台输出信息并回车,如下:

   自身输出:

  

  另一客户端输出:

  

 

  以上…… 

 

posted @ 2018-05-16 13:30  Just_for_Myself  阅读(2155)  评论(0编辑  收藏  举报