一、创建springboot项目
在线生成springboot项目https://start.spring.io/
加入依赖:
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.36.Final</version>
</dependency>
二、服务端
1.服务端加上@Component注解,交由Spring管理实例。
@Component
public final class PortStart {
public static final Logger log = LoggerFactory.getLogger(PortStart.class);
/**
* 连接处理group
*/
private EventLoopGroup boss = new NioEventLoopGroup();
/**
* 事件处理group
*/
private EventLoopGroup worker = new NioEventLoopGroup();
private ServerBootstrap bootstrap = new ServerBootstrap();
/**
* key: port
* value: ChannelFuture
*/
private ConcurrentHashMap<Integer, ChannelFuture> portAndChannelFutures = new ConcurrentHashMap<>();
private List<Integer> portList = new ArrayList<>();
public PortStart(List<Integer> list) {
this.portList = list;
}
public void start() {
// 绑定处理group
bootstrap.group(boss, worker).channel(NioServerSocketChannel.class)
//保持连接数
.option(ChannelOption.SO_BACKLOG, 300)
//有数据立即发送
//.option(ChannelOption.TCP_NODELAY, true)
//保持连接
.childOption(ChannelOption.SO_KEEPALIVE, true)
//处理新连接
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel sc) throws Exception {
// 增加任务处理
ChannelPipeline p = sc.pipeline();
p.addLast(
//使用了netty自带的编码器和解码器
new ByteArrayEncoder(),
// new ByteArrayDecoder(),
//心跳检测,读超时,写超时,读写超时
//new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS),
//自定义的处理器
new NettyServerHandler()
);
}
});
if (portAndChannelFutures.isEmpty()) {
portAndChannelFutures = new ConcurrentHashMap<>(portList.size());
}
// 多端口绑定
for (int i = 0; i < portList.size(); i++) {
final int port = portList.get(i);
ChannelFuture channelFuture = bootstrap.bind(port);
portAndChannelFutures.put(port, channelFuture);
addLogIfSuccess(channelFuture, port);
}
}
public boolean startOnePort(int port) {
ChannelFuture channelFuture = bootstrap.bind(port);
portAndChannelFutures.put(port, channelFuture);
return addLogIfSuccess(channelFuture, port).isSuccess();
}
//关闭单个端口的NioServerSocketChannel
public void stopServerChannel(int port) {
portAndChannelFutures.get(port).channel().close();
}
public void stopAll() {
boss.shutdownGracefully();
worker.shutdownGracefully();
log.info("stop all port");
}
private ChannelFuture addLogIfSuccess(ChannelFuture channelFuture, int port) {
return channelFuture.addListener(new GenericFutureListener<Future<? super Void>>() {
@Override
public void operationComplete(Future<? super Void> future) {
if (future.isSuccess()) {
log.info("Started success,port: {}", port);
} else {
log.error("Started Failed,port: {}", port);
}
}
});
}
}
2.自定义socket命令下发方法,后面控制设备命令下发及端口控制
@Component
@Slf4j
public class NettyServer {
private PortStart portStart;
@Resource
private ResourceConfig config;
public void bind() {
portStart = new PortStart(readPropertiesFile());
portStart.start();
}
--自定义读取resources目录下properties端口文件
public List<Integer> readPropertiesFile() {
try {
Properties props = new Properties();
InputStream in = this.getClass().getResourceAsStream("/config/"+config.getProperty_file_name());
props.load(new InputStreamReader(in, "UTF-8"));
Iterator<Map.Entry<Object, Object>> iterator = props.entrySet().iterator();
List<Integer> list = new ArrayList<>();
while(iterator.hasNext()) {
Map.Entry<Object, Object> entry = (Map.Entry<Object, Object>) iterator.next();
String strValue = (String)entry.getValue();
list.add(Integer.valueOf(strValue));
}
return list;
} catch (Exception e) {
log.error("NettyServer的错误信息:"+e.getMessage());
return null;
}
}
public void stopAll() {
portStart.stopAll();
}
--开启指定端口
public boolean startOnePort(int port) {
return portStart.startOnePort(port);
}
--关闭运行中指定端口
public void stopOnePort(int port) {
portStart.stopServerChannel(port);
}
}
3.启动类,添加 @PostConstruct注解服务启动
@Component
public class NettyStart {
@Resource
private NettyServer nettyServer;
@PostConstruct
public void run() {
nettyServer.bind();
}
}
三、服务端业务处理handler
服务端的生命周期以及接收客户端的信息收发和处理。这里不建议使用阻塞的操作,容易影响netty的性能。
@ChannelHandler.Sharable
@Component
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
/**
* 客户端连接会触发
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
log.info("端口号:"+getPort(ctx)+"连接上设备");
}
/**
* 客户端发消息会触发
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
ByteBuf buf = (ByteBuf) msg;
byte[] data = new byte[buf.readableBytes()];
buf.readBytes(data);
log.info("接收到报文{}", CommonUtils.bytesToHexString(data));
-- 使用线程池,使设备报文解析传递
ExecutorService pool= SpringBeanUtils.getBean(TaskExecutePool.class).getFixedThreadPool();
-- 通过端口号去区分不同设备,不同解析
MessageConsumer consumer= SpringBeanUtils.getBean(ConsumerRoute.class).routeByPort(getPort(ctx));
pool.execute(consumer);
}
/**
* 发生异常触发
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
log.info("设备断开连接的端口号:"+getPort(ctx));
log.error(cause.getMessage()+"ip地址:"+ctx.channel().remoteAddress().toString());
ctx.close();
}
}
总结
**从整体来看,只不过是将Netty服务端交给Spring来管理启动和销毁的工作,在服务端,我们对端口加以控制,对运行时端口的管控,借以更好的控制socket设备,对设备进行解析 结果的处理及对设备的命令下发控制**