1、什么是netty
Netty 是一个基于NIO的客户、服务器端的编程框架,使用Netty 可以确保你快速和简单的开发出一个网络应用,例如实现了某种协议的客户、服务端应用。Netty相当于简化和流线化了网络应用的编程开发过程,例如:基于TCP和UDP的socket服务开发。
基于 I/O 复用模型
2、Springboot整合Netty
① 导入依赖
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.50.Final</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
② Application启动类时启动Netty
public class Application {
public static void main(String[] args) {
new SpringApplicationBuilder(Application.class)
.web(WebApplicationType.SERVLET)
.run(args);
NettyServer nettyServer = new NettyServer();
nettyServer.start();
}
}
③ 创建服务启动监听器NettyServer
/**
* 服务启动监听器
*/
@Slf4j
public class NettyServer {
@Value("${keystore.server.path}")
private String SERVER_PATH;
@Value("${keystore.server.password}")
private String SERVER_PASSWORD;
@Value("${keystore.ca-trust.path}")
private String CA_TRUST_PATH;
@Value("${keystore.ca-trust.password}")
private String CA_TRUST_PASSWORD;
/**
* 启动netty
*/
public void start() {
EventLoopGroup rootGroup = new NioEventLoopGroup();
EventLoopGroup workGroup = new NioEventLoopGroup();
try {
//创建密钥管理器工厂
KeyManagerFactory kmf = SSLUtil.getKeyManagerFactory("D:\\jm\\keystore\\xx.keystore", "xxx");
//创建信任管理器工厂
TrustManagerFactory tmf = SSLUtil.getTrustManagerFactory("D:\\jm\\keystore\\xx.keystore", "xxx");
SslContext sslCtx = SslContextBuilder.forServer(kmf).trustManager(tmf).build();
ServerBootstrap sb = new ServerBootstrap();
sb.group(rootGroup, workGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ServerChannelInitializer(sslCtx))
.handler(new LoggingHandler(LogLevel.INFO))
//保持连接数
.option(ChannelOption.SO_BACKLOG, 1024)
// 有数据立即发送
.childOption(ChannelOption.TCP_NODELAY, true)
// 保持连接
.childOption(ChannelOption.SO_KEEPALIVE, true);
//绑定长连接端口号
ChannelFuture serverChannel = sb.bind(8089).sync();
serverChannel.channel().closeFuture().sync();
}catch (Exception e) {
log.info("start: {}", "网络服务启动异常" + e.getMessage());
e.printStackTrace();
} finally {
rootGroup.shutdownGracefully();
workGroup.shutdownGracefully();
log.info("start: {}", "网络服务关闭");
}
}
}
④ 封装客户端信息NettyClient
/**
* 封装客户端的信息
*/
@Data
public class NettyClient {
/**
* 客户端与服务器的连接
*/
private SocketChannel channel;
/**
* ip地址
*/
private String clientIp;
public NettyClient(SocketChannel channel, String clientIp) {
this.channel = channel;
this.clientIp = clientIp;
}
}
⑤ Netty服务初始化
/**
* netty服务初始化器
*/
@Slf4j
public class ServerChannelInitializer extends ChannelInitializer<SocketChannel> {
private SslContext sslCtx;
public ServerChannelInitializer(SslContext sslCtx) {
this.sslCtx = sslCtx;
}
/**
* netty 初始化
*
* @param ch
* @throws Exception
*/
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline pipeline = ch.pipeline();
pipeline.addLast("proxyDecoder", new HAProxyMessageDecoder());
pipeline.addLast("ssl", sslCtx.newHandler(ch.alloc()));
pipeline.addLast("decoder", new KYPNetDecoder());//解码
pipeline.addLast("encoder", new KYPNetEncoder());//编码
pipeline.addLast("handler", new NettyServerHandler());//处理器
System.out.println("ChatClient:" + ch.remoteAddress() + "连接上");
}
}
⑥ 当有新的客户端连接的时候,用于保存客户端信息
public class NettyChannelMap {
/**
* 保存客户端的Map
*/
public static Map<String, NettyClient> map = new ConcurrentHashMap<>();
/**
* 添加客户端
*
* @param clientId 客户端地址
* @param client
*/
public static void add(String clientId, NettyClient client) {
map.put(clientId, client);
}
/**
* 获取客户端
*
* @param clientId 客户端地址
* @return
*/
public static NettyClient get(String clientId) {
return map.get(clientId);
}
/**
* 移除客户端
*
* @param socketChannel
*/
public static void remove(SocketChannel socketChannel) {
for (Map.Entry entry : map.entrySet()) {
if (((NettyClient) entry.getValue()).getChannel() == socketChannel) {
map.remove(entry.getKey());
}
}
}
}
⑦ Netty服务端处理器
/**
* netty服务端处理器
*/
@Slf4j
public class NettyServerHandler extends SimpleChannelInboundHandler {
KYPConnectVerify connectVerify = new KYPConnectVerify();
@Autowired
private ParkingMessageInfoService parkingMessageInfoService;
@Autowired
private WebSocketServer webSocketServer;
@Override
public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
super.handlerAdded(ctx);
}
@Override
public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
// TODO Auto-generated method stub
super.handlerRemoved(ctx);
}
@Override
protected void channelRead0(ChannelHandlerContext ctx, Object entity) {
String clientIp = null;
if (entity instanceof HAProxyMessage) {
HAProxyMessage proxyMessage = (HAProxyMessage) entity;
clientIp = proxyMessage.sourceAddress();// 原始IP
NettyClient client = new NettyClient((SocketChannel) ctx.channel(), clientIp);
NettyChannelMap.add(clientIp, client);
} else {
if (entity instanceof KYPResponseParam) {
KYPResponseParam kypResponseParam = (KYPResponseParam) entity;
log.info("KYPResponseParam: {}", JSONObject.toJSONString(kypResponseParam));
} else {
if (clientIp != null) {
handleRequest(ctx.channel(), (KYPRequestParam) entity, clientIp);
}
}
}
}
/**
* 接收数据
*
* @param ctx 上下文
* @param entity 接收到的数据
* @throws Exception
*/
// @Override
// protected void channelRead0(ChannelHandlerContext ctx, Object entity) throws Exception {
//
// }
/**
* 有新连接时触发
*
* @param ctx
*/
@Override
public void channelActive(final ChannelHandlerContext ctx) {
log.info("有新的客户端连接:{}", getClientIp(ctx.channel()));
// String clientIp = getClientIp(ctx.channel());
// NettyClient client = new NettyClient((SocketChannel) ctx.channel(), getClientIp(ctx.channel()));
// NettyChannelMap.add(clientIp, client);
}
/**
* 客户端断开时触发
*
* @param ctx
* @throws Exception
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
super.channelInactive(ctx);
Channel incoming = ctx.channel();
log.info("客户端断开连接:{}", incoming.remoteAddress());
NettyChannelMap.remove((SocketChannel) incoming);
}
/**
* 抛出netty执行异常
*
* @param ctx
* @param cause
* @throws Exception
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
super.exceptionCaught(ctx, cause);
log.info("抛出异常执行,包括客户端断开连接时,会抛出IO异常");
// 当出现异常就关闭连接
cause.printStackTrace();
ctx.close();
}
/**
* 获取客户端IP
*
* @param channel
* @return
*/
private String getClientIp(Channel channel) {
InetSocketAddress inetSocketAddress = (InetSocketAddress) channel.remoteAddress();
String clientIP = inetSocketAddress.getAddress().getHostAddress();
return clientIP;
}
/**
* 处理客户端请求
*
* @param channel
* @param requestParam
*/
private void handleRequest(Channel channel, KYPRequestParam requestParam, String clientIp) {
log.info("DevID: {}", requestParam.getDevid());
KYPResponseParam responseParam = null;
if (requestParam.getProcessnum() == ProcessNums.KEEPLONGLIVE) {//建立长连接(定时请求服务心跳)
responseParam = handleLongLiveConnect(requestParam);
} else {
responseParam = handle(requestParam.getProcessnum(), requestParam);
log.info("Processnum: {}", requestParam.getProcessnum());
}
//不同服务
if (responseParam != null) {//通过管道通信 回传
ChannelFuture channelFuture = channel.writeAndFlush(responseParam);
log.info("channelFuture: 回传状态 {}", channelFuture.isSuccess());
//todo
ParkingMessageInfo messageInfo = parkingMessageInfoService.lambdaQuery()
.eq(ParkingMessageInfo::getClientIp, clientIp)
.eq(ParkingMessageInfo::getStatus, "0")
.eq(ParkingMessageInfo::getProcessNum, responseParam.getProcessnum())
.one();
if(ObjectUtil.isNotEmpty(messageInfo)){
webSocketServer.sendOneMessage(messageInfo.getUserId(),JSONObject.toJSONString(responseParam));
parkingMessageInfoService.lambdaUpdate() .eq(ParkingMessageInfo::getClientIp, clientIp)
.eq(ParkingMessageInfo::getStatus, "0")
.eq(ParkingMessageInfo::getProcessNum, responseParam.getProcessnum())
.set(ParkingMessageInfo::getStatus,"1").update();
}
}
}
/**
* 处理请求
*
* @param process 业务代码
* @param requestParam 请求参数
* @return
*/
public KYPResponseParam handle(int process, KYPRequestParam requestParam) {
KYPResponseParam responseParam = new KYPResponseParam();
KYPResponseBody body = new KYPResponseBody();
if (!connectVerify.verifyRequest(requestParam)) {
responseParam.setProcessnum((short) process);
responseParam.setDevid(requestParam.getDevid());
responseParam.setRequestno(requestParam.getRequestno());
String sendTime = TextHelper.getDayTimeFormat().format(new Date());
responseParam.setSendtime(sendTime);
responseParam.setRetcode(-3);
responseParam.setRetmsg("签名验证失败");
body.setResultCode(-3);
body.setResultMsg("签名验证失败");
} else {
responseParam.setProcessnum((short) process);
responseParam.setDevid(requestParam.getDevid());
responseParam.setRequestno(requestParam.getRequestno());
String sendTime = TextHelper.getDayTimeFormat().format(new Date());
responseParam.setSendtime(sendTime);
responseParam.setRetcode(0);
responseParam.setRetmsg("成功");
body.setResultCode(0);
body.setResultMsg("成功");
}
connectVerify.sign(responseParam);//签名
if (process == ProcessNums.NETHEARTBEAT) {//心跳代码
body.put("systemtime", TextHelper.getDayTimeFormat().format(new Date()));
} else if ((process > 30 && process < 50) || (process >= 300 && process <= 399) || (process >= 400 && process <= 499)) {//业务
log.info("业务请求参数: {}", JSONObject.toJSONString(requestParam));
body.setResultCode(0);
body.setResultMsg("业务忽略");
} else {
body.setResultCode(0);
body.setResultMsg("业务忽略");
}
responseParam.setBody(body);
return responseParam;
}
/**
* 绑定长连接请求,回传数据
*
* @param requestParam
* @return
*/
public KYPResponseParam handleLongLiveConnect(KYPRequestParam requestParam) {
KYPResponseParam responseParam = new KYPResponseParam();
responseParam.setDevid(requestParam.getDevid());
responseParam.setProcessnum(ProcessNums.KEEPLONGLIVE);
responseParam.setRequestno(requestParam.getRequestno());
String sendTime = TextHelper.getDayTimeFormat().format(new Date());
responseParam.setSendtime(sendTime);
connectVerify.sign(responseParam);
JSONObject body = new JSONObject();
if (!connectVerify.verifyRequest(requestParam)) {
responseParam.setRetcode(DataProtocal.FAILCODEREADFAIL);
responseParam.setRetmsg("签名验证失败");
body.put("resultcode", -3);
body.put("resultmsg", "签名验证失败");
} else {
responseParam.setRetcode(0);
responseParam.setRetmsg("建立长连接成功");
}
return responseParam;
}
}