Netty實現服務端用戶端長串連通訊及心跳檢測

來源:互聯網
上載者:User

標籤:value   cti   null   sock   inf   class   state   bst   cli   

       摘要: 通過netty實現服務端與用戶端的長串連通訊,及心跳檢測

      通過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;
  rivate 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);
    }
  }
}

Netty實現服務端用戶端長串連通訊及心跳檢測

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.