Netty粘拆包

2019-02-28  本文已影响2人  诺之林

本文的示例代码参考NettySticky

目录

准备

curl -s "https://get.sdkman.io" | bash

sdk install gradle 4.6

更多参考SDKMAN!

mkdir NettySticky && cd NettySticky

gradle init --type java-application

gradle run
# Hello world.

记得添加.gitignore => "gi gradle >> .gitignore"

Netty

Startup

vim build.gradle
# compile 'io.netty:netty-all:4.1.25.Final'
mkdir -p src/main/java/server

mv src/main/java/App.java src/main/java/server/Server.java

vim src/main/java/server/Server.java
package server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class Server {

    private static final int PORT = 8888;

    public static void main(String[] args) {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap
                .group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {

                    }
                });

        serverBootstrap.bind(PORT).addListener(future -> {
            if (future.isSuccess()) {
                System.out.println("端口[" + PORT + "] 绑定成功!");
            } else {
                System.err.println("端口[" + PORT + "] 绑定失败!!!");
            }
        });
    }
}
sed -i "" "s/App/server.Server/g" build.gradle && gradle run
# 端口[8888] 绑定成功!

telnet 127.0.0.1 8888
# Connected to localhost.

Protocol

vim build.gradle
# compile 'com.alibaba:fastjson:1.2.55'
# compileOnly 'org.projectlombok:lombok:1.18.6'
mkdir -p src/main/java/protocol

vim src/main/java/protocol/Packet.java
package protocol;

import com.alibaba.fastjson.annotation.JSONField;
import lombok.Data;

@Data
public abstract class Packet {
    @JSONField(deserialize = false, serialize = false)
    private Byte version = 1;

    @JSONField(serialize = false)
    public abstract Byte getCommand();
}
vim src/main/java/protocol/PacketCodec.java
package protocol;

import com.alibaba.fastjson.JSON;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.ByteBufAllocator;
import protocol.command.Command;
import protocol.request.LoginRequestPacket;

import java.util.HashMap;
import java.util.Map;

public class PacketCodec {
    public static final PacketCodec INSTANCE = new PacketCodec();

    private static final int MAGIC_NUMBER = 0x12345678;
    private static final Map<Byte, Class<? extends Packet>> packetTypeMap;

    static {
        packetTypeMap = new HashMap<>();
        packetTypeMap.put(Command.LOGIN, LoginRequestPacket.class);
    }

    public ByteBuf encode(Packet packet) {
        byte[] bytes = JSON.toJSONBytes(packet);
        ByteBuf byteBuf = ByteBufAllocator.DEFAULT.ioBuffer();

        byteBuf.writeInt(MAGIC_NUMBER);
        byteBuf.writeByte(packet.getVersion());
        byteBuf.writeByte(packet.getCommand());
        byteBuf.writeInt(bytes.length);
        byteBuf.writeBytes(bytes);

        return byteBuf;
    }

    public Packet decode(ByteBuf byteBuf) {
        byteBuf.skipBytes(4);
        byteBuf.skipBytes(1);
        byte command = byteBuf.readByte();
        int length = byteBuf.readInt();
        byte[] bytes = new byte[length];
        byteBuf.readBytes(bytes);

        return JSON.parseObject(bytes, packetTypeMap.get(command));
    }
}
mkdir -p src/main/java/protocol/command

vim src/main/java/protocol/command/Command.java
package protocol.command;

public interface Command {
    Byte LOGIN = 1;
}
mkdir -p src/main/java/protocol/request

vim src/main/java/protocol/request/LoginRequestPacket.java
package protocol.request;

import lombok.Data;
import protocol.Packet;
import protocol.command.Command;

@Data
public class LoginRequestPacket extends Packet {
    private Integer userId;
    private String username;

    @Override
    public Byte getCommand() {
        return Command.LOGIN;
    }
}

Client

mkdir -p src/main/java/client

vim src/main/java/client/Client.java
package client;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class Client {
    private static final String HOST = "127.0.0.1";
    private static final int PORT = 8888;

    public static void main(String[] args) {
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        Bootstrap bootstrap = new Bootstrap();
        bootstrap
                .group(workerGroup)
                .channel(NioSocketChannel.class)
                .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, 5000)
                .option(ChannelOption.SO_KEEPALIVE, true)
                .handler(new ChannelInitializer<SocketChannel>() {
                    @Override
                    protected void initChannel(SocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ClientHandler());
                    }
                });

        bootstrap.connect(HOST, PORT).addListener(future -> {
            if (future.isSuccess()) {
                System.out.println("连接成功!");
            } else {
                System.err.println("连接失败!!!");
            }
        });
    }
}
vim src/main/java/client/ClientHandler.java
package client;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import protocol.PacketCodec;
import protocol.request.LoginRequestPacket;

public class ClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        LoginRequestPacket packetLogin = new LoginRequestPacket();
        packetLogin.setUserId(1);
        packetLogin.setUsername("xiaowang");

        ByteBuf byteBuf = PacketCodec.INSTANCE.encode(packetLogin);
        ctx.channel().writeAndFlush(byteBuf);
        System.out.println("登录请求");
    }
}

Server

vim src/main/java/server/Server.java
package server;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;

public class Server {

    private static final int PORT = 8888;

    public static void main(String[] args) {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap
                .group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new ServerHandler());
                    }
                });

        serverBootstrap.bind(PORT).addListener(future -> {
            if (future.isSuccess()) {
                System.out.println("端口[" + PORT + "] 绑定成功!");
            } else {
                System.err.println("端口[" + PORT + "] 绑定失败!!!");
            }
        });
    }
}
vim src/main/java/server/ServerHandler.java
package server;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import protocol.Packet;
import protocol.PacketCodec;
import protocol.request.LoginRequestPacket;

public class ServerHandler extends ChannelInboundHandlerAdapter {

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        ByteBuf byteBuf = (ByteBuf) msg;
        Packet packet = PacketCodec.INSTANCE.decode(byteBuf);

        if (packet instanceof LoginRequestPacket) {
            System.out.println(packet);
        } else {
            System.out.println("unknown packet");
        }
    }
}

测试

gradle run
# 端口[8888] 绑定成功!
# LoginRequestPacket(userId=1, username=xiaowang)
sed -i "" "s/server.Server/client.Client/g" build.gradle && gradle run
# 连接成功!
# 登录请求

粘包

vim src/main/java/client/ClientHandler.java
package client;

import io.netty.buffer.ByteBuf;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import protocol.PacketCodec;
import protocol.request.LoginRequestPacket;

public class ClientHandler extends ChannelInboundHandlerAdapter {
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        for (int i = 0; i < 10; i++) {
            LoginRequestPacket packetLogin = new LoginRequestPacket();
            packetLogin.setUserId(1);
            packetLogin.setUsername("xiaowang");

            ByteBuf byteBuf = PacketCodec.INSTANCE.encode(packetLogin);
            ctx.channel().writeAndFlush(byteBuf);
            System.out.println("登录请求");
        }
    }
}
vim build.gradle
# mainClassName = 'Server'

gradle run
# 端口[8888] 绑定成功!
# PacketLogin(userId=1, username=xiaowang)
# PacketLogin(userId=1, username=xiaowang)
vim build.gradle
# mainClassName = 'Client'

gradle run
# 连接成功!
# 登录请求
# 登录请求
# 登录请求
# 登录请求
# 登录请求
vim src/main/java/Server.java
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.LengthFieldBasedFrameDecoder;

public class Server {

    private static final int PORT = 8888;

    public static void main(String[] args) {
        NioEventLoopGroup bossGroup = new NioEventLoopGroup();
        NioEventLoopGroup workerGroup = new NioEventLoopGroup();

        ServerBootstrap serverBootstrap = new ServerBootstrap();
        serverBootstrap
                .group(bossGroup, workerGroup)
                .channel(NioServerSocketChannel.class)
                .option(ChannelOption.SO_BACKLOG, 1024)
                .childOption(ChannelOption.SO_KEEPALIVE, true)
                .childHandler(new ChannelInitializer<NioSocketChannel>() {
                    @Override
                    protected void initChannel(NioSocketChannel ch) throws Exception {
                        ch.pipeline().addLast(new LengthFieldBasedFrameDecoder(Integer.MAX_VALUE, 6, 4));
                        ch.pipeline().addLast(new ServerHandler());
                    }
                });

        serverBootstrap.bind(PORT).addListener(future -> {
            if (future.isSuccess()) {
                System.out.println("端口[" + PORT + "] 绑定成功!");
            } else {
                System.err.println("端口[" + PORT + "] 绑定失败!!!");
            }
        });
    }
}
vim build.gradle
# mainClassName = 'Server'

gradle run
# 端口[8888] 绑定成功!
# PacketLogin(userId=1, username=xiaowang)
# PacketLogin(userId=1, username=xiaowang)
# PacketLogin(userId=1, username=xiaowang)
# PacketLogin(userId=1, username=xiaowang)
# PacketLogin(userId=1, username=xiaowang)
vim build.gradle
# mainClassName = 'Client'

gradle run
# 连接成功!
# 登录请求
# 登录请求
# 登录请求
# 登录请求
# 登录请求

下一步

鉴权

心跳

上一篇下一篇

猜你喜欢

热点阅读