Netty 实现聊天功能

Netty 是一个 Java NIO 客户端服务器框架,使用它能够快速简单地开发网络应用程序,好比服务器和客户端的协议。Netty 大大简化了网络程序的开发过程好比 TCP 和 UDP 的 socket 服务的开发。更多关于 Netty 的知识,能够参阅《Netty 4.x 用户指南》(https://github.com/waylau/netty-4-user-guidehtml

下面,就基于 Netty 快速实现一个聊天小程序。java

准备

  • JDK 7+
  • Maven 3.2.x
  • Netty 4.x
  • Eclipse 4.x

服务端

让咱们从 handler (处理器)的实现开始,handler 是由 Netty 生成用来处理 I/O 事件的。git

SimpleChatServerHandler.java

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(); // Broadcast a message to multiple Channels channels.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 加入\n"); channels.add(ctx.channel()); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { // (3) Channel incoming = ctx.channel(); // Broadcast a message to multiple Channels channels.writeAndFlush("[SERVER] - " + incoming.remoteAddress() + " 离开\n"); // A closed Channel is automatically removed from ChannelGroup, // so there is no need to do "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.java

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() +"链接上"); } } 

SimpleChatServer.java

编写一个 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 聊天服务端程序。

客户端

SimpleChatClientHandler.java

客户端的处理类比较简单,只须要将读到的信息打印出来便可

public class SimpleChatClientHandler extends SimpleChannelInboundHandler<String> { @Override protected void channelRead0(ChannelHandlerContext ctx, String s) throws Exception { System.out.println(s); } } 

SimpleChatClientInitializer.java

与服务端相似

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()); } } 

SimpleChatClient.java

编写一个 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 

相关文章
相关标签/搜索