本文的示例代码参考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
# 连接成功!
# 登录请求
# 登录请求
# 登录请求
# 登录请求
# 登录请求
下一步
鉴权
-
https://juejin.im/book/5b4bc28bf265da0f60130116/section/5b4db07fe51d451917170338 拒绝非本协议连接
-
https://juejin.im/book/5b4bc28bf265da0f60130116/section/5b4db0dc6fb9a04fdc367a81 实战:使用 channelHandler 的热插拔实现客户端身份校验
网友评论