SpringBoot集成Netty
-
- 1、POM
- 2、服务端
-
- 2.1、netty服务端处理类
- 2.2、服务端初始化,客户端与服务器端连接一旦创建,这个类中方法就会被回调,设置出站编码器和入站解码器
- 2.3、Netty服务启动类
- 3、客户端
-
- 3.1、客户端处理器
- 3.2、客户端初始化,客户端与服务器端连接一旦创建,这个类中方法就会被回调,设置出站编码器和入站解码器,客户端服务端编解码要一致
- 3.3、客户端
1、POM
<!-- 添加SpringBoot父类依赖 -->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.1.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- json jar -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.22</version>
</dependency>
<!-- lombok的主要作用是通过一些注解,消除样板式代码ps:对bean的简化 -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<!-- Netty -->
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>4.1.32.Final</version>
</dependency>
</dependencies>
2、服务端
2.1、netty服务端处理类
import com.huayue.common.util.SpringBeanUtils;
import com.huayue.yhhjkj.service.YHHJKJService;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import java.net.InetSocketAddress;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.Executor;
@Slf4j
public class NettyServerHandler extends ChannelInboundHandlerAdapter {
private static final ConcurrentHashMap<ChannelId, ChannelHandlerContext> CHANNEL_MAP = new ConcurrentHashMap<>();
@Override
public void channelActive(ChannelHandlerContext ctx) {
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
int clientPort = insocket.getPort();
ChannelId channelId = ctx.channel().id();
System.out.println();
if (CHANNEL_MAP.containsKey(channelId)) {
log.info("客户端【" + channelId + "】是连接状态,连接通道数量: " + CHANNEL_MAP.size());
} else {
CHANNEL_MAP.put(channelId, ctx);
log.info("客户端【" + channelId + "】连接netty服务器[IP:" + clientIp + "--->PORT:" + clientPort + "]");
log.info("连接通道数量: " + CHANNEL_MAP.size());
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
InetSocketAddress insocket = (InetSocketAddress) ctx.channel().remoteAddress();
String clientIp = insocket.getAddress().getHostAddress();
ChannelId channelId = ctx.channel().id();
if (CHANNEL_MAP.containsKey(channelId)) {
CHANNEL_MAP.remove(channelId);
System.out.println();
log.info("客户端【" + channelId + "】退出netty服务器[IP:" + clientIp + "--->PORT:" + insocket.getPort() + "]");
log.info("连接通道数量: " + CHANNEL_MAP.size());
}
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
String response = "TCPServer response:" + msg;
System.out.println(response);
this.channelWrite(ctx.channel().id(), response);
}
public void channelWrite(ChannelId channelId, Object msg) throws Exception {
ChannelHandlerContext ctx = CHANNEL_MAP.get(channelId);
if (ctx == null) {
log.info("通道【" + channelId + "】不存在");
return;
}
if (msg == null || msg == "") {
log.info("服务端响应空的消息");
return;
}
ctx.write(msg);
ctx.flush();
}
@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
String socketString = ctx.channel().remoteAddress().toString();
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state() == IdleState.READER_IDLE) {
log.info("Client: " + socketString + " READER_IDLE 读超时");
ctx.disconnect();
} else if (event.state() == IdleState.WRITER_IDLE) {
log.info("Client: " + socketString + " WRITER_IDLE 写超时");
ctx.disconnect();
} else if (event.state() == IdleState.ALL_IDLE) {
log.info("Client: " + socketString + " ALL_IDLE 总超时");
ctx.disconnect();
}
}
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
System.out.println();
ctx.close();
log.info(ctx.channel().id() + " 发生了错误,此连接被关闭" + "此时连通数量: " + CHANNEL_MAP.size());
}
}
2.2、服务端初始化,客户端与服务器端连接一旦创建,这个类中方法就会被回调,设置出站编码器和入站解码器
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.DelimiterBasedFrameDecoder;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
public class NettyServerChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
ByteBuf buf= Unpooled.copiedBuffer("\n".getBytes());
channel.pipeline().addLast(new DelimiterBasedFrameDecoder(10240,true,buf));
channel.pipeline().addLast("decoder",new StringDecoder(CharsetUtil.UTF_8));
channel.pipeline().addLast("encoder",new StringEncoder(CharsetUtil.UTF_8));
channel.pipeline().addLast(new NettyServerHandler());
}
}
2.3、Netty服务启动类
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;
import java.net.InetSocketAddress;
@Slf4j
@Component
public class NettyServer {
public void start(InetSocketAddress address) {
EventLoopGroup bossGroup = new NioEventLoopGroup(1);
EventLoopGroup workerGroup = new NioEventLoopGroup(2);
try {
ServerBootstrap bootstrap = new ServerBootstrap()
.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.localAddress(address)
.childHandler(new NettyServerChannelInitializer())
.option(ChannelOption.SO_BACKLOG, 256)
.childOption(ChannelOption.SO_KEEPALIVE, true);
ChannelFuture future = bootstrap.bind(address).sync();
log.info("netty服务器开始监听端口:" + address.getPort());
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
}
3、客户端
3.1、客户端处理器
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelId;
import io.netty.channel.ChannelInboundHandlerAdapter;
import lombok.extern.slf4j.Slf4j;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
public class NettyClientHandler extends ChannelInboundHandlerAdapter {
private static final ConcurrentHashMap<ChannelId, ChannelHandlerContext> CLIENT_MAP = new ConcurrentHashMap<>();
@Override
public void channelActive(ChannelHandlerContext ctx) {
CLIENT_MAP.put(ctx.channel().id(), ctx);
log.info("ClientHandler Active");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) {
ctx.close();
log.info("服务端终止了服务");
}
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) {
log.info("回写数据:" + msg);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
log.info("服务端发生异常【" + cause.getMessage() + "】");
ctx.close();
}
public void channelWrite(ChannelId channelId, String msg) {
ChannelHandlerContext ctx = CLIENT_MAP.get(channelId);
if (ctx == null) {
log.info("通道【" + channelId + "】不存在");
return;
}
ctx.write(msg + " 时间:" + System.currentTimeMillis());
ctx.flush();
}
}
3.2、客户端初始化,客户端与服务器端连接一旦创建,这个类中方法就会被回调,设置出站编码器和入站解码器,客户端服务端编解码要一致
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import io.netty.util.CharsetUtil;
public class NettyClientChannelInitializer extends ChannelInitializer<SocketChannel> {
@Override
protected void initChannel(SocketChannel channel) throws Exception {
channel.pipeline().addLast("decoder",new StringDecoder(CharsetUtil.UTF_8));
channel.pipeline().addLast("encoder",new StringEncoder(CharsetUtil.UTF_8));
channel.pipeline().addLast(new NettyServerHandler());
}
}
3.3、客户端
import io.netty.bootstrap.Bootstrap;
import io.netty.channel.*;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioSocketChannel;
import io.netty.handler.codec.string.StringDecoder;
import io.netty.handler.codec.string.StringEncoder;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
@Slf4j
@Data
public class NettyClient implements Runnable {
static final String HOST = System.getProperty("host", DefaultConstants.SOCKET_IP);
static final int PORT = Integer.parseInt(System.getProperty("port", String.valueOf(DefaultConstants.SOCKET_PORT)));
static final int SIZE = Integer.parseInt(System.getProperty("size", "256"));
private String content;
public NettyClient(String content) {
this.content = content;
}
@Override
public void run() {
EventLoopGroup group = new NioEventLoopGroup();
try {
int num = 0;
boolean boo =true;
Bootstrap b = new Bootstrap();
b.group(group)
.channel(NioSocketChannel.class)
.option(ChannelOption.TCP_NODELAY, true)
.handler(new NettyClientChannelInitializer() {
@Override
public void initChannel(SocketChannel ch) throws Exception {
ChannelPipeline p = ch.pipeline();
p.addLast("decoder", new StringDecoder());
p.addLast("encoder", new StringEncoder());
p.addLast(new NettyClientHandler());
}
});
ChannelFuture future = b.connect(HOST, PORT).sync();
while (boo) {
num++;
String data="##0212QN=20201011184757000;ST=32;CN=2011;PW=123456;MN=2020001;Flag=0;CP=&&DataTime=20201011184757;011-Rtd=183.757,011-Flag=N;060-Rtd=0.805,060-Flag=N;003-Rtd=30,003-Flag=N;w10-Rtd=0,w10-Flag=N;y09-Rtd=2.31,y09-Flag=N&&\nfa\n";
future.channel().writeAndFlush(data);
if (num == 10) {
boo = false;
}
System.out.println(content + "-----------------------------" + num);
}
log.info(content + "-----------------------------" + num);
System.out.println(content + "-----------------------------" + num);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
group.shutdownGracefully();
}
}
}