简述
Springboot项目中使用 Netty 作为服务端,接收并处理其他平台发送的 Json数据包,处理拆包、粘包及数据包中时间类型是 long 类型需转成 ***Date***的情况。
项目流程
项目结构(下载即可:传送门)
4.0.0
cn.angus.demo
externalDataConnector
1.0-SNAPSHOT
UTF-8
1.8
org.springframework.boot
spring-boot-starter-parent
1.5.6.RELEASE
org.springframework.boot
spring-boot-starter
1.5.4.RELEASE
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
org.jboss.netty
netty
3.2.10.Final
net.sf.json-lib
json-lib
2.4
jdk15
org.projectlombok
lombok
1.16.18
io.springfox
springfox-swagger2
2.7.0
io.springfox
springfox-swagger-ui
2.7.0
commons-codec
commons-codec
1.7
com.google.code.gson
gson
2.8.1
org.springframework.data
spring-data-mongodb
1.10.6.RELEASE
mysql
mysql-connector-java
5.1.21
org.springframework.boot
spring-boot-starter-data-jpa
1.5.6.RELEASE
org.apache.httpcomponents
fluent-hc
4.5.3
io.netty
netty-all
4.1.30.Final
org.springframework.boot
spring-boot-maven-plugin
cn.angus.demo.Application
src/main/resources
true
Listener
package cn.angus.demo.listener;
import cn.angus.demo.consts.Ports;
import cn.angus.demo.handler.VehicleGasSyncHandler;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.timeout.ReadTimeoutHandler;
import io.netty.util.concurrent.DefaultEventExecutorGroup;
import io.netty.util.concurrent.EventExecutorGroup;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import io.netty.handler.codec.json.JsonObjectDecoder;
import javax.annotation.PostConstruct;
@Component
@Slf4j
public class VehicleGasSyncListener {
@Autowired
private VehicleGasSyncHandler vehicleGasSyncHandler;
@PostConstruct
private void startNettyServerAsync(){
new Thread(this::startNettyServer).start();
}
private void startNettyServer(){
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
final EventExecutorGroup businessGroup = new DefaultEventExecutorGroup(10);
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 1024)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer() {
@Override
protected void initChannel(Channel channel) throws Exception {
// 设置连接超时时间(很重要)
channel.pipeline().addLast("readtime",new ReadTimeoutHandler(60));
// 解决粘包问题
channel.pipeline().addLast(new JsonObjectDecoder());
channel.pipeline().addLast(businessGroup, "executer", vehicleGasSyncHandler);
}
});
ChannelFuture f = b.bind(Ports.VEHICLE_GAS_PORT).sync();
f.channel().closeFuture().sync();
} catch(Exception e) {
log.error(e.getMessage(), e);
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
Handler
package cn.angus.demo.handler;
import cn.angus.demo.dao.SpotDao;
import cn.angus.demo.domain.Request;
import cn.angus.demo.domain.Spot;
import com.google.gson.Gson;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import java.nio.charset.Charset;
@Component
@ChannelHandler.Sharable
@Slf4j
public class VehicleGasSyncHandler extends ChannelInboundHandlerAdapter {
private final Gson gson;
@Autowired
private SpotDao spotDao;
private static final String SPOT = "spot";
@Autowired
public VehicleGasSyncHandler(Gson gson) {
this.gson = gson;
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg){
ByteBuf m = (ByteBuf) msg;
String rawMsg = m.toString(Charset.forName("utf-8"));
log.info("VehicleGasSyncHandler Receive message: " + rawMsg);
if (rawMsg.length() == 0 || rawMsg.length() -1 != rawMsg.lastIndexOf("}"))
return;
String response = persistAndResponse(rawMsg);
if (response != null) {
ctx.writeAndFlush(Unpooled.wrappedBuffer(response.getBytes()))
.addListener(ChannelFutureListener.FIRE_EXCEPTION_ON_FAILURE)
.addListener((ChannelFutureListener) channelFuture -> log.info("VehicleGasSyncHandler 成功发送响应{}", response));
m.release();
}
}
private String persistAndResponse(String rawMsg) {
try {
gson.fromJson(rawMsg, Spot.class);
} catch (Exception e) {
log.error(e.getMessage(), e);
}
// 省略返回结果。
return "";
}
}
GsonConfig.java
package cn.angus.demo.config;
import com.google.gson.*;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.Date;
@Configuration
public class GsonConfig {
@Bean
public Gson gson(){
return new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
// Date 类型适配器
.registerTypeAdapter(Date.class, (JsonDeserializer) (json, typeOfT, context) -> new Date(json.getAsJsonPrimitive().getAsLong()))
.registerTypeAdapter(Date.class, (JsonSerializer) (date, type, jsonSerializationContext) -> new JsonPrimitive(date.getTime()))
.create();
}
}
实体类(createTime:将long转成Date)
@Data
@Document(collection = "vehicle_spot")
@JsonIgnoreProperties(ignoreUnknown = true)
public class Spot {
@Id
private String id;
private String area;
private String address;
private String longitude;
private String latitude;
private Double slope;
private Date createTime;
private Boolean inUse;
private Integer fuelTypeId;
private String code;
}
Ports.java
public class Ports {
public static final int VEHICLE_GAS_PORT = 11111;
}
测试用例:
{
"_id" : "297e0587671b749801671b754b020000",
"area" : "京",
"address" : "北京",
"longitude" : "489",
"latitude" : "125",
"slope" : 5.0,
"create_time" :1544756993000,
"fuel_type_id" : 1,
"code" : "testCode"
}
mongo 入库:
项目下载: https://download.csdn.net/download/qq_35974759/10850042
下载后导入到Idea中,配置maven,修改application-dev中的数据库地址改为你的,启动项目,利用SocketTool模拟发送数据包。