一、登录流程:
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);
}
}

