用Netty实现Socket长链接

一、Netty服务端

1、NettyServer
2、NettyServerChannelInitializer
3、NettyServerHandler

如何启动?

package web

import com.hbscTools.NettyServer
import grails.boot.GrailsApp
import grails.boot.config.GrailsAutoConfiguration

class Application extends GrailsAutoConfiguration {
    static void main(String[] args) {
        GrailsApp.run(Application, args)
        //初始化netty
        NettyServer nettyServer = new NettyServer();
        nettyServer.bind();
    }
}

如何使用?

//服务器推送消息至指定客户端
NettyServerHandler.sendMsg2Client(机器编号, 消息体)
				  .addListener({ future ->//异步监听发送结果
								
                  })
//服务器群发消息至所有在线客户端
//还没写,等有需要再写!
...
...
...

心跳包机制

@Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state().equals(IdleState.READER_IDLE)) {
                // 空闲40s之后触发 (心跳包丢失)
                Integer counter = online_channels_heart.get(ctx.channel().id().asLongText());
                if (counter >= 3) {
                    // 连续丢失3个心跳包 (断开连接)
                    ctx.channel().close().sync();
                    System.out.println("已与" + ctx.channel().remoteAddress() + "断开连接");
                } else {
                    counter++;
                    //重置心跳丢失次数
                    online_channels_heart.replace(ctx.channel().id().asLongText(), counter);
                    System.out.println("丢失了第 " + counter + " 个心跳包");
                }
            }
        }
    }

TCP粘包与分包处理

ChannelPipeline pipeline = ch.pipeline();
//解决TCP粘包拆包的问题,以特定的字符结尾($_)
pipeline.addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, Unpooled.copiedBuffer("$_".getBytes())));

相关代码

package com.hbscTools;

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;
/**
 * author : ZZL(周子林)
 * e-mail : [email protected]
 * date   : 2019/12/17 16:37
 * desc   :
 * version: 1.0
 */
public class NettyServer {
    private int port = 8888;
    private NettyServerChannelInitializer serverChannelInitializer = null;

    public void bind() throws Exception {
        //配置服务端的NIO线程组
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workerGroup = new NioEventLoopGroup();
        try {
            serverChannelInitializer = new NettyServerChannelInitializer();
            ServerBootstrap b = new ServerBootstrap();
            b.group(bossGroup, workerGroup)
                    .channel(NioServerSocketChannel.class)
                    //保持长连接
                    .childOption(ChannelOption.SO_KEEPALIVE, true)
                    .option(ChannelOption.SO_BACKLOG, 1024)
                    .childHandler(serverChannelInitializer);
            //绑定端口,同步等待成功
            ChannelFuture f = b.bind(port).sync();
            //等待服务器监听端口关闭
            f.channel().closeFuture().sync();
        } finally {
            //释放线程池资源
            bossGroup.shutdownGracefully();
            workerGroup.shutdownGracefully();
        }
    }
}
package com.hbscTools;


import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;
/**
 * author : ZZL(周子林)
 * e-mail : [email protected]
 * date   : 2019/12/17 16:37
 * desc   :
 * version: 1.0
 */
public class NettyServerChannelInitializer extends ChannelInitializer {

    private NettyServerHandler handler ;

    @Override
    protected void initChannel(SocketChannel socketChannel) throws Exception {
        ChannelPipeline pipeline = socketChannel.pipeline();
        //解决TCP粘包拆包的问题,以特定的字符结尾($_)
        pipeline.addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, Unpooled.copiedBuffer("$_".getBytes())));
        //字符串解码和编码
        pipeline.addLast("decoder", new StringDecoder());
        pipeline.addLast("encoder", new StringEncoder());
        pipeline.addLast(new IdleStateHandler(40,0,0, TimeUnit.SECONDS));
        //服务器的逻辑
        handler = new NettyServerHandler();
        pipeline.addLast("handler", handler);
    }
}
package com.hbscTools;

import io.netty.channel.*;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.atomic.AtomicReference;

/**
 * author : ZZL(周子林)
 * e-mail : [email protected]
 * date   : 2019/12/17 16:37
 * desc   :
 * version: 1.0
 */
public class NettyServerHandler extends SimpleChannelInboundHandler {

    //新建一个channelGroup,用于存放连接的channel
    public static HashMap online_channels = new HashMap();
    //记录每一个channel的心跳包丢失次数
    public HashMap online_channels_heart = new HashMap();

    public static ChannelFuture sendMsg2Client(String id, String msg) {
        ChannelHandlerContext ctx = NettyServerHandler.online_channels.get(id);
        if (null == ctx) {
            System.out.println("Channel is not exist");
            return null;
        }
        //返回ChannelFuture ,异步回调结果,若消息发送失败,则继续发送
        //若要保证数据的完整性,则需要做MD5数据校验
        ChannelFuture future = ctx.channel()
                .writeAndFlush(msg.concat("$_"));
        return future;
    }

    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        System.out.println("RemoteAddress : " + ctx.channel().remoteAddress().toString() + " add !");
        online_channels.put(ctx.channel().id().asLongText(), ctx);
        online_channels_heart.put(ctx.channel().id().asLongText(), 0);
    }

    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        System.out.println("RemoteAddress : " + ctx.channel().remoteAddress().toString() + " remove !");
        online_channels.remove(ctx.channel().id().asLongText());
        online_channels_heart.remove(ctx.channel().id().asLongText());
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, Object o) throws Exception {
        System.out.println("Client: " + channelHandlerContext.channel().id().asLongText() + " say : " + o.toString());
        System.out.println(new Date());
        //add channel(重连)
        online_channels.putIfAbsent(channelHandlerContext.channel().id().asLongText(), channelHandlerContext);
        //重置心跳丢失次数
        online_channels_heart.replace(channelHandlerContext.channel().id().asLongText(), 0);
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("RemoteAddress : " + ctx.channel().remoteAddress().toString() + " active !");
    }


    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state().equals(IdleState.READER_IDLE)) {
                // 空闲40s之后触发 (心跳包丢失)
                Integer counter = online_channels_heart.get(ctx.channel().id().asLongText());
                if (counter >= 3) {
                    // 连续丢失3个心跳包 (断开连接)
                    ctx.channel().close().sync();
                    System.out.println("已与" + ctx.channel().remoteAddress() + "断开连接");
                } else {
                    counter++;
                    //重置心跳丢失次数
                    online_channels_heart.replace(ctx.channel().id().asLongText(), counter);
                    System.out.println("丢失了第 " + counter + " 个心跳包");
                }
            }
        }
    }
}

二、Netty客户端

1、NettyClient
2、NettyClientChannelInitializer
3、NettyClientHandler

准备工作

在app包下面的build.gradle中加入
dependencies {
...
...
implementation group: 'io.netty', name: 'netty-all', version: '4.1.16.Final'
}

如何启动?

在MyApplication的onCreate()
@Override
public void onCreate() {
	super.onCreate();
	/* 初始化Netty */
	new NettyClient("192.168.13.58", 8888).connect();
}

如何使用?

消息读取
@Override
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		System.out.println("Server say : " + msg.toString());
		//在此获取服务端消息,处理业务逻辑
		//EventBus事件传递,牵涉到EventBus的使用,建议先了解一下EventBus相关的原理以及用法
		EventBus.getDefault().post(msg)
}

心跳包发送

@Override
public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
				throws Exception {
		if (evt instanceof IdleStateEvent) {
				IdleStateEvent event = (IdleStateEvent) evt;
				if (event.state().equals(IdleState.READER_IDLE)) {
						System.out.println("READER_IDLE");
				} else if (event.state().equals(IdleState.WRITER_IDLE)) {
						/**发送心跳,保持长连接*/
						String s = "ping$_";
						ctx.channel().writeAndFlush(s);
						System.out.println("心跳发送成功!");
				} else if (event.state().equals(IdleState.ALL_IDLE)) {
						System.out.println("ALL_IDLE");
				}
		}
		super.userEventTriggered(ctx, evt);
}

客户端相关代码

package com.hbsc.automachine000.netty;

import java.util.concurrent.TimeUnit;

import io.netty.bootstrap.Bootstrap;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoop;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioSocketChannel;

/**
 * author : ZZL(周子林)
 * e-mail : [email protected]
 * date   : 2019/12/17 16:37
 * desc   :
 * version: 1.0
 */
public class NettyClient {

    private String host;
    private Integer port;
    private static Channel channel;

    public NettyClient(String host, Integer port) {
        this.host = host;
        this.port = port;
    }

    public void connect() {
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            Bootstrap b = new Bootstrap();
            b.group(group)
                    .option(ChannelOption.SO_KEEPALIVE, true)
                    .channel(NioSocketChannel.class)
                    .handler(new ClientChannelInitializer(host, port));

            ChannelFuture f = b.connect(host, port);

            //断线重连
            f.addListener(new ChannelFutureListener() {
                @Override
                public void operationComplete(ChannelFuture channelFuture) throws Exception {
                    if (!channelFuture.isSuccess()) {
                        final EventLoop loop = channelFuture.channel().eventLoop();
                        loop.schedule(() -> {
                            System.err.println("服务端链接不上,开始重连操作...");
                            connect();
                        }, 1L, TimeUnit.SECONDS);
                    } else {
                        channel = channelFuture.channel();
                        System.err.println("服务端链接成功...");
                    }
                }
            });

        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

package com.hbsc.automachine000.netty;

import java.util.concurrent.TimeUnit;

import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelPipeline;
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.handler.timeout.IdleStateHandler;

/**
 * author : ZZL(周子林)
 * e-mail : [email protected]
 * date   : 2019/12/17 16:40
 * desc   :
 * version: 1.0
 */
public class ClientChannelInitializer extends ChannelInitializer {

    private String host;
    private Integer port;

    public ClientChannelInitializer(String host, Integer port) {
        this.host = host;
        this.port = port;
    }

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        //解决TCP粘包拆包的问题,以特定的字符结尾($_)
        pipeline.addLast(new DelimiterBasedFrameDecoder(Integer.MAX_VALUE, Unpooled.copiedBuffer("$_".getBytes())));
        //字符串解码和编码
        pipeline.addLast("decoder", new StringDecoder());
        pipeline.addLast("encoder", new StringEncoder());
        //心跳检测
        pipeline.addLast(new IdleStateHandler(0, 30, 0, TimeUnit.SECONDS));
        //客户端的逻辑
        pipeline.addLast("handler", new NettyClientHandler(host, port));
    }
}

package com.hbsc.automachine000.netty;

import java.util.concurrent.TimeUnit;

import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.EventLoop;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

/**
 * author : ZZL(周子林)
 * e-mail : [email protected]
 * date   : 2019/12/17 16:43
 * desc   :
 * version: 1.0
 */
public class NettyClientHandler extends SimpleChannelInboundHandler {

    private String host;
    private Integer port;
    private NettyClient nettyClient;


    public NettyClientHandler(String host, Integer port) {
        this.host = host;
        this.port = port;
        nettyClient = new NettyClient(host,port);
    }

    //这个方法接收不到数据
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception { }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        System.out.println("Server say : " + msg.toString());
    }

    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("通道已连接!!");
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("断线了。。。。。。");
        //使用过程中断线重连
        final EventLoop eventLoop = ctx.channel().eventLoop();
        eventLoop.schedule(() -> {
            nettyClient.connect();
        }, 1, TimeUnit.SECONDS);
        ctx.fireChannelInactive();
    }

    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
            throws Exception {
        if (evt instanceof IdleStateEvent) {
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state().equals(IdleState.READER_IDLE)) {
                System.out.println("READER_IDLE");
            } else if (event.state().equals(IdleState.WRITER_IDLE)) {
                /**发送心跳,保持长连接*/
                String s = "ping$_";
                ctx.channel().writeAndFlush(s);
                System.out.println("心跳发送成功!");
            } else if (event.state().equals(IdleState.ALL_IDLE)) {
                System.out.println("ALL_IDLE");
            }
        }
        super.userEventTriggered(ctx, evt);
    }

}

你可能感兴趣的:(android,java,后端)