实现CommandLineRunner接口异步启动netty服务
/**
* netty server async start
*
* @author liupeng
* @version 1.0
* @description: TODO
* @date 2023/8/25 10:37
*/
@Component
@Slf4j
@Order
public class NettyService implements CommandLineRunner {
@Resource
private NettyProperties nettyProperties;
@Override
public void run(String... args) {
Executors.newSingleThreadExecutor().execute(() -> {
try {
TimeUnit.MILLISECONDS.sleep(6666 - System.currentTimeMillis() % 1000);
} catch (InterruptedException e) {
log.error("netty service delay error");
}
log.info("netty service start");
// 完成处理连接
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
// 完成与客户端处理 目前就两个设备端
EventLoopGroup workerGroup = new NioEventLoopGroup(2);
try {
Integer nettyPort = nettyProperties.getPort();
ServerBootstrap serverBootstrap = new ServerBootstrap();
serverBootstrap.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
//服务器连接队列大小 使用默认的NetUtil.SOMAXCONN
.option(ChannelOption.SO_BACKLOG, NetUtil.SOMAXCONN)
// set SO_KEEPALIVE
.childOption(ChannelOption.SO_KEEPALIVE, Boolean.TRUE)
// 自定义通道处理
.childHandler(new CustomChannelInitializer(nettyProperties));
Channel channel = serverBootstrap.bind(nettyPort).sync().addListener((ChannelFutureListener) future -> {
if (future.isSuccess()) {
log.info("监听端口: {} 成功 ", nettyPort);
} else {
log.info("监听端口: {} 失败 ", nettyPort);
}
}).channel();
// 阻塞等待通道关闭完成
channel.closeFuture().sync();
} catch (Exception e) {
log.info("netty service exception : " + e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
log.info("netty service shutdown");
}
});
}
}
application.yml配置文件中配置netty关键信息:如netty服务器端口号、需要连接的设备SN
/**
* @author liupeng
* @version 1.0
* @description: TODO
* @date 2023/8/29 10:31
*/
@Data
@Configuration
@EnableConfigurationProperties
@ConfigurationProperties(prefix = "netty")
public class NettyProperties {
/**
* port
*/
private Integer port;
/**
* 重试次数
*/
private Integer retryNum;
/**
* 重试等待时长 秒
*/
private Integer retryWaitTime;
/**
*Video设备id
*/
private List<String> videoServerId;
/**
* VA设备id
*/
private List<String> vaServerId;
}
注册ServerBootstrap中的自定义 ChannelInitializer
/**
* 自定义 ChannelInitializer
*
* @author liupeng
* @version 1.0
* @description: TODO
* @date 2023/8/25 10:51
*/
public class CustomChannelInitializer extends ChannelInitializer {
NettyProperties nettyProperties;
public CustomChannelInitializer(NettyProperties nettyProperties) {
this.nettyProperties = nettyProperties;
}
@Override
protected void initChannel(Channel channel) throws Exception {
ChannelPipeline pipeline = channel.pipeline();
// Http消息编码解码
pipeline.addLast("http-codec", new HttpServerCodec());
// Http消息组装
pipeline.addLast("aggregator", new HttpObjectAggregator(65536));
// 以块的方式来写的处理器
pipeline.addLast("http-chunked", new ChunkedWriteHandler());
// todo 登录 认证 handler
pipeline.addLast("login", new LoginHandler(nettyProperties));
//编码解码
pipeline.addLast("codec", new CodecHandler());
//分发
pipeline.addLast("dispatcher", new DispatcherHandler());
}
}
处理HTTP、WebSocket请求的Channel
/**
* 处理第一次http请求
*
* @author liupeng
* @version 1.0
* @description: TODO
* @date 2023/8/25 11:14
*/
@Slf4j
public class LoginHandler extends SimpleChannelInboundHandler<Object> {
/**
* handshaker
*/
private WebSocketServerHandshaker handshaker;
private NettyProperties nettyProperties;
public LoginHandler(NettyProperties nettyProperties) {
this.nettyProperties = nettyProperties;
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
if (msg instanceof FullHttpRequest) {
// HTTP接入
handleHttpRequest(ctx, (FullHttpRequest) msg);
} else if (msg instanceof WebSocketFrame) {
// WebSocket接入
handleWebSocketFrame(ctx, (WebSocketFrame) msg);
}
}
@Override
public void handlerAdded(ChannelHandlerContext ctx) { // (2)
Channel incoming = ctx.channel();
log.info("Client: {} added", incoming.remoteAddress());
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) { // (3)
Channel channel = ctx.channel();
log.info("Client: {} removed", channel.remoteAddress());
String uuid = ChannelUtil.getInstance().getId(ctx.channel());
ChannelUtil.getInstance().unBindChannel(uuid, channel);
//关闭连接
handshaker.close(ctx.channel(), new CloseWebSocketFrame(WebSocketCloseStatus.NORMAL_CLOSURE));
ChannelFuture future = ctx.close();
future.addListener((ChannelFutureListener) f -> {
// 额外操作
log.info("handlerRemoved : handlerRemoved.close channelId: {}", uuid);
});
}
@Override
public void channelActive(ChannelHandlerContext ctx) { // (5)
Channel incoming = ctx.channel();
log.info("Client: {} active", incoming.remoteAddress());
// ctx.channel().writeAndFlush(new PongWebSocketFrame());
}
/**
* ChannelInactive触发场景
* 客户端发送close帧(FIN包)
* 客户端关闭进程(RST包)
* 服务端或客户端主动调用channel.close()
*
* @param ctx
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) { // (6)
Channel incoming = ctx.channel();
log.info("Client: {} inactive", incoming.remoteAddress());
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
log.error(cause.toString());
Channel channel = ctx.channel();
log.info("Client: {} removed, error: {}", channel.remoteAddress(), cause.getMessage());
String uuid = ChannelUtil.getInstance().getId(ctx.channel());
ChannelUtil.getInstance().unBindChannel(uuid, channel);
// 其他异常,关闭连接
handshaker.close(ctx.channel(), new CloseWebSocketFrame(WebSocketCloseStatus.NORMAL_CLOSURE));
ChannelFuture future = ctx.close();
future.addListener((ChannelFutureListener) f -> {
// 额外操作
log.info("exceptionCaught : ChannelHandlerContext.close channelId: {}", uuid);
});
}
/**
* 处理Http请求,完成WebSocket握手
* todo 第一次连接中做token校验
*
* @param ctx
* @param request
*/
private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
// 如果HTTP解码失败,返回HTTP异常
if (!request.decoderResult().isSuccess()) {//
sendHttpResponse(ctx, request, new DefaultFullHttpResponse(HTTP_1_1, BAD_REQUEST));
return;
}
// 子协议
String header = request.headers().get(SEC_WEB_SOCKET_PROTOCOL);
// todo 后期设备过多可查配置表
List<String> vaServerId = nettyProperties.getVaServerId();
List<String> videoServerId = nettyProperties.getVideoServerId();
vaServerId.addAll(videoServerId);
if (!StringUtils.hasLength(header) || !vaServerId.contains(header)) {
log.error("header 设备唯一码 不存在 :" + header);
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
return;
}
WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://" + request.headers().get(HOST) + "/ws"
, header, false, 65536);
HttpVersion protocolVersion = request.protocolVersion();
//HTTP/1.1
if (protocolVersion.minorVersion() == 1) {
wsFactory = new WebSocketServerHandshakerFactory("wss://" + request.headers().get(HOST) + "/ws"
, header, false, 65536);
}
// 调试
// WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws://" + request.headers().get(HOST) + "/ws"
// , null, false, 65536 * 10);
handshaker = wsFactory.newHandshaker(request);
if (handshaker == null) {
// 无法处理的websocket版本
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
} else {
// 向客户端发送websocket握手,完成握手
// todo token 验证
// 设备唯一码
ChannelUtil.getInstance().bindChannel(header, ctx.channel());
log.info(" channel 连接 :" + header);
handshaker.handshake(ctx.channel(), request);
}
}
/**
* Http返回
*
* @param ctx
* @param request
* @param response
*/
private static void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request, FullHttpResponse response) {
// 返回应答给客户端
if (response.status().code() != 200) {
ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);
response.content().writeBytes(buf);
buf.release();
//允许跨域访问 设置头部信息
response.headers().set(ACCESS_CONTROL_ALLOW_ORIGIN, "*");
response.headers().set(ACCESS_CONTROL_ALLOW_METHODS, "GET,POST,PUT,DELETE");
response.headers().set(ACCESS_CONTROL_ALLOW_CREDENTIALS, "true");
response.headers().set(ACCESS_CONTROL_ALLOW_HEADERS, "Origin, X-Requested-With, Content-Type, Accept");
HttpUtil.setContentLength(response, response.content().readableBytes());
}
// 如果是非Keep-Alive,关闭连接 保持Keep-Alive
ChannelFuture f = ctx.channel().writeAndFlush(response);
if (!HttpUtil.isKeepAlive(request) || response.status().code() != 200) {
f.addListener(ChannelFutureListener.CLOSE);
}
}
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
//当前管道id
String id = ChannelUtil.getInstance().getId(ctx.channel());
// log.info("useUUID {}", id);
if (frame instanceof CloseWebSocketFrame) {
//关闭管道
log.info("channelId: {} CloseWebSocketFrame", id);
ChannelUtil.getInstance().unBindChannel(id, ctx.channel());
handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
} else if (frame instanceof PingWebSocketFrame) {
//Ping消息
log.info("channelId: {} PingWebSocketFrame", id);
ctx.channel().writeAndFlush(new PongWebSocketFrame(frame.content().retain()));
} else if (frame instanceof TextWebSocketFrame) {
编码解码 Handler,可自定义数据格式
/**
* 编码解码 Handler
*
* @author liupeng
* @version 1.0
* @description: TODO
* @date 2023/8/25 10:53
*/
public class CodecHandler extends MessageToMessageCodec<TextWebSocketFrame, String> {
@Override
protected void decode(ChannelHandlerContext ctx, TextWebSocketFrame msg, List<Object> in) throws Exception {
String text = msg.text();
in.add(text);
}
@Override
protected void encode(ChannelHandlerContext ctx, String res, List<Object> out) throws Exception {
out.add(new TextWebSocketFrame(res));
}
}
多个ChannelInboundHandler的调度器:目前就一种数据格式的处理Handler,后续可按需添加。
/**
* 调度 handle
*
* @author liupeng
* @version 1.0
* @description: TODO
* @date 2023/8/25 11:02
*/
@Slf4j
public class DispatcherHandler extends SimpleChannelInboundHandler<String> {
/**
* map
*/
private static final Map<String, SimpleChannelInboundHandler> HANDLER_MAP = new HashMap<>();
public DispatcherHandler() {
HANDLER_MAP.put(LIVE_STREAMING_LIST, new LiveStreamProcessingHandler());
HANDLER_MAP.put(LIVE_STREAMING_STATUS, new LiveStreamProcessingHandler());
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
Request request;
try {
request = ChannelUtil.getInstance().verifyMessage(msg);
}catch (Exception e){
log.error(" 校验请求参数异常 {} " , e.getMessage());
ctx.channel().writeAndFlush("Data format exception :" + msg);
return;
}
SimpleChannelInboundHandler handler = HANDLER_MAP.get(request.getExecute());
if (handler == null) {
ctx.channel().writeAndFlush("execute command error");
}
handler.channelRead(ctx, msg);
}
}
从spring容器中获取具体的业务bean,上面的LiveStreamProcessingHandler继承AbstractLiveStreamProcess抽象类,各自的具体业务可以自定义实现。
/**
* @author liupeng
* @version 1.0
* @description: TODO
* @date 2023/8/25 14:12
*/
@Slf4j
public class LiveStreamProcessingHandler extends SimpleChannelInboundHandler<String> {
@Override
protected void channelRead0(ChannelHandlerContext ctx, String msg) throws Exception {
log.info("message {}", msg);
Channel channel = ctx.channel();
String id = ChannelUtil.getInstance().getId(channel);
if (StringUtils.isEmpty(id)) {
//todo 集群可通过消息队列组件进行转发监听
}
Request request = ChannelUtil.getInstance().verifyMessage(msg);
String execute = request.getExecute();
try {
if (StringUtils.isNotEmpty(execute)) {
Object bean = ApplicationContextRegister
.getApplicationContext().getBean(execute);
if (ObjectUtils.isNotEmpty(bean)) {
AbstractLiveStreamProcess service = (AbstractLiveStreamProcess) bean;
Request process = service.process(request, channel);
if (ObjectUtils.isNotEmpty(process)) {
log.info(" responseData : {}", request);
channel.writeAndFlush(ChannelUtil.getInstance().responseData(request));
}
return;
}
}
request.setParams("execute command error");
} catch (Exception e) {
log.error(" LiveStreamProcessingHandler 处理异常" + e);
request.setParams("handler 处理异常 " + e.getMessage());
}
channel.writeAndFlush(ChannelUtil.getInstance().responseData(request));
}
}