Netty 入门与实战 (四) 实现简单的聊天室

本贴最后更新于 2049 天前,其中的信息可能已经时移世异

前面三个章节,我们使用了 Netty 实现了 DISCARD 丢弃服务和回复以及自定义编码解码,这篇博客,我们要用 Netty 实现简单的聊天室功能。

Ps: 突然想起来大学里面有个课程实训,给予 UDP 还是 TCP 实现的聊天程序,简单的分析一下,那个实现和基于 Netty 的实现是不一样的,基于 UDP 或者 TCP 做的聊天室中只能是客户端向服务发送消息(当然基于 UDP 的也可以建立两个 Channel 来实现服务器和客户端的双向通道),然后客户端接收到消息,这里的服务器仅仅作为一个接收消息处理之的作用,并不能主动向客户端推送消息。

基于前面几个章节的知识,这里我们做一个简单的聊天室功能,我们简单的说一下需求:

  • 进入聊天室,服务器发送欢迎信息到该用户的客户端
  • 有新人进入或者退出聊天室,那么聊天室的其他用户都能接收到通知信息
  • 某位用户发送消息,其他用户的客户端显示格式为 [时间][发送用户的名称/地址]消息内容,自己的客户端显示 [时间][You]消息内容

这篇博文仅仅实现服务端的代码,使用 telent 测试,客户端的作为下一篇阐述。

工具类代码

工具类就一个时间格式化的工具,如下:

package com.zhoutao123.simpleChat.utils; import java.text.SimpleDateFormat; import java.util.Date; public class DatetimeUtils { private static final SimpleDateFormat smf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); public static String getNowDatetime(){ return smf.format(new Date()); } }

服务端代码

服务处理适配器

和之前的代码一样,这里我们继承 SimpleChannelInboundHandler,具体的解释在注释里面。

package com.zhoutao123.simpleChat.server; import io.netty.channel.Channel; import io.netty.channel.ChannelHandlerContext; import io.netty.channel.SimpleChannelInboundHandler; import io.netty.channel.group.ChannelGroup; import io.netty.channel.group.DefaultChannelGroup; import io.netty.util.concurrent.GlobalEventExecutor; import static com.zhoutao123.simpleChat.utils.DatetimeUtils.getNowDatetime; public class ServerHandle extends SimpleChannelInboundHandler<String> { // 创建ChannelGroup 用于保存连接的Channel public static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); //当有新的Channel增加的时候 @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { // 获取当前的Channel Channel channel = ctx.channel(); //向其他Channel发送上线消息 channelGroup.writeAndFlush(String.format("[%s][服务器]\t用户:%s 加入聊天室!\n", getNowDatetime(), channel.remoteAddress())); // 添加Channel到Group里面 channelGroup.add(channel); // 向新用户发送欢迎信息 channel.writeAndFlush(String.format("你好,%s欢迎来到Netty聊天室\n", channel.remoteAddress())); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { // 用户退出后向全部Channel发送下线消息 channelGroup.writeAndFlush(String.format("[%s][服务器]\t用户:%s 离开聊天室!\n", getNowDatetime(), ctx.channel().remoteAddress())); // 移除 channelGroup.remove(ctx.channel()); } @Override protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception { // 服务器接收到新的消息后之后执行 // 获取当前的Channel Channel currentChannel = ctx.channel(); // 遍历 for (Channel channel : channelGroup) { String sendMessage = ""; // 如果是当前的用户发送You的信息,不是则发送带有发送人的信息 if (channel == currentChannel) { sendMessage = String.format("[%s][You]\t%s\n", getNowDatetime(), msg); } else { sendMessage = String.format("[%s][%s]\t %s\n", getNowDatetime(), currentChannel.remoteAddress(), msg); } channel.writeAndFlush(sendMessage); } } @Override public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { // 发送异常的时候通知移除 Channel channel = ctx.channel(); channelGroup.writeAndFlush(String.format("[%s][服务器]\t 用户 %s 出现异常掉线!\n", getNowDatetime(), channel.remoteAddress())); ctx.close(); } }

处理器初始化

这里主要是配置一些编码器以及解码器以及我们自己定义的 ServerHandleAdapter

package com.zhoutao123.simpleChat.server; import io.netty.channel.ChannelInitializer; import io.netty.channel.ChannelPipeline; import io.netty.channel.socket.SocketChannel; import io.netty.handler.codec.DelimiterBasedFrameDecoder; import io.netty.handler.codec.Delimiters; import io.netty.handler.codec.string.StringDecoder; import io.netty.handler.codec.string.StringEncoder; 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 ServerHandle()); System.out.println("SimpleChatClient:"+ch.remoteAddress() +"连接上"); } }

启动服务器

启动的代码和以前一致,没有打的改动.

package com.zhoutao123.simpleChat.server; import io.netty.bootstrap.ServerBootstrap; import io.netty.channel.ChannelFuture; import io.netty.channel.ChannelOption; import io.netty.channel.nio.NioEventLoopGroup; import io.netty.channel.socket.nio.NioServerSocketChannel; public class Server { private final static int port = 8080; public static void main(String[] args) throws InterruptedException { NioEventLoopGroup boss = new NioEventLoopGroup(); NioEventLoopGroup work = new NioEventLoopGroup(); try { ServerBootstrap serverBootstrap = new ServerBootstrap(); serverBootstrap.group(boss, work) .channel(NioServerSocketChannel.class) .childHandler(new SimpleChatServerInitializer()) .option(ChannelOption.SO_BACKLOG, 128) .childOption(ChannelOption.SO_KEEPALIVE, true); System.out.println("聊天服务已经启动....."); ChannelFuture sync = serverBootstrap.bind(port).sync(); sync.channel().closeFuture().sync(); } finally { work.shutdownGracefully(); boss.shutdownGracefully(); System.out.println("聊天服务已经被关闭"); } } }

telnet 测试

启动服务之后,我在 Linux 上使用 Telnet 命令来简单的测试了下,
这里创建了 4 个用户,发送了一些信息,可以观察一下:

  • Netty

    Netty 是一个基于 NIO 的客户端-服务器编程框架,使用 Netty 可以让你快速、简单地开发出一个可维护、高性能的网络应用,例如实现了某种协议的客户、服务端应用。

    49 引用 • 33 回帖 • 34 关注

相关帖子

欢迎来到这里!

我们正在构建一个小众社区,大家在这里相互信任,以平等 • 自由 • 奔放的价值观进行分享交流。最终,希望大家能够找到与自己志同道合的伙伴,共同成长。

注册 关于
请输入回帖内容 ...