聊天室-基于Netty 控制台版聊天室

控制台实现聊天室功能
参考一个厉害的教学up:孙哥
https://space.bilibili.com/284638819

功能分析

:::info
Spring ---->工厂
SpringBoot应用---->启动类main—>服务端启动

  1. 服务端一定是一个Bean
    1. Handler注入
    2. bean启动—>生命周期init()
      :::

代码实现

Session:存已登录的用户名和用户管道


//存储结构 从设计角度全局唯一map
//SessionManager
public class Session{
    private static final Map<String,Channel>usernameChannelMap
    private static final Map<Channel,String>channelUsernameMap
  //方法的五要素:修饰值 返回值  方法名(参数表)  异常
    // 1.多个用户信息   多个channel 多个数据
    // 2. key  value map
    public void bind (Channel channel,String username){

        usernameChannelMap.put(username,channel);
        channelUsernameMap.put(channel,username);
    }

    public void unbind (Channel channel){
        String username=channelUsernameMap.remove(channel);
        if(username!=null){
            usernameChannelMap.remove(username);
        }
    }
    public Channel getChannel(String username){

    }
}

群对象

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Group {
   private String groupName;
   private Set<String> members;
}

群会话session

public class GroupSession {

    //1 存储聊天室的信息
    //key string  聊天室的名字
    private static  final Map<String,Group> groupMap=new HashMap<>();
    public Group createGroup(String name, Set<String> members){
        Group group=new Group(name,members);
        /**
         * 向map添加key value
         * 如果添加key value 不存在 返回null
         */
        return groupMap.putIfAbsent(name,group);
    }
    //获取聊天室的成员
    public Set<String> getMembers(String name){
        return groupMap.get(name).getMembers();
    }
    //获取聊天室成员的channel
    public List<Channel> getMembersChannel(String name){
        List<Channel> retList=new ArrayList<>();
        Set<String> members = getMembers(name);

        for (String member : members) {
            Channel channel = Session.getChannel(member);
            retList.add(channel);
        }
        return retList;
    }

}

信息枚举类

/**
 * 信息枚举类
 */
public enum MessageType {
   LOGIN_REQUEST_MESSAGE ((byte) 1, LoginRequestMessage.class),
   LOGIN_RESPONSE_MESSAGE((byte) 2, LoginResponseMessage.class),
   CHAT_REQUEST_MESSAGE((byte) 3, ChatRequestMessage.class),
   CHAT_RESPONSE_MESSAGE((byte) 4,ChatResponseMessage.class),
   GROUP_CREATE_REQUEST_MESSAGE((byte) 5, GroupCreateRequestMessage.class),
   GROUP_CREATE_RESPONSE_MESSAGE((byte) 6,GroupCreateResponseMessage.class),
   GROUP_CHAT_REQUEST_MESSAGE((byte) 7,GroupChatRequestMessage.class),
   GROUP_CHAT_RESPONSE_MESSAGE((byte) 8,GroupChatResponseMessage.class),
   PING_MESSAGE((byte) 9,PingMessage.class),
   PONG_MESSAGE((byte) 10,PongMessage.class);





   private final   byte value;
   private final Class<?> classType;

   MessageType(byte value, Class<?> classType) {
      this.value = value;
      this.classType = classType;
   }



   public byte getValue() {
      return value;
   }

   public Class<?> getClassType() {
      return classType;
   }
   public static Class<?> getClass(byte value){
      switch (value){
         case 1: return LoginRequestMessage.class;

         case 2:return LoginResponseMessage.class;
         case 3:return ChatRequestMessage.class;
         case 4:return ChatResponseMessage.class;
         case 5:return GroupCreateRequestMessage.class;
         case 6:return GroupCreateResponseMessage.class;
         case 7:return GroupChatRequestMessage.class;
         case 8:return GroupChatResponseMessage.class;
         case 9:return PingMessage.class;
         case 10:return PongMessage.class;

         default:return null;
      }
   }

}

分别封装请求消息和效应消息
image.png
解码器:

@Slf4j
public class ChatByteToMessageDecoder extends ByteToMessageDecoder {
    @Override
    protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
        //1.幻术 suns
        ByteBuf buf = in.readBytes(4);
        //2. 协议版本 一个字节
        byte protoVersion = in.readByte();
        //3.序列化方式
        byte serializableNo = in.readByte();

        //4.功能指令
        byte funcNo = in.readByte();

        //5.正文长度
        int contentLength = in.readInt();

        //6.正文
        log.debug("解码"+in);
        Message message=null;
        String str = (String) in.readCharSequence(contentLength,Charset.forName("UTF-8"));
       message = (Message) JSONUtil.toBean(str, MessageType.getClass(funcNo));
        log.debug("解码"+message);
        out.add(message);
    }
}

编码器:

public class ChatMessageToByteEncoder extends MessageToByteEncoder<Message> {

    private static final Logger log = LoggerFactory.getLogger(ChatMessageToByteEncoder.class);

    @Override
    protected void encode(ChannelHandlerContext ctx, Message msg, ByteBuf out) throws Exception {
        log.debug("encode method invoke");
        //协议头
        //1.  幻术 四个字节 suns
        out.writeBytes("suns".getBytes());
        //2. 一个字节协议版本
        out.writeByte(1);
        //3. 序列化方式    1. json 2. protobuf  3. hession
        out.writeByte(1);
        //4. 功能指令  0.登录 1. 聊天
        out.writeByte(msg.gainMessageType());
        // 正文长度
        String jsonContent = JSONUtil.toJsonStr(msg);
        out.writeInt(jsonContent.length());
        out.writeCharSequence(jsonContent, Charset.forName("UTF-8"));
    }
}

handler
ChatRequestMessageHandler

@Slf4j
public class ChatRequestMessageHandler extends SimpleChannelInboundHandler<ChatRequestMessage> {
   @Override
   protected void channelRead0(ChannelHandlerContext ctx, ChatRequestMessage msg) throws Exception {
      String from = msg.getFrom();
      String to = msg.getTo();
      String content = msg.getContent();
      Channel channel = Session.getChannel(to);
      log.debug("from:{} to:{} content:{}",from,to,content);

      if(channel!=null){
         log.debug("发送成功");
         ChatResponseMessage chatResponseMessage =new ChatResponseMessage("200","chat ok",from,content);
         channel.writeAndFlush(chatResponseMessage);
         ctx.writeAndFlush(new ChatResponseMessage("200","send is OK"));
      }else {
         log.debug("发送失败");
         ctx.writeAndFlush(new ChatResponseMessage("500","error"));
      }


   }
}

LoginRequestMessageHandler


@Slf4j
public class LoginRequestMessageHandler extends SimpleChannelInboundHandler<LoginRequestMessage> {

   private static Map<String, String> userDB = new HashMap<>();

   static {
      userDB.put("suns1", "123456");
      userDB.put("suns2", "123456");
      userDB.put("suns3", "123456");
      userDB.put("suns4", "123456");
      userDB.put("suns5", "123456");
   }

   @Override
   protected void channelRead0(ChannelHandlerContext ctx, LoginRequestMessage msg) throws Exception {
      log.debug("login method invoke....");
      String username= msg.getUsername();
      String password = msg.getPassword();
      boolean isLogin=login(username,password);
      if(isLogin){
         log.debug("login is ok....");
         Session.bind(ctx.channel(),username);
         ctx.writeAndFlush(new LoginResponseMessage("200","login is ok"));
      }else {
         log.debug("login is error");
         ctx.writeAndFlush(new LoginResponseMessage("500","Check you name or password"));
      }
   }

   private boolean login(String username, String password) {
      String storePassword = userDB.get(username);
      if (storePassword == null || !password.equals(storePassword)) {
         return false;
      }
      return true;
   }
}

等handler
image.png

客户端

package com.suns.netty;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.suns.netty.codec.ChatByteToMessageDecoder;
import com.suns.netty.codec.ChatMessageToByteEncoder;
import com.suns.netty.message.*;
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;

import java.net.InetSocketAddress;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Scanner;
import java.util.Set;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;


@Slf4j
public class MyNettyClient {

    public static void main(String[] args) throws InterruptedException, JsonProcessingException {
        Scanner scanner = new Scanner(System.in);

        CountDownLatch WAIT_LOGIN = new CountDownLatch(1);
        AtomicBoolean LOGIN = new AtomicBoolean(false);
        AtomicBoolean SERVER_ERROR = new AtomicBoolean(false);

        EventLoopGroup eventLoopGroup = new NioEventLoopGroup();
        LoggingHandler LOGGING_HANDLER = new LoggingHandler();

        try {
            Bootstrap bootstrap = new Bootstrap();
            bootstrap.channel(NioSocketChannel.class);
            bootstrap.option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 50);
            Bootstrap group = bootstrap.group(eventLoopGroup);
            bootstrap.handler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(1024, 7, 4, 0, 0));
                    ch.pipeline().addLast(LOGGING_HANDLER);
                    ch.pipeline().addLast(new ChatMessageToByteEncoder());
                    ch.pipeline().addLast(new ChatByteToMessageDecoder());
                    ch.pipeline().addLast(new IdleStateHandler(8, 3, 0, TimeUnit.SECONDS));
                    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
                            if (idleStateEvent.state() == IdleState.WRITER_IDLE) {
                                ctx.writeAndFlush(new PingMessage("client"));
                            } else if (idleStateEvent.state() == IdleState.READER_IDLE) {
                                log.debug("服务器端 已经8秒没有响应数据了。。。");
                                log.debug("关闭channel");
                                ctx.channel().close();

                                SERVER_ERROR.set(true);
                                /*
                                close.addListener(new ChannelFutureListener() {
                                    @Override
                                    public void operationComplete(ChannelFuture future) throws Exception {
                                         //进行重连
                                    }
                                });
                                close.addListener(promise->{

                                });
                                */
                            }

                        }
                    });
                    ch.pipeline().addLast(new ChannelInboundHandlerAdapter() {

                        @Override
                        public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
                            log.debug("recive data {}", msg);
                            if (msg instanceof LoginResponseMessage) {
                                LoginResponseMessage loginResponseMessage = (LoginResponseMessage) msg;
                                if (loginResponseMessage.getCode().equals("200")) {
                                    LOGIN.set(true);
                                }
                                WAIT_LOGIN.countDown();
                            }


                        }

                        @Override
                        public void channelInactive(ChannelHandlerContext ctx) throws Exception {
                            if (SERVER_ERROR.get()) {
                               /*
                               当前执行这行代码的线程 ,后续会被netty回收,所以在这里直接重连不好
                                Channel channel = bootstrap.connect(new InetSocketAddress(8000)).sync().channel();
                                channel.closeFuture().sync();
                                */
                                log.debug("重连...");

                                //等价于从EventLoopGroup中 从新获得了
                                EventLoop eventLoop = ctx.channel().eventLoop();
                                eventLoop.submit(() -> {
                                    Channel channel = null;
                                    try {
                                        ChannelFuture connect = bootstrap.connect(new InetSocketAddress(8000));

                                        connect.addListener(promise -> {
                                            //promise.isSuccess();
                                            log.debug("{} ", promise.cause());
                                        });

                                        Channel channel1 = connect.sync().channel();
                                    } catch (InterruptedException e) {
                                        throw new RuntimeException(e);
                                    }
                                    try {
                                        channel.closeFuture().sync();
                                    } catch (InterruptedException e) {
                                        throw new RuntimeException(e);
                                    }
                                });


                            } else {
                                log.debug("client close ...");
                            }

                        }

                        @Override
                        public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
                            log.debug("client close....");
                            super.exceptionCaught(ctx, cause);
                        }

                        @Override
                        public void channelActive(ChannelHandlerContext ctx) throws Exception {
                            //用户名 密码 让用户输入 Scanner 输入
                            //用户的输入 菜单的展示  UI相关的内容
                            new Thread(() -> {
                                System.out.println("请输入用户名: ");
                                String username = scanner.nextLine();

                                System.out.println("请输入密码: ");
                                String password = scanner.nextLine();

                                //发送登录操作
                                LoginRequestMessage loginRequestMessage = new LoginRequestMessage(username, password);
                                ctx.writeAndFlush(loginRequestMessage);

                                try {
                                    WAIT_LOGIN.await();
                                } catch (InterruptedException e) {
                                    throw new RuntimeException(e);
                                }

                                if (!LOGIN.get()) {
                                    ctx.channel().close();
                                    return;
                                }

                                while (true) {
                                    System.out.println("========================================");
                                    System.out.println("send [username] [content]");
                                    System.out.println("gcreate [group name] [m1,m2,m3...]");
                                    System.out.println("gsend [group name] [content]");
                                    System.out.println("quit");
                                    System.out.println("========================================");

                                    String command = scanner.nextLine();
                                    String[] args = command.split(" ");
                                    switch (args[0]) {
                                        case "send":
                                            ctx.writeAndFlush(new ChatRequestMessage(username, args[1], args[2]));
                                            break;
                                        case "gcreate":
                                            String groupName = args[1];
                                            String[] membersString = args[2].split(",");
                                            Set<String> members = new HashSet<>(Arrays.asList(membersString));
                                            members.add(username);
                                            ctx.writeAndFlush(new GroupCreateRequestMessage(groupName, members));
                                            break;
                                        case "gsend":
                                            String gName = args[1];
                                            String content = args[2];
                                            ctx.writeAndFlush(new GroupChatRequestMessage(username, gName, content));
                                            break;
                                        case "quit":
                                            //服务器端 unbind
                                            ctx.channel().close();
                                            return;
                                    }

                                }


                            }, "Client UI").start();

                        }
                    });
                }
            });
            Channel channel = bootstrap.connect(new InetSocketAddress(8000)).sync().channel();
            channel.closeFuture().sync();
         /*   ChannelFuture connect = bootstrap.connect(new InetSocketAddress(8000));
            connect.addListener(promise -> {
                   if(!promise.isSuccess()){
                      //重试 3次
                   }else{
                       Channel channel = connect.channel();
                       channel.closeFuture().sync();
                   }
            });*/


        } catch (InterruptedException e) {
            log.error("client error ", e);
        } finally {
            eventLoopGroup.shutdownGracefully();
        }

    }
}

服务端

package com.suns.netty;

import com.suns.netty.codec.ChatByteToMessageDecoder;
import com.suns.netty.codec.ChatMessageToByteEncoder;
import com.suns.netty.handler.*;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;
import io.netty.handler.logging.LoggingHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import io.netty.handler.timeout.IdleStateHandler;
import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.TimeUnit;

/*
RPC  ---> dubbo

Spring ---> 工厂的
SpringBoot应用 ---> 启动类 main ---> 服务端启动
1. 服务器 一定是一个 Bean
                      1. Handler注入
                      2. bean启动 ---> 生命周期 init()

                  @Import(Bean.class)--->spi

                  启动器 starter
                  1. 导入jar---> @Import()
                  2. AutoConfigure



 */
@Slf4j
public class MyNettyServer {

    public static void main(String[] args) {
        LoggingHandler LOGGING_HANDLER = new LoggingHandler();
        EventLoopGroup bossGroup = new NioEventLoopGroup(1);
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        LoginRequestMessageHandler LOGINREQUESTMESSAGEHANDLER = new LoginRequestMessageHandler();
        ChatRequestMessageHandler CHATREQUESTMESSAGEHANDLER = new ChatRequestMessageHandler();
        GroupCreateMessageHandler GROUPCREATEMESSAGEHANDLER = new GroupCreateMessageHandler();
        GroupChatRequestMessasgeHandler GROUPCHATREQUESTMESSASGEHANDLER = new GroupChatRequestMessasgeHandler();
        QuitHandler QUITHANDLER = new QuitHandler();


        try {
            ServerBootstrap serverBootstrap = new ServerBootstrap();
            serverBootstrap.channel(NioServerSocketChannel.class);
            serverBootstrap.group(bossGroup, workerGroup);
            serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
                @Override
                protected void initChannel(NioSocketChannel ch) throws Exception {
                    ChannelPipeline pipeline = ch.pipeline();
                    pipeline.addLast(new LengthFieldBasedFrameDecoder(1024, 7, 4, 0, 0));
                    pipeline.addLast(LOGGING_HANDLER);
                    pipeline.addLast(new ChatByteToMessageDecoder());
                    pipeline.addLast(new ChatMessageToByteEncoder());
                    pipeline.addLast(new IdleStateHandler(8, 3, 0, TimeUnit.SECONDS));
                    pipeline.addLast(new ChannelInboundHandlerAdapter() {
                        @Override
                        public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
                            IdleStateEvent idleStateEvent = (IdleStateEvent) evt;
                            if (idleStateEvent.state() == IdleState.READER_IDLE) {
                                log.debug("client已经8s没有与服务端通信..... ");
                                log.debug("关闭channel");
                                ctx.channel().close();
                            }else if(idleStateEvent.state() == IdleState.WRITER_IDLE){
                                //代码在实际开发中应该应用,但是为了模拟client的处理 暂时注释
                               //ctx.writeAndFlush(new PongMessage("server"));
                            }
                        }
                    });
                    pipeline.addLast(LOGINREQUESTMESSAGEHANDLER);
                    pipeline.addLast(CHATREQUESTMESSAGEHANDLER);
                    pipeline.addLast(GROUPCREATEMESSAGEHANDLER);
                    pipeline.addLast(GROUPCHATREQUESTMESSASGEHANDLER);
                    pipeline.addLast(QUITHANDLER);
                }
            });
            Channel channel = serverBootstrap.bind(8000).sync().channel();
            channel.closeFuture().sync();
        } catch (Exception e) {
            log.error("server error", e);
        } finally {
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值