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);
    }