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

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);

    @Override
    public 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));
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        log.info("有客户端断开连接, channelId : {}", ctx.channel().id().asLongText());
        CHANNEL.remove(ctx.channel().id().asLongText());
    }

    @Override
    public 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));
                }
            }
        }
    }

    @Override
    public 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;

    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        CHANNEL = ctx.channel();
        log.info("客户端 " + userCode + " 上线");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        CHANNEL = null;
    }

    @Override
    public 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);
            }
        }
    }

    @Override
    public 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: 32001

logging:
  level:
    com.hahashou.netty: info

userCode: Aa

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

server:
  port: 32002

logging:
  level:
    com.hahashou.netty: info

userCode: 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客户端

  • 4
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

哈哈兽0026

你的鼓励将是我创作的最大动力

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

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

打赏作者

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

抵扣说明:

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

余额充值