netty聊天室

pom文件

<dependencies>
        <!--<dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-thymeleaf</artifactId>
        </dependency>-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.2</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <!--阿里巴巴druid 数据源-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.21</version>
        </dependency>
        <!--热部署-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>
        <!--mybatis 逆向生成器-->
        <dependency>
            <groupId>org.mybatis.generator</groupId>
            <artifactId>mybatis-generator-maven-plugin</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!--mapper -->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>1.2.4</version>
        </dependency>
        <dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.50.Final</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.github.tobato/fastdfs-client -->
        <dependency>
            <groupId>com.github.tobato</groupId>
            <artifactId>fastdfs-client</artifactId>
            <version>1.27.2</version>
        </dependency>

        <!-- apache 工具类 -->
        <!--用于摘要运算、编码解码的包。常见的编码解码工具Base64、MD5、Hex、SHA1、DES等-->
        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.11</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-lang3</artifactId>
            <version>3.4</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
        <!-- 二维码 -->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.3.3</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-milestones</id>
            <name>Spring Milestones</name>
            <url>https://repo.spring.io/milestone</url>
            <snapshots>
                <enabled>false</enabled>
            </snapshots>
        </repository>
    </repositories>

netty服务器

@Component
public class WebSocketServer {

    private static class SingletionWSServer{
        static final WebSocketServer instant=new WebSocketServer();
    }

    public static WebSocketServer getInstance()
    {
        return SingletionWSServer.instant;
    }
	//主线程池
    private EventLoopGroup mainGroup;
    //从线程池
    private EventLoopGroup subGroup;
    private ServerBootstrap server;
    private ChannelFuture future;

    public WebSocketServer(){
        mainGroup=new NioEventLoopGroup();
        subGroup=new NioEventLoopGroup();
        server=new ServerBootstrap();
        server.group(mainGroup,subGroup)
                .channel(NioServerSocketChannel.class)
                .childHandler(new WebSocketInitialize());
    }

    public void start()
    {
        this.future=server.bind("192.168.5.137",8888);
        if(future.isSuccess())
        {
            System.out.println("启动netty成功");
        }
    }
}

初始化管道

public class WebSocketInitialize extends ChannelInitializer<SocketChannel> {

    protected void initChannel(SocketChannel channel) throws Exception {
        ChannelPipeline pipeline = channel.pipeline();

        pipeline.addLast(new HttpServerCodec());
        //在http上有一些数据流产生,有大有小,我们对其进行处理
        pipeline.addLast(new ChunkedWriteHandler());
        //对httpMessage进行聚合处理,聚合成request或者response
        pipeline.addLast(new HttpObjectAggregator(1024*64));
        /**
         * 本handler会帮你处理一些繁重复杂的事情,帮你处理握手动作
         * handshaking (close,ping,pong) ping+pong=心跳
         * 对于webSocket来说,都是以frames 进行传输的,不同数据类型对应的frames也不同
         */
        pipeline.addLast(new WebSocketServerProtocolHandler("/ws"));
        //自定义handler
        pipeline.addLast(new ChatHandler());
    }
}

子处理器

public class ChatHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {

    //用于记录和管理所以客户端的channel
    public static ChannelGroup users=new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    protected void channelRead0(ChannelHandlerContext ctx, TextWebSocketFrame msg) 
        throws Exception {
       //获取客户端所传递的消息
        String content = msg.text();
       //1:获取客户端所传递的消息
        DataContent dataContent = JsonUtils.jsonToPojo(content, DataContent.class);
        Integer action = dataContent.getAction();
        Channel channel=ctx.channel();
        //2:判断消息的类型,根据不同的类型处理不同的业务
        if(action== MsgActionEnum.CONNECT.type){
            //2.1 当webSocket 第一次启动的时候,初始化channel,把channel和userId关联起来
            String senderId = dataContent.getChatMsg().getSenderId();
            UserChannelRel.put(senderId,channel);
            /**
             * 测试
             */
            for(Channel c:users)
            {
                System.out.println(c.id().asLongText());
            }
            UserChannelRel.outPut();
        }else if(action== MsgActionEnum.CHAT.type){
            //2.2 聊天类型的消息,把聊天记录保存到数据库中,同时标记消息的签收状态(未签收)
            ChatMsg chatMsg=dataContent.getChatMsg();
            String msgContent = chatMsg.getMsg();
            String senderId = chatMsg.getSenderId();
            String receiverId = chatMsg.getReceiverId();
            //保存消息到数据库,并且标记为未签收
            UserService userService= (UserService) SpringUtil.getBean("userServiceImpl");
            String msgId = userService.saveMsg(chatMsg);
            chatMsg.setMsgId(msgId);
            DataContent dataContentMsg = new DataContent();
            dataContentMsg.setChatMsg(chatMsg);
            //发送消息
            Channel receiveChannel = UserChannelRel.get(receiverId);
            if(receiveChannel==null){
                //离线用户
            } else {
                //当 receiveChannel 不为空的时候,从ChannelGroup 去查找对应的channel是否存在
                Channel findChannel = users.find(receiveChannel.id());
                if(findChannel!=null){
                    receiveChannel.writeAndFlush(
                            new TextWebSocketFrame(
                                    JsonUtils.objectToJson(dataContentMsg)
                            )
                    );
                } else {
                    //离线用户
                }
            }
        }else if(action== MsgActionEnum.SIGNED.type){
            //2.3 签收消息类型,针对具体消息进行签收,修改数据库中对应消息的签收状态(已签收)
            UserService userService= (UserService) SpringUtil.getBean("userServiceImpl");
            //扩展字段signed类型消息中,代表要求签收消息的id,逗号间隔
            String msgIdStr = dataContent.getExtand();
            String[] msgsId = msgIdStr.split(",");

            List<String> msgIdList = new ArrayList<>();
            for(String mid:msgsId)
            {
                if(StringUtils.isNotBlank(mid))
                {
                    msgIdList.add(mid);
                }
            }
            System.out.println(msgIdList.toString());
            if(msgIdList!=null&&!msgIdList.isEmpty()&&msgIdList.size()>0)
            {
                //对消息批量签收
                userService.updateMsgSigned(msgIdList);
            }
        }else if(action== MsgActionEnum.KEEPALIVE.type){
            //2.4 心跳类型的消息
            System.out.println("收到来自channel 为【"+channel+"】的心跳包");
        }




    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        users.add(ctx.channel());
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        users.remove(ctx.channel());
    }

    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) 
    throws Exception {
        cause.printStackTrace();
        //发生了异常需要关闭连接,同时从ChannelGroup 中移除
        ctx.channel().close();
        users.remove(ctx.channel());
    }
}

用户id和channel之间的关联关系处理

/**
 * ProjectName im_bird_sys
 * 用户id和channel之间的关联关系处理
 * @author xieyucan
 * <br>CreateDate 2022/9/28 18:12
 */
public class UserChannelRel {

    private static HashMap<String, Channel> manager=new HashMap<>();

    public static void put(String sendId,Channel channel)
    {
        manager.put(sendId, channel);
    }

    public static Channel get(String sendId)
    {
        return manager.get(sendId);
    }

    public static void outPut()
    {
        for(Map.Entry<String, Channel> entry:manager.entrySet())
        {
            System.out.println("UserId:"+entry.getKey()+",channelId:"
            +entry.getValue().id().asLongText());
        }
    }
}

聊天内容类

@Data
@AllArgsConstructor
@NoArgsConstructor
public class DataContent implements Serializable {

    /**
     * 动作类型
     */
    private Integer action;

    /**
     * 聊天内容
     */
    private ChatMsg chatMsg;

    /**
     * 扩展字段
     */
    private String extand;
    
}

心跳机制

/**
 * 用于检测channel 的心跳handler
 * 继承ChannelInboundHandlerAdapter,目的是不需要实现ChannelRead0 这个方法
 */
public class HeartBeatHandler extends ChannelInboundHandlerAdapter {

    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) 
        throws Exception {
        if(evt instanceof IdleStateEvent){
            IdleStateEvent event = (IdleStateEvent)evt;//强制类型转化
            if(event.state()== IdleState.READER_IDLE){
                System.out.println("进入读空闲......");
            }else if(event.state() == IdleState.WRITER_IDLE) {
                System.out.println("进入写空闲......");
            }else if(event.state()== IdleState.ALL_IDLE){
                System.out.println("channel 关闭之前:users 的数量为:"+
                                   ChatHandler.users.size());
                Channel channel = ctx.channel();
                //资源释放
                channel.close();
                System.out.println("channel 关闭之后:users 的数量为:"+
                                   ChatHandler.users.size());

            }
        }
    }
}

监听netty状态,时刻开启netty

@Component
public class NettyBooter implements ApplicationListener<ContextRefreshedEvent> {

    @Override
    public void onApplicationEvent(ContextRefreshedEvent event) {
        if(event.getApplicationContext().getParent()==null)
        {
            try {
                WebSocketServer.getInstance().start();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值