zoukankan      html  css  js  c++  java
  • Netty学习笔记(四) 简单的聊天室功能之服务端开发

    前面三个章节,我们使用了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][服务器]	用户:%s 加入聊天室!
    ", getNowDatetime(), channel.remoteAddress()));
            // 添加Channel到Group里面
            channelGroup.add(channel);
            // 向新用户发送欢迎信息
            channel.writeAndFlush(String.format("你好,%s欢迎来到Netty聊天室
    ", channel.remoteAddress()));
        }
    
        @Override
        public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
            // 用户退出后向全部Channel发送下线消息
            channelGroup.writeAndFlush(String.format("[%s][服务器]	用户:%s 离开聊天室!
    ", 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]	%s
    ", getNowDatetime(), msg);
                } else {
                    sendMessage = String.format("[%s][%s]	 %s
    ", 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][服务器]	 用户 %s 出现异常掉线!
    ", 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个用户,发送了一些信息,可以观察一下:

  • 相关阅读:
    linux 文件搜索
    解决android 无法打开 DDMS 中的data目录
    JAVA 截图+tess4j识别
    JAVA 获取网页源代码保存到本地文件
    java连接sqlserver数据简单操作
    SQL server 2008 安装提示:属性不匹配
    SQLServer 安装提示需要重启计算机的解决方案
    Android蓝牙----打开,关闭操作
    JAVA中String类的比较
    Android中的AlertDialog和ProgressDialog用法
  • 原文地址:https://www.cnblogs.com/zhoutao825638/p/10382191.html
Copyright © 2011-2022 走看看