第 2 篇 : Netty客户端之间互发消息和服务端广播

news/2024/10/19 5:29:14/

1. 服务端改造

1.1 修改 Message类

package com.hahashou.netty.server.config;import com.alibaba.fastjson.JSON;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.util.CharsetUtil;
import lombok.Data;/*** @description:* @author: 哼唧兽* @date: 9999/9/21**/
@Data
public class Message {/** 发送者用户code */private String userCode;/** 接收者用户code */private String friendUserCode;/** 连接时专用 */private String channelId;/** 文字 */private String text;public static ByteBuf transfer(Message message) {return Unpooled.copiedBuffer(JSON.toJSONString(message), CharsetUtil.UTF_8);}
}

1.2 修改 NettyServerHandler类

package com.hahashou.netty.server.config;import com.alibaba.fastjson.JSON;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;/*** @description:* @author: 哼唧兽* @date: 9999/9/21**/
@Component
@ChannelHandler.Sharable
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {/** key: 用户code; value: channelId */public static Map<String, String> USER_CHANNEL = new ConcurrentHashMap<>(32);/** key: channelId; value: Channel */public static Map<String, Channel> CHANNEL = new ConcurrentHashMap<>(32);@Overridepublic void channelActive(ChannelHandlerContext ctx) {Channel channel = ctx.channel();String channelId = channel.id().asLongText();log.info("有客户端连接, channelId : " + channelId);CHANNEL.put(channelId, channel);Message message = new Message();message.setChannelId(channelId);channel.writeAndFlush(Message.transfer(message));}@Overridepublic void channelInactive(ChannelHandlerContext ctx) {log.info("有客户端断开连接, channelId : {}", ctx.channel().id().asLongText());CHANNEL.remove(ctx.channel().id().asLongText());}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {if (msg != null) {Message message = JSON.parseObject(msg.toString(), Message.class);String userCode = message.getUserCode(),channelId = message.getChannelId(),text = message.getText();if (StringUtils.hasText(userCode) && StringUtils.hasText(channelId)) {log.info("客户端 " + userCode + " 连接");USER_CHANNEL.put(userCode, channelId);} else if (StringUtils.hasText(text)) {String friendUserCode = message.getFriendUserCode();if (StringUtils.hasText(friendUserCode)) {// 向某客户端发送String queryChannelId = USER_CHANNEL.get(friendUserCode);if (StringUtils.hasText(queryChannelId)) {Channel channel = CHANNEL.get(queryChannelId);if (channel == null) {log.error(friendUserCode + "已下线, 存储消息 :{}", text);return;}channel.writeAndFlush(Message.transfer(message));} else {log.error("无此用户信息");}} else {// 向服务端发送时, 返回当前时间message.setUserCode("ADMIN");message.setText(LocalDateTime.now().toString());ctx.channel().writeAndFlush(Message.transfer(message));}}}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {log.info("有客户端发生异常, channelId : {}", ctx.channel().id().asLongText());}
}

1.3 修改 ServerController类

package com.hahashou.netty.server.controller;import com.hahashou.netty.server.config.Message;
import com.hahashou.netty.server.config.NettyServerHandler;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @description:* @author: 哼唧兽* @date: 9999/9/21**/
@RestController
@RequestMapping("/server")
@Slf4j
public class ServerController {@PostMapping("/send")public String send(@RequestBody Message dto) {String friendUserCode = dto.getFriendUserCode();if (StringUtils.hasText(friendUserCode)) {String channelId = NettyServerHandler.USER_CHANNEL.get(friendUserCode);if (StringUtils.hasText(channelId)) {Channel channel = NettyServerHandler.CHANNEL.get(channelId);if (channel == null) {return "该客户端已下线, 需要存储相关信息";}channel.writeAndFlush(Message.transfer(dto));} else {log.error("无此用户信息");}} else {// 全体广播NettyServerHandler.USER_CHANNEL.forEach((userCode, channelId) -> {Channel channel = NettyServerHandler.CHANNEL.get(channelId);if (channel == null) {log.error(userCode + "已下线, 需要存储相关信息");return;}channel.writeAndFlush(Message.transfer(dto));});}return "success";}
}

1.4 启动服务端

2. 客户端改造

2.1 复制服务端的 Message类

2.2 修改 NettyClientHandler类

package com.hahashou.netty.client.config;import com.alibaba.fastjson.JSON;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;/*** @description:* @author: 哼唧兽* @date: 9999/9/21**/
@Component
@ChannelHandler.Sharable
@Slf4j
public class NettyClientHandler extends ChannelInboundHandlerAdapter {@Value("${userCode}")private String userCode;public static Channel CHANNEL;@Overridepublic void channelActive(ChannelHandlerContext ctx) {CHANNEL = ctx.channel();log.info("客户端 " + userCode + " 上线");}@Overridepublic void channelInactive(ChannelHandlerContext ctx) {CHANNEL = null;}@Overridepublic void channelRead(ChannelHandlerContext ctx, Object msg) {if (msg != null) {Message message = JSON.parseObject(msg.toString(), Message.class);String channelId = message.getChannelId(),text = message.getText();if (StringUtils.hasText(channelId)) {Channel channel = ctx.channel();message.setUserCode(userCode);channel.writeAndFlush(Message.transfer(message));} else if (StringUtils.hasText(text)) {log.info("收到" + message.getUserCode() + "消息: {}", text);}}}@Overridepublic void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {CHANNEL = null;}
}

2.3 修改 ClientController类

package com.hahashou.netty.client.controller;import com.hahashou.netty.client.config.Message;
import com.hahashou.netty.client.config.NettyClientHandler;
import io.netty.channel.Channel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;/*** @description:* @author: 哼唧兽* @date: 9999/9/21**/
@RestController
@RequestMapping("/client")
@Slf4j
public class ClientController {@PostMapping("/send")public String send(@RequestBody Message dto) {Channel channel = NettyClientHandler.CHANNEL;if (channel == null) {return "服务端已下线";}channel.writeAndFlush(Message.transfer(dto));return "success";}
}

3. 通过修改配置信息, 生成2个客户端

3.1 A客户端 application.yml, 打包jar

server:port: 32001logging:level:com.hahashou.netty: infouserCode: Aa

3.2 B客户端 application.yml, 打包jar

server:port: 32002logging:level:com.hahashou.netty: infouserCode: Bb

3.3 启动A、B客户端

A与B服务启动

4. 测试

4.1 A与B互发消息

A向B发送消息json示例
根据上述示例, 以及下述图片, 自行补充相关信息(注意服务的切换)
A收到的消息
B收到的消息

4.2 A客户端向服务端发送消息

A向服务端发送消息
A客户端显示的时间

4.3 服务端向A单独通知以及全体广播

向A单独通知
全体广播
A客户端
A客户端
B客户端
B客户端


http://www.ppmy.cn/news/1437706.html

相关文章

OpenStack云计算(十)——OpenStack虚拟机实例管理,增加一个计算节点并进行实例冷迁移,增加一个计算节点的步骤,实例冷迁移的操作方法

项目实训一 本实训任务对实验环境要求较高&#xff0c;而且过程比较复杂&#xff0c;涉及的步骤非常多&#xff0c;有一定难度&#xff0c;可根据需要选做。可以考虑改为直接观看相关的微课视频 【实训题目】 增加一个计算节点并进行实例冷迁移 【实训目的】 熟悉增加一个…

6、ES单机设置用户名密码、集群设置用户名密码、es-head登录、如何去掉密码

目录 一、ES单节点密码配置1、修改配置文件2、 重启es服务3&#xff0c;执行修改密码命令4、访问服务 二、ES集群密码配置1、确定主节点2、生成elastic-stack-ca.p123、生成elastic-certificates.p124、修改配置文件并重启集群5、进行密码配置6、验证 三、es-head登录增加密码的…

信创需求激增,国产服务器操作系统赋能数字化转型

信创&#xff0c;即信息技术应用创新&#xff0c;是指在关键领域和环节推进信息技术的自主创新&#xff0c;构建安全可控的信息技术体系。随着数字化转型的加速&#xff0c;信创需求激增&#xff0c;国产服务器操作系统在其中扮演着至关重要的角色。国产服务器操作系统如何赋能…

java:SpringBoot入门

Spring 提供若干子项目,每个项目用于完成特定功能 Spring Boot 可以简化配置并且快速开发 SpringBootWeb快速入门 创建Springboot模块并使用Springweb依赖 在类上添加注解 RestController可以将字符串自动转成json返回数据给页面 再在方法上添加注解 RequestMapping(&…

c# 画球

主代码 using System; using System.Collections.Generic; using System.ComponentModel; using System.Data; using System.Drawing; using System.Linq; using System.Text; using System.Threading.Tasks; using System.Windows.Forms;namespace RayTracing {public partia…

Python和C++音调音符规划和算法

&#x1f3af;要点 &#x1f3af;音符表征和代码实现&#xff1a;&#x1f58a;半音位置和索引之间的转换 | &#x1f58a;全音阶音调表征 | &#x1f58a;全音阶缓存 | &#x1f58a;全音阶音高表征。&#x1f3af;音程表征和代码实现&#xff1a;&#x1f58a;音程质量 | &a…

visionTransformer window平台下报错

错误&#xff1a; KeyError: Transformer/encoderblock_0/MlpBlock_3/Dense_0kernel is not a file in the archive解决方法&#xff1a; 修改这个函数即可&#xff0c;主要原因是Linux系统与window系统路径分隔符不一样导致 def load_from(self, weights, n_block):ROOT f&…

使用rust学习基本算法(三)

使用rust学习基本算法&#xff08;三&#xff09; 动态规划 动态规划算法是一种在数学、管理科学、计算机科学和经济学中广泛使用的方法&#xff0c;用于解决具有重叠子问题和最优子结构特性的问题。它通过将复杂问题分解为更小的子问题来解决问题&#xff0c;这些子问题被称为…