在推送中主要是为了维持tcp长链接,其中采用的技术包括ping/pong 断开从连。本文简单的实现:
重要数据结构:
public enum MsgType{
PING,PONG,LONGIN,PUSH
}
//所有消息类型的基类
public class BaseMsg implements Serializable{
private static final long serialVersionUID = 1L;
private String clientId;//唯一标示防止channel调用混乱
private MsgType type;
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(){
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;
}
}
public class PingMsg extends BaseMsg{
public PingMsg(){
super();
setType(MsgType.PING);
}
}
public class PongMsg extends BaseMsg{
public PongMsg(){
setType(MsgType.PONG);
}
}
public class PushMsg extends BaseMsg {
private String title;
private String content;
public PushMsg() {
setType(MsgType.PUSH);
}
public String getTitle() {
return title;
}
public void setTitle(String title) {
this.title = title;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
}
保存Channel的类
public class NettyChannelMap {
private Map<String, SocketChannel> map = new ConcurrentHashMap<String, SocketChannel>();
private static NettyChannelMap instance = new NettyChannelMap();
private NettyChannelMap(){
}
public static NettyChannelMap getInstance(){
return instance;
}
public void addSocketChannel(String clientId,SocketChannel socketChannel){
map.put(clientId, socketChannel);
}
public SocketChannel getSocketChannel(String clientId){
return map.get(clientId);
}
public void removeSocketChannel(String clientId){
map.remove(clientId);
}
public void removeSocketChannel(SocketChannel socketChannel){
if(map.containsValue(socketChannel)){//查看是否包含
String key = null;
SocketChannel value = null;
for(Map.Entry<String, SocketChannel> entry: map.entrySet()){
key = entry.getKey();
value = entry.getValue();
if(value==socketChannel){
break;
}
}
map.remove(key);
}
}
public void pushMsg2AllClient(PushMsg pushMsg){
if(map.size()==0)
return;
for(SocketChannel socketChannel : map.values()){
socketChannel.writeAndFlush(pushMsg);
}
}
}
服务端处理的Handler
public class NettyServerHandler extends SimpleChannelInboundHandler<BaseMsg>{
@Override
protected void messageReceived(ChannelHandlerContext ctx, BaseMsg baseMsg) throws Exception {
SocketChannel socketChannel = (SocketChannel) ctx.channel();
// 这里为客户端传过来的信息
switch (baseMsg.getType()) {
case PING:
PingMsg pingMsg = (PingMsg) baseMsg;
PongMsg pongMsg = new PongMsg();
SocketChannel socketChannel2 = NettyChannelMap.getInstance().getSocketChannel(pingMsg.getClientId());
socketChannel2.writeAndFlush(pongMsg);
System.out.println("收到PING类消息,clientId为"+pingMsg.getClientId());
break;
case LOGIN:
LoginMsg loginMsg = (LoginMsg) baseMsg;
NettyChannelMap.getInstance().addSocketChannel(loginMsg.getClientId(), socketChannel);
System.out.println("CientId:"+loginMsg.getClientId()+" userName:"+loginMsg.getUserName()+" password:"+loginMsg.getPassword()+"登录成功!");
break;
default:
System.out.println("------defautl-------");
break;
}
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// channel失效,从Map中移除
SocketChannel socketChannel = (SocketChannel) ctx.channel();
NettyChannelMap.getInstance().removeSocketChannel(socketChannel);
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// TODO Auto-generated method stub
System.out.println("出现异常:"+cause.getMessage());
ctx.close();
}
}
客户端的处理类:
public class NettyClientHandler extends SimpleChannelInboundHandler<BaseMsg>{
//设置心跳时间 开始时间
public static final int MIN_CLICK_DELAY_TIME = 1000*30;
private int tryTime = 0;
public static final int MAX_TRY_TIME = 8;
private static Map<String, SocketChannel> Map = new ConcurrentHashMap<String, SocketChannel>();
@Override
protected void messageReceived(ChannelHandlerContext ctx, BaseMsg baseMsg) throws Exception {
// TODO Auto-generated method stub
switch (baseMsg.getType()) {
case LOGIN:
break;
case PONG:
System.out.println("接受到服务器端的PONG");
break;
case PUSH:
PushMsg pushMsg =(PushMsg) baseMsg;
System.out.println("收到服务器端的推送消息:主题"+pushMsg.getTitle()+" 内容:"+pushMsg.getContent());
break;
default:
System.out.println("-------default--------");
break;
}
}
@Override
public void channelActive(final ChannelHandlerContext ctx) throws Exception {
// TODO Auto-generated method stub
tryTime=0;
LoginMsg loginMsg = new LoginMsg();
loginMsg.setPassword("yao"+System.currentTimeMillis());
loginMsg.setUserName("robot"+System.currentTimeMillis());
final String clientId= System.currentTimeMillis()+"";
loginMsg.setClientId(clientId);
Map.put(clientId, (SocketChannel) ctx.channel());
//开启线程进行ping
new Thread(new Runnable() {//每隔180秒发送一个ping
public void run() {
while(true){
try {
Thread.sleep(1000*180);
PingMsg pingMsg = new PingMsg();
pingMsg.setClientId(clientId);
ctx.writeAndFlush(pingMsg);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}).start();
ctx.writeAndFlush(loginMsg);
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
// 断线后的操作
super.channelInactive(ctx);
tryTime++;
if(tryTime>MAX_TRY_TIME)
return;
System.out.println("从连接中。。。。。。。");
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
// TODO Auto-generated method stub
System.out.println("出现异常啦:"+cause.getMessage());
ctx.close();
}
}
启动类:
public class NettyClientBootstrap {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup work = new NioEventLoopGroup();
Bootstrap bootstrap = new Bootstrap();
bootstrap.group(work).channel(NioSocketChannel.class)
.option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
// TODO Auto-generated method stub
socketChannel.pipeline().addLast(new ObjectEncoder());
socketChannel.pipeline().addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
socketChannel.pipeline().addLast(new NettyClientHandler());
}
});
ChannelFuture future = bootstrap.connect("127.0.0.1",9999).sync();
System.out.println("推送客户端。。。。。。启动");
future.channel().closeFuture().sync();
work.shutdownGracefully();
}
}
public class NettyServerBootstrap {
public static void main(String[] args) throws InterruptedException {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup();
ServerBootstrap bootstrap = new ServerBootstrap();
bootstrap.group(boss,work).channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.option(ChannelOption.SO_KEEPALIVE, true)
.childOption(ChannelOption.SO_KEEPALIVE, true)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel socketChannel) throws Exception {
socketChannel.pipeline().addLast(new ObjectEncoder());
socketChannel.pipeline().addLast(new ObjectDecoder(ClassResolvers.cacheDisabled(null)));
socketChannel.pipeline().addLast(new NettyServerHandler());
}
});
ChannelFuture future = bootstrap.bind(9999).sync();
System.out.println("推送服务端启动...........");
//开启定时器推送
new Thread(new Runnable() {
public void run() {
try {
while (true) {
Thread.sleep(1000*60*5);
PushMsg pushMsg = new PushMsg();
pushMsg.setContent("当前时间为:"+new Date());
pushMsg.setTitle("整点报时");
NettyChannelMap.getInstance().pushMsg2AllClient(pushMsg);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
future.channel().closeFuture().sync();
boss.shutdownGracefully();
work.shutdownGracefully();
}
}