使用Netty实现自定义推送

在推送中主要是为了维持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();
}
}


发布了168 篇原创文章 · 获赞 133 · 访问量 91万+
展开阅读全文

netty消息转发中client端循环推送的问题

04-09

接触netty不久,开发时遇到一个问题,求帮忙 流程:page——> java ——> 第三方server ——>java——>page 单写client向第三方循环发送没有问题,该处打印ctx.channel [id: 0x5916337a, L:/xxx.xxx.x.xxx:55041 - R:/xxx.xxx.x.xxx:4002] ``` public void run(String host, int port) throws Exception { EventLoopGroup group = new NioEventLoopGroup(); try { Bootstrap bootstrap = new Bootstrap().group(group).channel(NioSocketChannel.class); // 有数据立即发送 bootstrap.option(ChannelOption.TCP_NODELAY, true); // 保持连接 bootstrap.option(ChannelOption.SO_KEEPALIVE, true); bootstrap.handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { // 自定义解码工具(含拆包粘包处理) ch.pipeline().addLast(new SelfDefineEncodeHandler()); // 自定义方法处理 ch.pipeline().addLast(new HttpClientHandler()); } }); Channel channel = bootstrap.connect(host, port).sync().channel(); System.out.println(channel.toString()); while (true) { ByteBuf bytebuf = Unpooled.buffer(1); bytebuf.writeByte(0xff); channel.writeAndFlush(bytebuf); Thread.sleep(10000); } } catch (Exception e) { e.printStackTrace(); } finally { group.shutdownGracefully(); } } ``` 我先写一个server端接收page传过来的请求 ``` public void run(int port) throws Exception { EventLoopGroup bossGroup = new NioEventLoopGroup(); EventLoopGroup workerGroup = new NioEventLoopGroup(); try { ServerBootstrap b = new ServerBootstrap(); b.group(bossGroup, workerGroup).channel(NioServerSocketChannel.class) .childHandler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { ChannelPipeline pipeline = ch.pipeline(); pipeline.addLast("http-codec", new HttpServerCodec()); pipeline.addLast("aggregator", new HttpObjectAggregator(65536)); pipeline.addLast("http-chunked", new ChunkedWriteHandler()); pipeline.addLast("handler", new DataServerHandler()); } }); Channel ch = b.bind(port).sync().channel(); ch.closeFuture().sync(); } finally { bossGroup.shutdownGracefully(); workerGroup.shutdownGracefully(); } } ``` 然后再 DataServerHandler中的channelRead0中嵌入了netty的client端 ``` // 把外层channel的eventLoop挂接在内层上 Bootstrap bootstrap = new Bootstrap().group(ctx.channel().eventLoop()).channel(NioSocketChannel.class); // 有数据立即发送 bootstrap.option(ChannelOption.TCP_NODELAY, true); // 保持连接 bootstrap.option(ChannelOption.SO_KEEPALIVE, true); bootstrap.handler(new ChannelInitializer<SocketChannel>() { @Override protected void initChannel(SocketChannel ch) throws Exception { // 自定义解码工具(含拆包粘包处理) ch.pipeline().addLast(new SelfDefineEncodeHandler()); // 自定义方法处理 ch.pipeline().addLast(new ChannelInboundHandlerAdapter() { // 内层建立的连接,从这里接收内层的应答,在这里是服务端的应答 @Override public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception { //... } @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { super.channelActive(ctx); } }); } }); connectFuture = bootstrap.connect("xxx.xxx.x.xxx", 7789); System.out.println(connectFuture.channel().toString()); while(true){ ByteBuf bytebuf = Unpooled.buffer(1); bytebuf.writeByte(0xff); connectFuture.channel().writeAndFlush(bytebuf); System.out.println("发送请求"); Thread.sleep(10000); } ``` 因为要向connectFuture连接的路径发送,所以调用connectFuture.channel(),但是该处拿到的connectFuture.channel()为[id: 0x71e6e0dc],因此在测试工具中并没有收到ff的消息 将循环写到channelActive以后,测试工具可以不停的接收传过来的ff,但是java无法接收返回的消息 ``` @Override public void channelActive(ChannelHandlerContext ctx) throws Exception { // outerCnl = ctx.channel(); System.out.println(ctx.channel().toString()); while (true) { ByteBuf bytebuf = Unpooled.buffer(1); bytebuf.writeByte(0xff); ctx.channel().writeAndFlush(bytebuf); Thread.sleep(10000); } // super.channelActive(ctx); } ``` 如果未使用循环,返回的信息可以正确的接收解析并传送给page 刚接触netty,有些东西不太懂,有人有过类似的错误么,望解答 问答

请大神解答一下关于netty客户端定时发送消息给服务端

01-23

1 . 只有一个客户端和服务端 连着 , 发送消息的时候用channel 的 writeAndFlush , 用线程结果Channel 一直被线程占用 , 只能发消息 , 其他的事情做不了 2. 请问有什么解决的办法吗 , 或者netty 是有那个类可以专门处理客户端给服务端发送数据的呢 3. 还有就是有没有关于客户端给服务端发送数据执行流程的博文呢 4 . 谢谢帮助我的大神 ``` // workerGroup处理已经被接收的连接 NioEventLoopGroup workerGroup = new NioEventLoopGroup(); AtsClient client = this; //workerGroup.scheduleAtFixedRate(command, initialDelay, period, unit); try { // Bootstrap是一个启动 NIO 服务的辅助启动类 Bootstrap b = new Bootstrap(); b.group(workerGroup); // 使用NioSocketChannel类的新的channel接收进来的连接 b.channel(NioSocketChannel.class); // option() 是提供给NioSocketChannel用来接收进来的连接 b.option(ChannelOption.SO_KEEPALIVE, true); // 处理最近接收的channel b.handler(new ChannelInitializer<SocketChannel>() { @Override public void initChannel(SocketChannel ch) throws Exception { // 设定IdleStateHandler心跳检测每五秒进行一次写检测,如果五秒内write()方法未被调用则触发一次userEventTrigger()方法,实现客户端每五秒秒向服务端发送一次消息 ch.pipeline().addLast( new IdleStateHandler(0, 5, 0, TimeUnit.SECONDS)); // 解码器 ch.pipeline().addLast("AtsDecoder", new AtsDecoder()); // 处理器 ch.pipeline().addLast("AtsHanler", new AtsHandler(client)); // 编码器 ch.pipeline().addLast("AtsEncoder", new AtsEncoder()); } }); // Start the client. 绑定端口然后启动服务 ChannelFuture f = b.connect(host, port).sync(); channel = f.channel(); System.out.println(channel); channel.eventLoop().scheduleAtFixedRate(new Runnable() { @Override public void run() { sendPlanTrain(); } }, 5, 3, TimeUnit.SECONDS); 这个是客户端连接服务端的代码 , 这里调用了channel的eventLoop().scheduleAtFixedRate , 启动了线程 , 里面是往服务端发送的数据方法 . channel 执行了sendPlanTrain(); 就一直被线程征用 , 其他处理器无法使用 ``` ``` class Sender implements Runnable { Channel channel = null; public Sender(Channel channel) { this.channel = channel; } @Override public void run() { Map<String,String> ScadaDataMap = null; Jedis jedis = null; try { ScadaDataMap = new HashMap<String,String>(); Thread.sleep(3 * 1000); //设置暂停的时间 3 秒 JedisUtilProxy.initJedisPoolUtil("127.0.0.1", 6379); jedis = JedisUtilProxy.getJedis(); for (int i = 1 ; i <= 30 ; i++) { String statu = jedis.hget("SCADA_DATA",String.valueOf(i)); ScadaDataMap.put(String.valueOf(i), statu); } sendPlanTrain(); } catch (Exception e) { e.printStackTrace(); } } ``` ``` @Override public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { logger.debug("===AtsHandler.userEventTriggered.客户端循环心跳监测发送: " + new Date()); if (evt instanceof IdleStateEvent) { IdleStateEvent event = (IdleStateEvent) evt; if (event.state() == IdleState.WRITER_IDLE) { logger.debug("===AtsHandler.userEventTriggered.客户端没有信息发送给服务端 客户端每5秒发送 MESSAGE_POLLING 信息给服务端"); ByteBuf byteBuf = null; byte[] frameHead = new byte[4]; frameHead[0] = (byte) 0xff; frameHead[1] = 0; frameHead[2] = 5; frameHead[3] = 0; byte[] message = new byte[4]; message[0] = 0; message[1] = (byte) 2; message[2] = (byte) (AtsMessageIDCodeEnum.MESSAGE_POLLING .getCode() >> 8); message[3] = (byte) (AtsMessageIDCodeEnum.MESSAGE_POLLING .getCode() & 0xFF); byte[] frame = ArrayUtils.addAll(frameHead, message); byteBuf = ctx.channel().alloc().buffer(8 * 4); byteBuf.writeBytes(frame); ctx.writeAndFlush(byteBuf); } } ``` 问答

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: 大白 设计师: CSDN官方博客

分享到微信朋友圈

×

扫一扫,手机浏览