Netty初体验,聊天室03(项目核心)

本文主要介绍5块内容,同样也是本项目的核心代码。

1、服务端项目开发

2、客户端项目开发

3、客户端与服务端心跳机制

4、用户Session的管理

5、群组的Session的管理

开始上代码

1、服务端的开发

@Slf4j
public class ChatServer {

    public static void main(String[] args) {
        LoginRequestMessageHandler loginRequestMessageHandler = new LoginRequestMessageHandler();
        ProcotolFrameDecoder procotolFrameDecoder = new ProcotolFrameDecoder();
        MessageCodecSharable messageCodec = new MessageCodecSharable();
        ChatRequestMessageHandler chatRequestMessageHandler = new ChatRequestMessageHandler();
        GroupChatRequestHandler groupChatRequestHandler = new GroupChatRequestHandler();
        GroupCreateRequestHandler groupCreateRequestHandler = new GroupCreateRequestHandler();
        PingMessageHandler pingMessageHandler = new PingMessageHandler();
        int port = getPort();
        NioEventLoopGroup boss = new NioEventLoopGroup(1);
        NioEventLoopGroup worker = new NioEventLoopGroup(6);

        ServerBootstrap bootstrap = new ServerBootstrap();
        try {
            bootstrap.group(boss,worker)
                    .channel(NioServerSocketChannel.class)
                    .childHandler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(procotolFrameDecoder);
                            ch.pipeline().addLast(new LoggingHandler());
                            //自定义 协议
                            ch.pipeline().addLast(messageCodec);
                            ch.pipeline().addLast(pingMessageHandler);
                            //优化 防止死链接 占用资源
                            // 服务器端检测读频率 超过5秒没读到客户端请求,则认定客户端掉线
                            ch.pipeline().addLast(new IdleStateHandler(10,0,0));
                            // ChannelDuplexHandler 可以同时作为入站和出站处理器
               
                            });
                            //自定义其它handler
                            ch.pipeline().addLast(loginRequestMessageHandler);
                            ch.pipeline().addLast(chatRequestMessageHandler);
                            ch.pipeline().addLast(groupChatRequestHandler);
                            ch.pipeline().addLast(groupCreateRequestHandler);
                        }
                    });
            Channel channel = bootstrap.bind(8080).sync().channel();
            channel.closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            worker.shutdownGracefully();
            boss.shutdownGracefully();
        }

    }

2、客户端的开发

在接收客户端输入时,要新开一个线程,否则会阻塞。

@Slf4j
public class ChatClient {

    public static void main(String[] args) {
        int port = 8080;
        MessageCodecSharable messageCodec = new MessageCodecSharable();
        ProcotolFrameDecoder procotolFrameDecoder = new ProcotolFrameDecoder();
        ChatRequestMessageHandler chatRequestMessageHandler = new ChatRequestMessageHandler();
        // 通过判断true 或者 false 计算是否登录成功
        AtomicBoolean EXIT = new AtomicBoolean(false);
        // 让线程等待服务端返回登录结果
        CountDownLatch WAIT_FOR_LOGIN = new CountDownLatch(1);
        NioEventLoopGroup worker = new NioEventLoopGroup(6);
        Bootstrap bootstrap = new Bootstrap();
        Scanner scanner = new Scanner(System.in);
        try {
            bootstrap.group(worker)
                    .channel(NioSocketChannel.class)
                    .handler(new ChannelInitializer<SocketChannel>() {
                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            ch.pipeline().addLast(procotolFrameDecoder);
                            ch.pipeline().addLast(messageCodec);


                            // 客户端检测写事件
                            ch.pipeline().addLast(new IdleStateHandler(0,5,0));
                            //
                            ch.pipeline().addLast(new ChannelDuplexHandler(){
                                @Override
                                public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                                    IdleStateEvent event = (IdleStateEvent) evt;
                                    if (event.state() == IdleState.WRITER_IDLE){
                                        log.info("已经有2秒,客户端没有请求服务端了");
                                        //ctx.channel().close();
                                        ctx.writeAndFlush(new PingMessage(200,"心跳"));
                                    }
                                }
                            });
                            ch.pipeline().addLast(new LoggingHandler());
                            ch.pipeline().addLast(chatRequestMessageHandler);
                            ch.pipeline().addLast(new ChannelInboundHandlerAdapter(){
                                @Override
                                public void channelActive(ChannelHandlerContext ctx) throws Exception {
                                    new Thread(()->{
                                        System.out.println("请输入你的姓名:");
                                        String username = scanner.nextLine();
                                        System.out.println("请输入你的密码:");
                                        String password = scanner.nextLine();
                                        LoginRequestMessage login = new LoginRequestMessage(username,password);
                                        System.out.println("发送的数据:"+login);
                                        ctx.writeAndFlush(login);
                                        try {
                                            WAIT_FOR_LOGIN.await();
                                            while (true){
                                                System.out.println("欢迎进入聊天系统,请输入对应信息");
                                                System.out.println("==================================");
                                                System.out.println("send [username] [content]");
                                                System.out.println("gsend [group name] [content]");
                                                System.out.println("gcreate [group name] [m1,m2,m3...]");
                                                System.out.println("gmembers [group name]");
                                                System.out.println("gjoin [group name]");
                                                System.out.println("gquit [group name]");
                                                System.out.println("quit");
                                                System.out.println("==================================");
                                                String command ;
                                                command = scanner.nextLine();
                                                String[] result = command.split(" ");
                                                switch (result[0]){
                                                    case "send":
                                                        ctx.writeAndFlush(new ChatRequestMessage(result[2],result[1],username));
                                                        break;
                                                    case "gsend":
                                                        ctx.writeAndFlush(new GroupChatRequestMessage(username,result[1], result[2]));
                                                        break;
                                                    case "gcreate":
                                                        Set<String> userSet = Arrays.stream(result[2].split(",")).collect(Collectors.toSet());
                                                        userSet.add(username);
                                                        ctx.writeAndFlush(new GroupCreateRequestMessage(username,result[1],userSet));
                                                        break;
                                                    case "gmembers":
                                                        ctx.writeAndFlush("");
                                                        break;
                                                    case "gjoin":
                                                        ctx.writeAndFlush("");
                                                        break;
                                                    case "gquit":
                                                        ctx.writeAndFlush("");
                                                        break;
                                                    case "quit":
                                                        //退出
                                                        ctx.channel().close();
                                                        return;
                                                }
                                            }
                                        } catch (InterruptedException e){
                                            e.printStackTrace();
                                        }
                                    }).start();

                                }

                                @Override
                                public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                                    if (msg instanceof LoginResponse){
                                        LoginResponse loginResponse = (LoginResponse)msg;
                                        if (loginResponse.isSuccess()){
                                            EXIT.set(true);
                                            WAIT_FOR_LOGIN.countDown();
                                        }else if (!loginResponse.isSuccess()){
                                            ctx.channel().closeFuture();
                                        }
                                    }
                                    if (msg instanceof ChatResponseMessage){
                                        ChatResponseMessage message = (ChatResponseMessage) msg;
                                        if (message.isSuccess()){
                                            log.info("[聊天模块] 聊天失败,失败原因: {}",message.getReason());

                                        }else{
                                            log.info("[聊天模块] 聊天内容: {}",message.getContext());
                                        }
                                    }
                                    if (msg instanceof GroupChatResponseMessage){
                                        GroupChatResponseMessage message = (GroupChatResponseMessage) msg;
                                        if (message.isSuccess()){
                                            log.info("[群聊模块] 聊天失败,失败原因:{}",message.getReason());
                                        }else{
                                            log.info("[群聊模块] 聊天内容来自{},聊天内容{}",message.getFrom(),message.getContent());
                                        }
                                    }
                                    if (msg instanceof GroupCreateResponseMessage){
                                        GroupCreateResponseMessage message = (GroupCreateResponseMessage)msg;
                                        if (message.isSuccess() && message.getReason() != null){
                                            log.info("[群聊模块] 建群成功 群名{}",message.getReason());
                                        }
                                        if (!message.isSuccess()){
                                            log.info("[群聊模块] 建群失败 群名{}",message.getReason());
                                        }
                                        if (message.getFrom() != null){
                                            log.info("[群聊模块] 群主 {},拉你进群{}",message.getFrom(),message.getContent());
                                        }
                                    }
                                    if (msg instanceof PongMessage){
                                        PongMessage pongMessage = (PongMessage) msg;
                                    }
                                }
                                // 在连接断开时触发
                                @Override
                                public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                                    log.debug("连接已经断开,按任意键退出..");
                                    SessionFactory.getSession().unbind(ctx.channel());
                                    EXIT.set(true);
                                }

                                // 在出现异常时触发
                                @Override
                                public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                                    log.debug("连接已经断开,按任意键退出..{}", cause.getMessage());
                                    EXIT.set(true);
                                }
                            });
                        }
                    });
            Channel channel = bootstrap.connect("localhost", port).sync().channel();
            channel.closeFuture().sync();
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            worker.shutdownGracefully();
        }
    }
}

3、服务端与客户端进行通信时,一定要确保对方是否存活。

心跳是检测对方是否在线的依据。心跳频率可以根据项目实际情况进行设定。

//客户端发起心跳请求
//
                            ch.pipeline().addLast(new ChannelDuplexHandler(){
                                @Override
                                public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                                    IdleStateEvent event = (IdleStateEvent) evt;
                                    if (event.state() == IdleState.WRITER_IDLE){
                                        log.info("已经有2秒,客户端没有请求服务端了");
                                        //ctx.channel().close();
                                        ctx.writeAndFlush(new PingMessage(200,"探活"));
                                    }
                                }
                            });

//服务端接收客户端的心跳请求,并响应
// 服务器端检测读频率 超过5秒没读到客户端请求,则认定客户端掉线
                            ch.pipeline().addLast(new IdleStateHandler(10,0,0));
                            // ChannelDuplexHandler 可以同时作为入站和出站处理器
                            ch.pipeline().addLast(new ChannelDuplexHandler(){
                                @Override
                                public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                                    IdleStateEvent event = (IdleStateEvent) evt;
                                    //触发了读空闲事件
                                    if (event.state() == IdleState.READER_IDLE){
                                        log.info("已经10秒没有读取到数据了");
                                        //ctx.channel().close();
                                    }
                                }
                            });


//响应客户端
@ChannelHandler.Sharable
public class PingMessageHandler extends SimpleChannelInboundHandler<PingMessage> {
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, PingMessage msg) throws Exception {
        ctx.writeAndFlush(new PongMessage(true,"OK"));
    }
}

4、Session的管理。

通过管理session,可以方便服务端随时向客户端发送响应。

public class SessionMemoryImpl implements Session{

    Map<Channel,String> channelBindUsername = new HashMap<>();
    Map<String,Channel> usernameBindChannel = new HashMap<>();

//在内存中保留channel与用户的绑定关系,以及用户与channel的绑定。
//方便在业务中调用
    @Override
    public void bind(Channel channel, String username) {
        channelBindUsername.put(channel,username);
        usernameBindChannel.put(username,channel);
    }

    @Override
    public void unbind(Channel channel) {
        String user = channelBindUsername.get(channel);
        channelBindUsername.remove(channel);
        usernameBindChannel.remove(user);
    }

    @Override
    public Object getAttribute(Channel channel, String name) {
        return null;
    }

    @Override
    public void setAttribute(Channel channel, String name, Object value) {

    }

    @Override
    public Channel getChannel(String username) {
        return usernameBindChannel.get(username);
    }
}

5、群组的Session管理,通过实现接口,自定义逻辑。

public interface GroupSession {

    //Map<String, Set<String>> GroupBindUsers = new HashMap<>();

    boolean createGroup(String groupName,Set<String> users);

    boolean cancelGroupMember(String groupName,String user);

    boolean cancelGroup(String groupName);

    List<Channel> getOnlineChannel(String groupName);

}

  • 25
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

工作这么多年,学会了内卷

共同加油,继续努力!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值