客户端与服务端的交互流程
大致流程如下:
- 客户端构建登录用请求对象;
- 客户端编码成ByteBuf;
- 客户端写出数据(writeAndFlush());
- 服务端收到ByteBuf;
- 服务端解码成登录请求;
- 服务端登录校验;
- 服务端构建登录响应对象;
- 服务端编码成ByteBuf;
- 服务端写出数据(writeAndFlush());
- 客户端收到ByteBuf;
- 客户端解码成登录响应;
- 客户端处理响应结果;
客户端发送登录请求
修改后的ParseUtil.java
/**
* @program: learnnetty
* @description: 数据包的转换
* @create: 2020-05-06 11:09
**/
public class ParseFrame {
private static final int HEAD_FLAG = 0x990718;
private static ParseFrame parseFrame = null;
private ParseFrame() {
}
public static ParseFrame getInstance() {
if (parseFrame == null){
parseFrame = new ParseFrame();
}
return parseFrame;
}
/**
* 数据包的序列化
* @param frame 对象
* @return 完成序列化
*/
public ByteBuf encode(ByteBufAllocator bba, BaseFrame frame){
//此处使用IO读写相关的内存,直接内存不受JVM管理,写到IO缓冲区效率更高
ByteBuf buf = bba.DEFAULT.ioBuffer();
byte[] data = Serializer.DEFAULT.serialize(frame);
//组装报文
buf.writeInt(HEAD_FLAG);
buf.writeByte(Serializer.DEFAULT.gerSerializerAlgorithm());
buf.writeByte(frame.getCommand());
buf.writeInt(data.length);
buf.writeBytes(data);
return buf;
}
/**
* 逆序列化
* @param byteBuf 数据
* @return 对象
*/
public BaseFrame decode(ByteBuf byteBuf){
//跳过头部标志位
byteBuf.skipBytes(4);
//获取序列化算法
byte serializerAlgorithm = byteBuf.readByte();
//获取指令
byte command = byteBuf.readByte();
//获取数据长度
int length = byteBuf.readInt();
//获取数据
byte[] data = new byte[length];
byteBuf.readBytes(data);
Class<? extends BaseFrame> requestType = RequestUtil.getRequestType(command);
Serializer serializer = SerializerUtil.getSerializer(serializerAlgorithm);
if (requestType != null && serializer != null){
return serializer.deserialize(requestType, data);
}
return null;
}
}
修改后的Client.java
/**
* @program: learnnetty
* @description: 客户端
* @create: 2020-05-06 15:39
**/
public class Client {
private static final int MAX_RETRY = 5;
public static void main(String[] args) {
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap
.group(workerGroup)
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<NioSocketChannel>() {
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel
.pipeline()
.addLast(new ClientHandler());
}
});
connect(bootstrap, "127.0.0.1", 8080, MAX_RETRY);
}
private static void connect(final Bootstrap bootstrap, final String host, final int port, final int retry){
bootstrap.connect(host, port).addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
if (future.isSuccess()){
System.out.println("客户端连接成功");
}else if (retry == 0){
System.out.println("客户端连接失败次数过多,放弃连接");
}else {
System.out.println("客户端连接失败,尝试重连");
int order = MAX_RETRY - retry + 1;
int delay = 1 << order;
System.out.println(new Date() + " 连接失败,第" + order + "次连接");
bootstrap.config().group().schedule(new Runnable() {
@Override
public void run() {
connect(bootstrap, host, port, retry - 1);
}
}, delay, TimeUnit.SECONDS);
}
}
});
}
}
修改后的ClientHandler.java
/**
* @program: learnnetty
* @description: 客户端业务处理
* @create: 2020-05-06 15:40
**/
public class ClientHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(new Date() + ",客户端开始登录");
LoginRequestFrame loginFrame = new LoginRequestFrame();
loginFrame.setUserId(UUID.randomUUID().toString());
loginFrame.setUserName("zcd");
loginFrame.setPassword("zzz");
ByteBuf buf = ParseFrame.getInstance().encode(ctx.alloc(), loginFrame);
ctx.channel().writeAndFlush(buf);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf byteBuf = (ByteBuf) msg;
BaseFrame frame = ParseFrame.getInstance().decode(byteBuf);
if (frame instanceof LoginResponseFrame){
LoginResponseFrame responseFrame = (LoginResponseFrame) frame;
if (responseFrame.isSuccess()){
System.out.println("客户端登录成功");
}else {
System.out.println("客户端登录失败,原因:" + responseFrame.getMsg());
}
}
}
}
修改后的Server.java
/**
* @program: learnnetty
* @description: 服务端
* @create: 2020-05-06 17:18
**/
public class Server {
public static void main(String[] args) {
NioEventLoopGroup workerGroup = new NioEventLoopGroup();
NioEventLoopGroup bossGroup = new NioEventLoopGroup();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<NioSocketChannel>() {
@Override
protected void initChannel(NioSocketChannel nioSocketChannel) throws Exception {
nioSocketChannel
.pipeline()
.addLast(new ServerHandler());
}
});
bind(serverBootstrap, 8080);
}
private static void bind(final ServerBootstrap serverBootstrap, final int port){
serverBootstrap.bind(port).addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) throws Exception {
if (future.isSuccess()){
System.out.println("端口[" + port + "]绑定成功");
}else {
System.out.println("端口[" + port + "]绑定失败,尝试绑定[" + (port + 1) + "]端口");
bind(serverBootstrap, port + 1);
}
}
});
}
}
修改后的ServerHandler.java
/**
* @program: learnnetty
* @description: 服务端处理
* @create: 2020-05-06 17:19
**/
public class ServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
super.channelActive(ctx);
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println(new Date() + ",服务端开始处理登录请求");
ByteBuf buf = (ByteBuf)msg;
BaseFrame baseFrame = ParseFrame.getInstance().decode(buf);
if (baseFrame instanceof LoginRequestFrame){
LoginRequestFrame requestFrame = (LoginRequestFrame)baseFrame;
LoginResponseFrame responseFrame = new LoginResponseFrame();
responseFrame.setUserId(requestFrame.getUserId());
responseFrame.setUserName(requestFrame.getUserName());
if(requestFrame.getUserName().equals("zcd") && requestFrame.getPassword().equals("zzz")){
responseFrame.setSuccess(true);
responseFrame.setMsg("登录成功");
System.out.println(new Date() + ", zcd客户端登录成功");
}else {
responseFrame.setSuccess(false);
responseFrame.setMsg("密码或用户名错误");
System.out.println(new Date() + ", zcd客户端登录失败");
}
ByteBuf respByteBuf = ParseFrame.getInstance().encode(ctx.alloc(), responseFrame);
ctx.channel().writeAndFlush(respByteBuf);
}
}
}
客户端输出:
客户端连接成功
Wed May 06 17:34:25 CST 2020,客户端开始登录
客户端登录成功
服务端输出:
端口[8080]绑定成功
Wed May 06 17:34:26 CST 2020,服务端开始处理登录请求
Wed May 06 17:34:26 CST 2020, zcd客户端登录成功