1. netty介绍
netty官网:Netty: Home
Netty是一个基于Java NIO的网络编程框架,提供了一套高效的、事件驱动的异步网络通信机制。简化了网络应用程序的开发过程,提供了可靠的、高性能的网络传输。
netty的详细说明,网上资料很多,大家感兴趣自己学习哈。
2. 完成初始化netty
本过程只完成项目和netty的结合,定义好心跳检测handler,业务处理handler,服务端启动。
注意:从现在开始代码量将增多
2.1 启动Netty
实现spring提供的 CommandLineRunner接口,run方法中启动Netty服务端,考虑到后面会有存储Channel和serverId的关系以及节点信息等情况,我定义一个主信息类ServerInfo
2.2 ServerInfo -- 主信息类
@Slf4j
public class ServerInfo {
private ServerInfo() {}
private static NioEventLoopGroup bossGroup;
public static void setBossGroup(NioEventLoopGroup bg) {
bossGroup = bg;
}
public static NioEventLoopGroup getBossGroup() {
return bossGroup;
}
private static NioEventLoopGroup workerGroup;
private static Channel serverChannel;
private static Bootstrap connectOtherNodeBootStrap;
private static ServerBootstrap bootstrapForClient;
public static void setWorkerGroup(NioEventLoopGroup bg) {
workerGroup = bg;
}
public static NioEventLoopGroup getWorkerGroup() {
return workerGroup;
}
public static void setServerChannel(Channel ch) {
serverChannel = ch;
}
public static Channel getServerChannel() {
return serverChannel;
}
public static void setConnectOtherNodeBootStrap(Bootstrap bs) {
connectOtherNodeBootStrap = bs;
}
public static Bootstrap getConnectOtherNodeBootStrap() {
return connectOtherNodeBootStrap;
}
public static void setBootstrapForClient(ServerBootstrap sbs) {
bootstrapForClient = sbs;
}
public static ServerBootstrap getBootstrapForClient() {
return bootstrapForClient;
}
}
2.3 SpringInitRunner -- 启动netty的Runner
@Component
@Slf4j
public class SpringInitRunner implements CommandLineRunner {
@Autowired
private DttaskServerConfig dttaskServerConfig;
@PostConstruct
public void init() {
initServerBootStrap();
initConnectOtherNodeBootStrap();
}
private void initConnectOtherNodeBootStrap() {
ServerInfo.setConnectOtherNodeBootStrap(new Bootstrap());
ServerInfo.getConnectOtherNodeBootStrap().group(new NioEventLoopGroup(4))
.channel(NioSocketChannel.class)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline().addLast(
new CrawlerMessageDecoder(MESSAGE_MAX_SIZE, MESSAGE_LENGTH_FILED_OFFSET, MESSAGE_LENGTH_FILED_LENGTH));
socketChannel.pipeline().addLast(
new IdleStateHandler(dttaskServerConfig.getReadIdleSecondTime(),
dttaskServerConfig.getWriteIdleSecondTime(),
dttaskServerConfig.getAllIdleSecondTime()));
socketChannel.pipeline().addLast(new CrawlerMessageEncoder());
socketChannel.pipeline().addLast(
new ServerClientChannelHandler());
}
});
}
private void initServerBootStrap() {
ServerInfo.setBossGroup(new NioEventLoopGroup(4));
ServerInfo.setWorkerGroup(new NioEventLoopGroup(8));
ServerInfo.setBootstrapForClient(new ServerBootstrap());
ServerInfo.getBootstrapForClient().group(ServerInfo.getBossGroup(), ServerInfo.getWorkerGroup())
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) {
socketChannel.pipeline().addLast(
new CrawlerMessageDecoder(MESSAGE_MAX_SIZE, MESSAGE_LENGTH_FILED_OFFSET, MESSAGE_LENGTH_FILED_LENGTH));
socketChannel.pipeline().addLast(new CrawlerMessageEncoder());
IdleStateHandler idleStateHandler = new IdleStateHandler(dttaskServerConfig.getReadIdleSecondTime(), dttaskServerConfig.getWriteIdleSecondTime(), dttaskServerConfig.getAllIdleSecondTime());
socketChannel.pipeline().addLast(idleStateHandler);
socketChannel.pipeline().addLast(new HeartBeatServerHandler());
socketChannel.pipeline().addLast(
new ServerClientChannelHandler());
}
});
}
@Override
public void run(String... args) {
log.info("spring启动完成,接下来启动 netty");
try {
log.info("启动监听其它节点端请求的服务端...");
ServerInfo.setServerChannel(ServerInfo.getBootstrapForClient().bind(dttaskServerConfig.listenerPort()).sync().channel());
// 下面连接其它节点...
} catch (Exception e) {
log.error("启动 监听其它节点请求的服务端出现异常", e);
System.exit(-1);
}
log.info("netty 启动成功...");
}
@PreDestroy
public void shutdown() {
try {
ServerInfo.getServerChannel().close().sync();
} catch (InterruptedException e) {
log.error("crawler-server netty shutdown 出现异常", e);
Thread.currentThread().interrupt();
} finally {
ServerInfo.getWorkerGroup().shutdownGracefully();
ServerInfo.getBossGroup().shutdownGracefully();
}
}
}
2.4 ServerClientChannelHandler -- netty处理请求的handler
@Slf4j
public class ServerClientChannelHandler extends SimpleChannelInboundHandler<DttaskMessage> {
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("channelActive={}", ctx.channel().id());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, DttaskMessage message) throws Exception {
log.info("收到客户端的请求:{}", message);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
log.warn("channelInactive...");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.warn("exceptionCaught...", cause);
}
}
2.5 HeartBeatServerHandler -- 处理心跳的handler
@Slf4j
public class HeartBeatServerHandler extends ChannelInboundHandlerAdapter {
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.READER_IDLE) {
log.warn("读取空闲...");
} else if (event.state() == IdleState.WRITER_IDLE) {
log.warn("写入空闲...");
} else if (event.state() == IdleState.ALL_IDLE) {
log.warn("serverId={}与server通信读取或写入空闲...");
}
}
}
}
2.6 DttaskMessageDecoder -- 消息编码的handler
public class DttaskMessageDecoder extends LengthFieldBasedFrameDecoder {
public DttaskMessageDecoder(int maxFrameLength, int lengthFieldOffset, int lengthFieldLength) {
super(maxFrameLength, lengthFieldOffset, lengthFieldLength);
}
@Override
protected Object decode(ChannelHandlerContext ctx, ByteBuf in) throws Exception {
ByteBuf newIn = (ByteBuf) super.decode(ctx, in);
if (newIn == null) {
return null;
}
if (newIn.readableBytes() < MESSAGE_TOTAL_SIZE) {
return null;
}
int frameLength = newIn.readInt();
if (newIn.readableBytes() < frameLength) {
return null;
}
DttaskMessage dttaskMessage = new DttaskMessage();
byte type = newIn.readByte();
dttaskMessage.setType(type);
int infoLength = newIn.readInt();
byte[] infoBytes = new byte[infoLength];
newIn.readBytes(infoBytes);
dttaskMessage.setInfo(new String(infoBytes));
newIn.release();
return dttaskMessage;
}
}
2.7 DttaskMessageEncoder -- 消息解码的handler
public class DttaskMessageEncoder extends MessageToByteEncoder<DttaskMessage> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, DttaskMessage dttaskMessage, ByteBuf byteBuf) throws Exception {
int bodyLength = MESSAGE_TYPE_SIZE + MESSAGE_INFO_SIZE;
byte[] infoBytes = null;
if (dttaskMessage.getInfo() != null) {
infoBytes = dttaskMessage.getInfo().getBytes();
bodyLength += infoBytes.length;
}
byteBuf.writeInt(bodyLength);
byteBuf.writeByte(dttaskMessage.getType());
if (infoBytes != null) {
byteBuf.writeInt(infoBytes.length);
byteBuf.writeBytes(infoBytes);
} else {
byteBuf.writeInt(0x00);
}
}
}
2.8 DttaskMessage -- netty消息的封装类
目前定义了3种消息类型,COMMON_RESP、PING、PONG,后面会根据业务需要进行扩展
@Data
public class DttaskMessage {
public static final byte COMMON_RESP = 0X00;
public static final byte PING = 0X01;
public static final byte PONG = 0X02;
// 类型
private byte type;
// 消息实际信息
private String info;
public static DttaskMessage buildPingMessage(long serverId) {
DttaskMessage dttaskMessage = new DttaskMessage();
dttaskMessage.setType(PING);
dttaskMessage.setInfo(JSON.toJSONString(new PingMessage(serverId)));
return dttaskMessage;
}
public static DttaskMessage buildPongMessage(long serverId) {
DttaskMessage dttaskMessage = new DttaskMessage();
dttaskMessage.setType(PONG);
dttaskMessage.setInfo(JSON.toJSONString(new PongMessage(serverId)));
return dttaskMessage;
}
public static DttaskMessage buildCommonRespMessage(String message, boolean successFlag) {
DttaskMessage dttaskMessage = new DttaskMessage();
dttaskMessage.setType(COMMON_RESP);
dttaskMessage.setInfo(JSON.toJSONString(new CommonRespMessage(message, successFlag)));
return dttaskMessage;
}
}
2.9 每种消息
2.9.1 CommonRespMessage
@Data
@AllArgsConstructor
@NoArgsConstructor
public class CommonRespMessage {
private String message;
private Boolean successFlag;
}
2.9.2 PingMessage
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PingMessage {
private Long serverId;
}
2.9.3 PongMessage
@Data
@AllArgsConstructor
@NoArgsConstructor
public class PongMessage {
private Long serverId;
}
2.10 辅助类
考虑到后面要进行投票,每个节点会确定角色,程序中定义了ServerRole枚举类,每个节点的信息类NodeInfo,这里就不一一列出了,可以在源码处查看