基本思路: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(9);
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(9,"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实现服务端与客户端的长连接通讯,及心跳检测。的更多相关文章

  1. Netty实现服务端客户端长连接通讯及心跳检测

    通过netty实现服务端与客户端的长连接通讯,及心跳检测.        基本思路:netty服务端通过一个Map保存所有连接上来的客户端SocketChannel,客户端的Id作为Map的key.每 ...

  2. 保持WCF服务端与客户端的长连接

    背景 客户端与服务端使用WCF建立连接后:1.可能长时间不对话(调用服务操作):2.客户端的网络不稳定. 为服务端与客户端两边都写“心跳检测”代码?不愿意. 解决 设置inactivityTimeou ...

  3. Netty入门系列(1) --使用Netty搭建服务端和客户端

    引言 前面我们介绍了网络一些基本的概念,虽然说这些很难吧,但是至少要做到理解吧.有了之前的基础,我们来正式揭开Netty这神秘的面纱就会简单很多. 服务端 public class PrintServ ...

  4. Tcp服务端判断客户端是否断开连接

    今天搞tcp链接弄了一天,前面创建socket,绑定,监听等主要分清自己的参数,udp还是tcp的.好不容易调通了,然后就是一个需求,当客户端主动断开连接时,服务端也要断开连接,这样一下次客户端请求链 ...

  5. Netty学习笔记(二) 实现服务端和客户端

    在Netty学习笔记(一) 实现DISCARD服务中,我们使用Netty和Python实现了简单的丢弃DISCARD服务,这篇,我们使用Netty实现服务端和客户端交互的需求. 前置工作 开发环境 J ...

  6. Netty 学习(二):服务端与客户端通信

    Netty 学习(二):服务端与客户端通信 作者: Grey 原文地址: 博客园:Netty 学习(二):服务端与客户端通信 CSDN:Netty 学习(二):服务端与客户端通信 说明 Netty 中 ...

  7. Netty 学习(一):服务端启动 & 客户端启动

    Netty 学习(一):服务端启动 & 客户端启动 作者: Grey 原文地址: 博客园:Netty 学习(一):服务端启动 & 客户端启动 CSDN:Netty 学习(一):服务端启 ...

  8. vertx 从Tcp服务端和客户端开始翻译

    写TCP 服务器和客户端 vert.x能够使你很容易写出非阻塞的TCP客户端和服务器 创建一个TCP服务 最简单的创建TCP服务的方法是使用默认的配置:如下 NetServer server = ve ...

  9. 采用MQTT协议实现android消息推送(2)MQTT服务端与客户端软件对比、android客户端示列表

    1.服务端软件对比 https://github.com/mqtt/mqtt.github.io/wiki/servers 名称(点名进官网) 特性 简介 收费 支持的客户端语言 IBM MQ 完整的 ...

随机推荐

  1. DevExpress学习1

    为了程序的美观设计,决定用dev控件. 第一步,去官网下载了试用30天的安装包,官网地址:https://www.devexpress.com/,安装完成. 第二步,开始程序下找到DevExpress ...

  2. DIV以及图片水平垂直居中兼容多种浏览器

    纯css完美地解决图片以及div垂直水平居中,兼容IE7.0.IE6.0.IE5.5.IE5.0.FF.Opera.Safari具体实现css 如下,感兴趣的朋友可以参考下哈   第一种:全CSS控制 ...

  3. 分享:一个基于NPOI的excel导入导出组件(强类型)

    一.引子 新进公司被安排处理系统的数据报表任务——对学生的考试成绩进行统计并能导出到excel.虽然以前也有弄过,但感觉不是很好,所以这次狠下心,多花点时间作个让自己满意的插件. 二.适用领域 因为需 ...

  4. node.js(四)path优化(路径优化)

    1.normalize函数的基本用法 normalize函数将不符合规范的路径经过格式化转换为标准路径,解析路径中的.与..外,还能去掉多余的斜杠. 如下示例: var path = require( ...

  5. Oracle的总体回顾

    1.多表查询:一张以上的表进行查询,称为多表查询,多表查询的时候可以为表指定别名的方式以简化查询列的编写,在多表查询中,会产生笛卡尔积,就是两张表的总数相乘得到的结果,如果要想消除笛卡尔积要通过关联条 ...

  6. Eclipse 浏览文件插件- OpenExplorer

    http://blog.csdn.net/w709854369/article/details/6599167 EasyExplorer  是一个类似于 Windows Explorer的Eclips ...

  7. Eclipse --Type /com.xx.app/gen already exists but is not a source folde解决方案

    两种解决方案: Two actions, first: 1.Right click on the project and go to "Properties" 2.Select & ...

  8. php中date函数获取当前时间的时区误差解决办法

    例:echo date('Y-m-d H:i:s', time()); 输出时间:2008-10-12 02:32:17 但实际时间是:2008-10-12 10:32:17时间误差8个小时 PHP手 ...

  9. php配置redis支持

    在php.ini里面添加下面两行,注意这两行的顺序一定不要颠倒(扩展库下载网址https://github.com/phpredis/phpredis/downloads),同时注意这2个文件的版本一 ...

  10. getchar()与EOF

    大师级经典的著作,要字斟句酌的去读,去理解.以前在看K&R的The C Programming Language(Second Edition)中第1.5节的字符输入/输出,很迷惑getcha ...