Netty对于TCP长连接的实现
原来都是通过tcp,udp的短连接方式进行协议间的通信,所以当Client端发送请求过来后,接收和回执是一次性的。Netty会为每一次的请求建立一个Channel(管道),当这次请求结束后,就会销毁这个Channel。
但是TCP长连接状态下,Client发送请求到Server后,每个通道都会存在,直到其中一方退出。其实和Websocket类型,双方建立通信后,只要其中一方不断开,就可以持续保持通信发送接收数据。
代码实现
引入Netty依赖
compile group: 'io.netty', name: 'netty-all', version: '4.1.32.Final'
1.当双方建立连接后,Server端需要把通道信息保存
/**
* @author chen
* ,存放连接的客户端
*/
public class NettyConfig {
/**
* 存储每一个客户端接入进来时的channel对象
*/
private static Map<String, Channel> CHANNEL_MAP = new ConcurrentHashMap<>();
/**
* 客户端发送消息后,简历对应通道关系
*
* @param userId
* @param channel
*/
public static void addChannel(String channelId, Channel channel) {
CHANNEL_MAP.putIfAbsent(channelId, channel);
}
/**
* 通过设备id获取通道
*
* @param userId
* @return
*/
public static Channel getChannelByChannelId(String channelId) {
return CHANNEL_MAP.get(channelId);
}
/**
* 通过管道id获取设备
*
* @param channelId
* @return
*/
public static void deleteChannel(String channelId) {
CHANNEL_MAP.remove(channelId);
}
}
2.Serve,netty配置信息
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.ChannelPipeline;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.ServerSocketChannel;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
public class Server {
private int port;
private ServerSocketChannel serverSocketChannel;
public Server(int port){
this.port = port;
bind();
}
private void bind() {
Thread thread = new Thread(new Runnable() {
@Override
public void run() {
//服务端要建立两个group,一个负责接收客户端的连接,一个负责处理数据传输
//连接处理group
EventLoopGroup boss = new NioEventLoopGroup();
//事件处理group
EventLoopGroup worker = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
// 绑定处理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(
//自定义的处理器
new TcpKeepAliveChannelHandler());
}
});
//绑定端口,同步等待成功
ChannelFuture future;
try {
future = bootstrap.bind(port).sync();
if (future.isSuccess()) {
serverSocketChannel = (ServerSocketChannel) future.channel();
System.out.println("服务端启动成功,端口:"+port);
} else {
System.out.println("服务端启动失败!");
}
//等待服务监听端口关闭,就是由于这里会将线程阻塞,导致无法发送信息,所以我这里开了线程
future.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
}
finally {
//优雅地退出,释放线程池资源
boss.shutdownGracefully();
worker.shutdownGracefully();
}
}
});
thread.start();
}
public void sendMessage(Object msg){
if(serverSocketChannel != null){
serverSocketChannel.writeAndFlush(msg);
}
}
public static void main(String[] args) {
Server server = new Server(8088);
}
}
3.TcpKeepAliveChannelHandler 自定义通道处理器
@Slf4j
@ChannelHandler.Sharable
public class TcpKeepAliveChannelHandler extends ChannelInboundHandlerAdapter {
/**
* 客户端与服务端创建连接的时候调用
*/
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
//将连接的通道存储到Map中
System.out.println("客户端与服务端连接开始...");
NettyConfig.addChannel(ctx.channel().id().toString(), ctx.channel());
}
/**
* 客户端与服务端断开连接时调用
*/
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
System.out.println("客户端与服务端连接关闭...");
//删除map中的对应关系
NettyConfig.deleteChannel(ctx.channel().id().toString());
}
/**
* 工程出现异常的时候调用
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
/**
* 服务端接收了客户端发来的信息 对设备进行处理
*/
@Override
public void channelRead(ChannelHandlerContext ctx, Object info){
{
log.info("接收到了来自客户端的消息 -> {}" , info)
ByteBuf buf = (ByteBuf) info;
byte[] req = new byte[buf.readableBytes()];
buf.readBytes(req);
String body = new String(req, "UTF-8");
System.out.println("接收客户端数据:" + body);
ByteBuf pingMessage = Unpooled.buffer();
pingMessage.writeBytes(req);
//返回给客户端的消息
channelHandlerContext.writeAndFlush(pingMessage);
}
}
4.服务端主动给客户端发送数据
private void sendMessage(byte[] msg,String channelId) {
Channel channel = NettyConfig.getChannelByChannelId(channelId);
ByteBuf byteBuf = Unpooled.buffer().writeBytes(command);
channel.writeAndFlush(byteBuf);
}