Ps:此係列文章來源於Netty 入門與實戰:仿寫微信 IM 即時通訊系統,知識付費的時代,有能力請支持正版,一頓飯錢而已~
*登陸流程
客戶端連接上服務端之後
- 客戶端會構建一個登錄請求對象,然後通過編碼把請求對象編碼爲 ByteBuf,寫到服務端。
- 服務端接受到 ByteBuf 之後,首先通過解碼把 ByteBuf 解碼爲登錄請求響應,然後進行校驗。
- 服務端校驗通過之後,構造一個登錄響應對象,依然經過編碼,然後再寫回到客戶端。
- 客戶端接收到服務端的之後,解碼 ByteBuf,拿到登錄響應響應,判斷是否登陸成功
*邏輯處理器
客戶端啓動的時候,給客戶端配置的邏輯處理器叫做 ClientHandler。
這樣,在客戶端側,Netty 中 IO 事件相關的回調就能夠回調到我們的 ClientHandler
。
bootstrap.handler(new ChannelInitializer<SocketChannel>() {
@Override
public void initChannel(SocketChannel ch) {
ch.pipeline().addLast(new ClientHandler());
}
});
同理,給服務端引導類 ServerBootstrap
也配置一個邏輯處理器 ServerHandler。
在服務端側,Netty 中 IO 事件相關的回調就能夠回調到我們的 ServerHandler
。
serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel ch) {
ch.pipeline().addLast(new ServerHandler());
}
}
*客戶端發送登錄請求
客戶端處理登錄請求
ClientHandler.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(), loginRequestPacket);
// 寫數據
ctx.channel().writeAndFlush(buffer);
}
在編碼的環節,我們把 PacketCodeC
變成單例模式,然後把 ByteBuf
分配器抽取出一個參數,這裏第一個實參 ctx.alloc()
獲取的就是與當前連接相關的 ByteBuf
分配器,建議這樣來使用。
寫數據的時候,我們通過 ctx.channel()
獲取到當前連接(Netty 對連接的抽象爲 Channel,後面小節會分析),然後調用 writeAndFlush()
就能把二進制數據寫到服務端。
服務端處理登錄請求
ServerHandler.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) packet;
// 登錄校驗
if (valid(loginRequestPacket)) {
// 校驗成功
} else {
// 校驗失敗
}
}
}
private boolean valid(LoginRequestPacket loginRequestPacket) {
return true;
}
拿到 ByteBuf
之後,首先要做的事情就是解碼,解碼出 java 數據包對象,然後判斷如果是登錄請求數據包 LoginRequestPacket
,就進行登錄邏輯的處理,這裏,我們假設所有的登錄都是成功的,valid()
方法返回 true。
*服務端發送登錄響應
服務端處理登錄響應
ServerHandler.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(), loginResponsePacket);
ctx.channel().writeAndFlush(responseByteBuf);
這段邏輯仍然是在服務端邏輯處理器 ServerHandler
的 channelRead()
方法裏,我們構造一個登錄響應包 LoginResponsePacket
,然後在校驗成功和失敗的時候分別設置標誌位,接下來,調用編碼器把 Java 對象編碼成 ByteBuf
,調用 writeAndFlush()
寫到客戶端。
客戶端處理登錄響應
ClientHandler.java
客戶端接收服務端數據的處理邏輯也是在 ClientHandler
的 channelRead()
方法
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
進行解碼操作,如果類型是登錄響應數據包,我們這裏邏輯比較簡單,在控制檯打印出一條消息。
*登陸請求與響應類
@Data
public class LoginRequestPacket extends Packet {
private String userId;
private String username;
private String password;
@Override
public Byte getCommand() {
return LOGIN_REQUEST;
}
}
@Data
public class LoginResponsePacket extends Packet {
private boolean success;
private String reason;
@Override
public Byte getCommand() {
return LOGIN_RESPONSE;
}
}