netty实现tcp长连接和心跳检测

分享一下我老师大神的人工智能教程!零基础,通俗易懂!http://blog.csdn.net/jiangjunshow

也欢迎大家转载本篇文章。分享知识,造福人民,实现我们中华民族伟大复兴!

               

       通过netty实现服务端与客户端的长连接通讯,及心跳检测。

       基本思路:netty服务端通过一个Map保存所有连接上来的客户端SocketChannel,客户端的Id作为Map的key。每次服务器端如果要向某个客户端发送消息,只需根据ClientId取出对应的SocketChannel,往里面写入message即可。心跳检测通过IdleEvent事件,定时向服务端放送Ping消息,检测SocketChannel是否终断。

        环境JDK1.8 和netty5

        以下是具体的代码实现和介绍:

1公共的Share部分(主要包含消息协议类型的定义)

设计消息类型:

public enum  MsgType {    PING,ASK,REPLY,LOGIN}

 Message基类:

 

//必须实现序列,serialVersionUID 一定要有,否者在netty消息序列化反序列化会有问题,接收不到消息!!!public abstract class BaseMsg  implements Serializable {    private static final long serialVersionUID = 1L;    private MsgType type;    //必须唯一,否者会出现channel调用混乱    private String clientId;     //初始化客户端id    public BaseMsg() {        this.clientId = Constants.getClientId();    }     public String getClientId() {        return clientId;    }     public void setClientId(String clientId) {        this.clientId = clientId;    }     public MsgType getType() {        return type;    }     public void setType(MsgType type) {        this.type = type;    }}

 常量设置:

 

public class Constants {    private static String clientId;    public static String getClientId() {        return clientId;    }    public static void setClientId(String clientId) {        Constants.clientId = clientId;    }}

 登录类型消息:

 

public class LoginMsg extends BaseMsg {    private String userName;    private String password;    public LoginMsg() {        super();        setType(MsgType.LOGIN);    }     public String getUserName() {        return userName;    }     public void setUserName(String userName) {        this.userName = userName;    }     public String getPassword() {        return password;    }     public void setPassword(String password) {        this.password = password;    }}

 心跳检测Ping类型消息:

public class PingMsg extends BaseMsg {    public PingMsg() {        super();        setType(MsgType.PING);    }}

 请求类型消息:

public class AskMsg extends BaseMsg {    public AskMsg() {        super();        setType(MsgType.ASK);    }    private AskParams params;     public AskParams getParams() {        return params;    }     public void setParams(AskParams params) {        this.params = params;    }}//请求类型参数//必须实现序列化接口public class AskParams implements Serializable {    private static final long serialVersionUID = 1L;    private String auth;     public String getAuth() {        return auth;    }     public void setAuth(String auth) {        this.auth = auth;    }}

 响应类型消息:

public class ReplyMsg extends BaseMsg {    public ReplyMsg() {        super();        setType(MsgType.REPLY);    }    private ReplyBody body;     public ReplyBody getBody() {        return body;    }     public void setBody(ReplyBody body) {        this.body = body;    }}//相应类型body对像public class ReplyBody implements Serializable {    private static final long serialVersionUID = 1L;}public class ReplyClientBody extends ReplyBody {    private String clientInfo;     public ReplyClientBody(String clientInfo) {        this.clientInfo = clientInfo;    }     public String getClientInfo() {        return clientInfo;    }     public void setClientInfo(String clientInfo) {        this.clientInfo = clientInfo;    }}public class ReplyServerBody extends ReplyBody {    private String serverInfo;    public ReplyServerBody(String serverInfo) {        this.serverInfo = serverInfo;    }    public String getServerInfo() {        return serverInfo;    }    public void setServerInfo(String serverInfo) {        this.serverInfo = serverInfo;    }}

 2 Server端:主要包含对SocketChannel引用的Map,ChannelHandler的实现和Bootstrap.

Map:

public class NettyChannelMap {    private static Map<String,SocketChannel> map=new ConcurrentHashMap<String, SocketChannel>();    public static void add(String clientId,SocketChannel socketChannel){        map.put(clientId,socketChannel);    }    public static Channel get(String clientId){       return map.get(clientId);    }    public static void remove(SocketChannel socketChannel){        for (Map.Entry entry:map.entrySet()){            if (entry.getValue()==socketChannel){                map.remove(entry.getKey());            }        }    } }

 Handler

public class NettyServerHandler extends SimpleChannelInboundHandler<BaseMsg> {    @Override    public void channelInactive(ChannelHandlerContext ctx) throws Exception {        //channel失效,从Map中移除        NettyChannelMap.remove((SocketChannel)ctx.channel());    }    @Override    protected void messageReceived(ChannelHandlerContext channelHandlerContext, BaseMsg baseMsg) throws Exception {         if(MsgType.LOGIN.equals(baseMsg.getType())){            LoginMsg loginMsg=(LoginMsg)baseMsg;            if("robin".equals(loginMsg.getUserName())&&"yao".equals(loginMsg.getPassword())){                //登录成功,把channel存到服务端的map中                NettyChannelMap.add(loginMsg.getClientId(),(SocketChannel)channelHandlerContext.channel());                System.out.println("client"+loginMsg.getClientId()+" 登录成功");            }        }else{            if(NettyChannelMap.get(baseMsg.getClientId())==null){                    //说明未登录,或者连接断了,服务器向客户端发起登录请求,让客户端重新登录                    LoginMsg loginMsg=new LoginMsg();                    channelHandlerContext.channel().writeAndFlush(loginMsg);            }        }        switch (baseMsg.getType()){            case PING:{                PingMsg pingMsg=(PingMsg)baseMsg;                PingMsg replyPing=new PingMsg();                NettyChannelMap.get(pingMsg.getClientId()).writeAndFlush(replyPing);            }break;            case ASK:{                //收到客户端的请求                AskMsg askMsg=(AskMsg)baseMsg;                if("authToken".equals(askMsg.getParams().getAuth())){                    ReplyServerBody replyBody=new ReplyServerBody("server info $$$$ !!!");                    ReplyMsg replyMsg=new ReplyMsg();                    replyMsg.setBody(replyBody);                    NettyChannelMap.get(askMsg.getClientId()).writeAndFlush(replyMsg);                }            }break;            case REPLY:{                //收到客户端回复                ReplyMsg replyMsg=(ReplyMsg)baseMsg;                ReplyClientBody clientBody=(ReplyClientBody)replyMsg.getBody();                System.out.println("receive client msg: "+clientBody.getClientInfo());            }break;            default:break;        }        ReferenceCountUtil.release(baseMsg);    }}

 ServerBootstrap:

public class NettyServerBootstrap {    private int port;    private SocketChannel socketChannel;    public NettyServerBootstrap(int port) throws InterruptedException {        this.port = port;        bind();    }     private void bind() throws InterruptedException {        EventLoopGroup boss=new NioEventLoopGroup();        EventLoopGroup worker=new NioEventLoopGroup();        ServerBootstrap bootstrap=new ServerBootstrap();        bootstrap.group(boss,worker);        bootstrap.channel(NioServerSocketChannel.class);        bootstrap.option(ChannelOption.SO_BACKLOG, 128);        //通过NoDelay禁用Nagle,使消息立即发出去,不用等待到一定的数据量才发出去        bootstrap.option(ChannelOption.TCP_NODELAY, true);        //保持长连接状态        bootstrap.childOption(ChannelOption.SO_KEEPALIVE, true);        bootstrap.childHandler(new ChannelInitializer<SocketChannel>() {            @Override            protected void initChannel(SocketChannel socketChannel) throws Exception {                ChannelPipeline p = socketChannel.pipeline();                p.addLast(new ObjectEncoder());                p.addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));                p.addLast(new NettyServerHandler());            }        });        ChannelFuture f= bootstrap.bind(port).sync();        if(f.isSuccess()){            System.out.println("server start---------------");        }    }    public static void main(String []args) throws InterruptedException {        NettyServerBootstrap bootstrap=new NettyServerBootstrap(9999);        while (true){            SocketChannel channel=(SocketChannel)NettyChannelMap.get("001");            if(channel!=null){                AskMsg askMsg=new AskMsg();                channel.writeAndFlush(askMsg);            }            TimeUnit.SECONDS.sleep(5);        }    }}

 3 Client端:包含发起登录,发送心跳,及对应消息处理

handler

public class NettyClientHandler extends SimpleChannelInboundHandler<BaseMsg> {    //利用写空闲发送心跳检测消息    @Override    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {        if (evt instanceof IdleStateEvent) {            IdleStateEvent e = (IdleStateEvent) evt;            switch (e.state()) {                case WRITER_IDLE:                    PingMsg pingMsg=new PingMsg();                    ctx.writeAndFlush(pingMsg);                    System.out.println("send ping to server----------");                    break;                default:                    break;            }        }    }    @Override    protected void messageReceived(ChannelHandlerContext channelHandlerContext, BaseMsg baseMsg) throws Exception {        MsgType msgType=baseMsg.getType();        switch (msgType){            case LOGIN:{                //向服务器发起登录                LoginMsg loginMsg=new LoginMsg();                loginMsg.setPassword("yao");                loginMsg.setUserName("robin");                channelHandlerContext.writeAndFlush(loginMsg);            }break;            case PING:{                System.out.println("receive ping from server----------");            }break;            case ASK:{                ReplyClientBody replyClientBody=new ReplyClientBody("client info **** !!!");                ReplyMsg replyMsg=new ReplyMsg();                replyMsg.setBody(replyClientBody);                channelHandlerContext.writeAndFlush(replyMsg);            }break;            case REPLY:{                ReplyMsg replyMsg=(ReplyMsg)baseMsg;                ReplyServerBody replyServerBody=(ReplyServerBody)replyMsg.getBody();                System.out.println("receive client msg: "+replyServerBody.getServerInfo());            }            default:break;        }        ReferenceCountUtil.release(msgType);    }}

 bootstrap

public class NettyClientBootstrap {    private int port;    private String host;    private SocketChannel socketChannel;    private static final EventExecutorGroup group = new DefaultEventExecutorGroup(20);    public NettyClientBootstrap(int port, String host) throws InterruptedException {        this.port = port;        this.host = host;        start();    }    private void start() throws InterruptedException {        EventLoopGroup eventLoopGroup=new NioEventLoopGroup();        Bootstrap bootstrap=new Bootstrap();        bootstrap.channel(NioSocketChannel.class);        bootstrap.option(ChannelOption.SO_KEEPALIVE,true);        bootstrap.group(eventLoopGroup);        bootstrap.remoteAddress(host,port);        bootstrap.handler(new ChannelInitializer<SocketChannel>() {            @Override            protected void initChannel(SocketChannel socketChannel) throws Exception {                socketChannel.pipeline().addLast(new IdleStateHandler(20,10,0));                socketChannel.pipeline().addLast(new ObjectEncoder());                socketChannel.pipeline().addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));                socketChannel.pipeline().addLast(new NettyClientHandler());            }        });        ChannelFuture future =bootstrap.connect(host,port).sync();        if (future.isSuccess()) {            socketChannel = (SocketChannel)future.channel();            System.out.println("connect server  成功---------");        }    }    public static void main(String[]args) throws InterruptedException {        Constants.setClientId("001");        NettyClientBootstrap bootstrap=new NettyClientBootstrap(9999,"localhost");         LoginMsg loginMsg=new LoginMsg();        loginMsg.setPassword("yao");        loginMsg.setUserName("robin");        bootstrap.socketChannel.writeAndFlush(loginMsg);        while (true){            TimeUnit.SECONDS.sleep(3);            AskMsg askMsg=new AskMsg();            AskParams askParams=new AskParams();            askParams.setAuth("authToken");            askMsg.setParams(askParams);            bootstrap.socketChannel.writeAndFlush(askMsg);        }    }}

具体的例子和相应pom.xml 见  https://github.com/WangErXiao/ServerClient

转发请注明来源: http://my.oschina.net/robinyao/blog/399060

总结:

java实现tcp的序列化和反序列化的时候,最好使用json字符串传递数据。这样确保客户端反序列化成功。如果客户端和服务端都是使用java实现,则使用对象序列化和反序列化可以的。如果客户端使用非java语言实现的,不能使用java的对象序列化和反序列化。最好使用字符串传递数据。推荐使用json字符串。

           

给我老师的人工智能教程打call!http://blog.csdn.net/jiangjunshow
这里写图片描述
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值