Netty 实现聊天功能

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

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

准备

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

服务端

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

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 类而不是你自己去实现接口方法。

  2. 覆盖了 handlerAdded() 事件处理方法。每当从服务端收到新的客户端连接时,客户端的 Channel 存入ChannelGroup 列表中,并通知列表中的其他客户端 Channel

  3. 覆盖了 handlerRemoved() 事件处理方法。每当从服务端收到客户端断开时,客户端的 Channel 自动从 ChannelGroup 列表中移除了,并通知列表中的其他客户端 Channel

  4. 覆盖了 channelRead0() 事件处理方法。每当从服务端读到客户端写入信息时,将信息转发给其他客户端的 Channel。其中如果你使用的是 Netty 5.x 版本时,需要把 channelRead0() 重命名为messageReceived()

  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 中 simplechat

参考

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Spring Boot可以很方便地整合Netty实现聊天功能Netty是一个高性能的网络编程框架,可以用来实现各种网络应用,包括聊天室。 在Spring Boot中,可以使用NettyChannelHandler来处理客户端连接、消息接收和发送等操作。可以使用Spring Boot的WebSocket支持来实现浏览器与服务器之间的实时通信。 具体实现步骤如下: 1. 引入Netty和WebSocket的依赖 在pom.xml文件中添加以下依赖: ``` <dependency> <groupId>io.netty</groupId> <artifactId>netty-all</artifactId> <version>4.1.25.Final</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-websocket</artifactId> </dependency> ``` 2. 创建Netty服务器 创建一个Netty服务器,监听指定的端口,处理客户端连接和消息接收等操作。可以使用Spring Boot的@Configuration注解和@Bean注解来创建Netty服务器。 ``` @Configuration public class NettyConfig { @Value("${netty.port}") private int port; @Autowired private ChannelInitializer<SocketChannel> channelInitializer; @Bean public EventLoopGroup bossGroup() { return new NioEventLoopGroup(); } @Bean public EventLoopGroup workerGroup() { return new NioEventLoopGroup(); } @Bean public ServerBootstrap serverBootstrap() { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(bossGroup(), workerGroup()) .channel(NioServerSocketChannel.class) .childHandler(channelInitializer); return serverBootstrap; } @Bean public ChannelFuture channelFuture() throws InterruptedException { return serverBootstrap().bind(port).sync(); } } ``` 其中,@Value("${netty.port}")注解用来读取配置文件中的端口号,@Autowired注解用来注入ChannelInitializer,用于处理客户端连接和消息接收等操作。 3. 创建ChannelInitializer 创建一个ChannelInitializer,用于初始化NettyChannelPipeline,添加ChannelHandler来处理客户端连接和消息接收等操作。 ``` @Component public class ChatServerInitializer extends ChannelInitializer<SocketChannel> { @Autowired private ChatServerHandler chatServerHandler; @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast(new HttpServerCodec()); pipeline.addLast(new HttpObjectAggregator(65536)); pipeline.addLast(new WebSocketServerProtocolHandler("/chat")); pipeline.addLast(chatServerHandler); } } ``` 其中,HttpServerCodec用于处理HTTP请求和响应,HttpObjectAggregator用于将HTTP请求和响应合并为一个完整的消息,WebSocketServerProtocolHandler用于处理WebSocket握手和消息传输,chatServerHandler用于处理客户端连接和消息接收等操作。 4. 创建ChannelHandler 创建一个ChannelHandler,用于处理客户端连接和消息接收等操作。 ``` @Component @ChannelHandler.Sharable public class ChatServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> { private static final ChannelGroup channels = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { channels.add(ctx.channel()); } @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { channels.remove(ctx.channel()); } @Override protected void channelRead(ChannelHandlerContext ctx, TextWebSocketFrame msg) throws Exception { String message = msg.text(); channels.writeAndFlush(new TextWebSocketFrame(message)); } } ``` 其中,@ChannelHandler.Sharable注解用于标记该ChannelHandler可以被多个Channel共享,channels用于保存所有连接的ChannelchannelActive方法用于添加新的ChannelchannelInactive方法用于移除已关闭的ChannelchannelRead方法用于处理接收到的消息,将消息广播给所有连接的客户端。 5. 创建WebSocket客户端 在前端页面中创建WebSocket客户端,连接到Netty服务器,发送和接收消息。 ``` var socket = new WebSocket("ws://localhost:808/chat"); socket.onopen = function(event) { console.log("WebSocket connected."); }; socket.onmessage = function(event) { console.log("Received message: " + event.data); }; socket.onclose = function(event) { console.log("WebSocket closed."); }; function sendMessage() { var message = document.getElementById("message").value; socket.send(message); } ``` 其中,WebSocket连接的URL为ws://localhost:808/chat,onopen方法用于在连接建立时输出日志,onmessage方法用于在接收到消息时输出日志,onclose方法用于在连接关闭时输出日志,sendMessage方法用于发送消息。 6. 运行程序 运行Spring Boot程序,访问前端页面,即可实现聊天功能。 以上就是使用Spring Boot整合Netty实现聊天功能的步骤。 ### 回答2: Spring Boot是一个用来简化创建Spring应用程序的框架,而Netty是一个高性能的网络编程框架。将它们整合起来可以实现一个简单的聊天应用程序。 首先,我们需要创建一个基于Spring Boot的项目。可以使用Spring Initializer工具来快速生成一个基本的Spring Boot项目骨架。在pom.xml文件中添加Netty的依赖,这样项目就可以使用Netty库。 接下来,创建一个Netty的服务器类,用于处理客户端的连接和消息。在这个类中,我们需要实现NettyChannelInboundHandlerAdapter接口来处理连接和消息的事件。在连接建立时,可以将连接信息保存到一个Map中,以便后续消息的转发。当接收到消息时,可以根据消息内容将消息转发给指定的客户端。 在Spring Boot的配置文件中,我们需要配置Netty服务器的监听端口和服务器线程池的大小等参数。可以使用Spring Boot的注解来标记需要进行自动配置的类和方法。 在Spring Boot的控制器中,我们可以添加一个用于接收用户发送的消息的API接口。当这个接口被调用时,它会将用户发送的消息转发给Netty服务器,然后由服务器进行处理和转发。 此外,可以根据实际需求添加一些其他的功能,比如用户身份验证、消息加解密等。可以在Netty服务器的代码中添加相应的逻辑来实现这些功能。 最后,我们可以使用Spring Boot的打包工具将项目打包成一个可执行的Jar文件。然后可以将这个Jar文件部署到服务器上,并启动它来运行这个聊天应用程序。 总之,通过将Spring Boot和Netty整合起来,我们可以快速构建一个简单的聊天应用程序。Spring Boot提供了便捷的开发和配置方式,而Netty能够提供高性能的网络通信能力,这使得我们可以很容易地实现一个高效的聊天系统。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值