Netty初学六 客户端登录以及客户端与服务端收发消息

一、登录流程:

1.登录流程图:

客户端首先构建一个登录请求对象,然后通过编码把请求对象编码为ByteBuf,写到服务端

服务端收到ByteBuf之后首先通过解码将ByteBuf解码为登录请求对象然后进行校验

服务端校验通过之后构建一个登录响应对象然后再次经过编码再次写回客户端

客户端接收服务端的响应数据之后解码ByteBuf,获得登录响应的对象判断是否登录成功

2.客户端发送登录请求:

java 复制代码
 public void channelActive(ChannelHandlerContext ctx) {
    System.out.println(new Date() + ": 客户端开始登录");
    // 创建登录对象
LoginRequestPacket loginRequestPacket = new LoginRequestPacket();
loginRequestPacket.setUserId(UUID.randomUUID().toString());
loginRequestPacket.setUsername("flash");
loginRequestPacket.setPassword("pwd");
// 编码
ByteBuf buffer = PacketCodeC.INSTANCE.encode(ctx.alloc(), loginRequ
 estPacket);
// 写数据
ctx.channel().writeAndFlush(buffer);
}

写数据时先通过ctx.channel()获取当前连接,然后调用writeAndFlush()把二进制数据写到服务器

3.服务端处理登录请求:

java 复制代码
 public void channelRead(ChannelHandlerContext ctx, Object msg) {
  ByteBuf requestByteBuf = (ByteBuf) msg;
    // 解码
    Packet packet = PacketCodeC.INSTANCE.decode(requestByteBuf);
    // 判断是否是登录请求数据包
    if (packet instanceof LoginRequestPacket) {
        LoginRequestPacket loginRequestPacket = (LoginRequestPacket) pack
 et;
        // 登录校验
        if (valid(loginRequestPacket)) {
            // 校验成功
        } else {
            // 校验失败
        }
    }
}
private boolean valid(LoginRequestPacket loginRequestPacket) {
    return true;
}

4.服务端发送登录响应:

java 复制代码
 LoginResponsePacket loginResponsePacket = new LoginResponsePacket()
;
loginResponsePacket.setVersion(packet.getVersion());
if (valid(loginRequestPacket)) {
loginResponsePacket.setSuccess(true);
} else {
loginResponsePacket.setReason("账号密码校验失败");
loginResponsePacket.setSuccess(false);
}
// 编码
ByteBuf responseByteBuf = PacketCodeC.INSTANCE.encode(ctx.alloc(), l
 oginResponsePacket);
ctx.channel().writeAndFlush(responseByteBuf);

5.客户端处理登录响应:

java 复制代码
public void channelRead(ChannelHandlerContext ctx, Object msg) {
ByteBuf byteBuf = (ByteBuf) msg;
Packet packet = PacketCodeC.INSTANCE.decode(byteBuf);
if (packet instanceof LoginResponsePacket) {
LoginResponsePacket loginResponsePacket = (LoginResponsePacket) 
packet;
if (loginResponsePacket.isSuccess()) {
System.out.println(new Date() + ": 客户端登录成功");
} else {
System.out.println(new Date() + ": 客户端登录失败,原因:" +
 loginResponsePacket.getReason());
}
}
}

客户端拿到数据之后,调用PacketCodeC进行解码操作,如果类型是登录响应包,即在控制台打印出一条消息

6.客户端的输出:

服务端输出:

二、收发消息对象

1.代码示例:客户端发送至服务端的消息对象的定义

java 复制代码
@Data
 public class MessageRequestPacket extends Packet {
private String message;
@Override
 public Byte getCommand() {
return MESSAGE_REQUEST;
}
}

服务端发送到客户端的消息对象的定义(示例代码):

java 复制代码
@Data
 public class MessageResponsePacket extends Packet {
private String message;
@Override
 public Byte getCommand() {
return MESSAGE_RESPONSE;
}
}

指令:

java 复制代码
public interface Command {
Byte LOGIN_REQUEST = 1;
Byte LOGIN_RESPONSE = 2;
Byte MESSAGE_REQUEST = 3;
Byte MESSAGE_RESPONSE = 4;
}

2.判断客户端是否登录成功:

思路:给客户端连接Channel绑定属性,通过channel.attr().set()方式,在登录成功之后给Channel绑定一个登录成功的标志位,然后再判断登录成功的时候取出这个标志位

定义标志位:

java 复制代码
public interface Attributes {
AttributeKey<Boolean> LOGIN = AttributeKey.newInstance("login");
}

给客户端绑定登录成功的标志位:

java 复制代码
 public void channelRead(ChannelHandlerContext ctx, Object msg) {
// ...
 if (loginResponsePacket.isSuccess()) {
LoginUtil.markAsLogin(ctx.channel());
System.out.println(new Date() + ": 客户端登录成功");
  } else {
            System.out.println(new Date() + ": 客户端登录失败,原因:" +
 loginResponsePacket.getReason());
        }
    // ...
}
public class LoginUtil {
    public static void markAsLogin(Channel channel) {
        channel.attr(Attributes.LOGIN).set(true);
    }
    public static boolean hasLogin(Channel channel) {
        Attribute<Boolean> loginAttr = channel.attr(Attributes.LOGIN);
        return loginAttr.get() != null;
    }
}

在控制台输入登录消息并发送:

java 复制代码
 private static void connect(Bootstrap bootstrap, String host, int port, int retr
 y) {
    bootstrap.connect(host, port).addListener(future -> {
        if (future.isSuccess()) {
            Channel channel = ((ChannelFuture) future).channel();
            // 连接成功之后,启动控制台线程
            startConsoleThread(channel);
        }
        // ...
    });
}
private static void startConsoleThread(Channel channel) {
    new Thread(() -> {
        while (!Thread.interrupted()) {
            if (LoginUtil.hasLogin(channel)) {
                System.out.println("输入消息发送至服务端: ");
                Scanner sc = new Scanner(System.in);
                String line = sc.nextLine();
                MessageRequestPacket packet = new MessageRequestPacket();
                packet.setMessage(line);
                ByteBuf byteBuf = PacketCodeC.INSTANCE.encode(channel.allo
 c(), packet);
                channel.writeAndFlush(byteBuf);
            }
        }
    }).start();
}

服务端收发消息处理:

java 复制代码
 public void channelRead(ChannelHandlerContext ctx, Object msg) {
    ByteBuf requestByteBuf = (ByteBuf) msg;
    Packet packet = PacketCodeC.INSTANCE.decode(requestByteBuf);
    if (packet instanceof LoginRequestPacket) {
        // 处理登录
    } else if (packet instanceof MessageRequestPacket) {
        // 处理消息
        MessageRequestPacket messageRequestPacket = ((MessageRequestPac
 ket) packet);
        System.out.println(new Date() + ": 收到客户端消息: " +
 messageRequestPacket.getMessage());
        MessageResponsePacket messageResponsePacket = new MessageResp
 onsePacket();
        messageResponsePacket.setMessage("服务端回复【" +
 messageRequestPacket.getMessage() + "】");
ByteBuf responseByteBuf = PacketCodeC.INSTANCE.encode(ctx.allo
 c(),
messageResponsePacket);
ctx.channel().writeAndFlush(responseByteBuf);
}
}
相关推荐
小皮侠35 分钟前
【算法篇】逐步理解动态规划模型6(回文串问题)
java·开发语言·算法·动态规划
勤奋的小王同学~38 分钟前
(javaSE)抽象类和接口:抽象类概念语法和特性, 抽象类的作用;接口的概念 接口特性 实现多个接口 接口间的继承 Object类
java·开发语言
Ai财富密码1 小时前
【Linux教程】Linux 生存指南:掌握常用命令,避开致命误操作
java·服务器·前端
LUCIAZZZ1 小时前
项目拓展-Jol分析本地对象or缓存的内存占用
java·开发语言·jvm·数据库·缓存·springboot
GalaxyPokemon1 小时前
LeetCode - 69. x 的平方根
java·数据结构·算法
在未来等你2 小时前
设计模式精讲 Day 1:单例模式(Singleton Pattern)
java·设计模式·面向对象·软件架构
heart000_12 小时前
基于 WebWorker 的 WebAssembly 图像处理吞吐量分析
java·图像处理·wasm
菜鸟阿达2 小时前
Idea 2025 commit 关闭侧边栏 开启探框
java·ide·intellij-idea
雨果talk2 小时前
【一文看懂多模块Bean初始化难题】Spring Boot多模块项目中的Bean初始化难题:包名不一致的优雅解决方案
java·spring boot·后端·spring·springboot
xiaowu0802 小时前
C# 中的Async 和 Await 的用法详解
java·开发语言·c#