手写netty高性能分布式发布订阅系统

       闲来无聊。公司因为有事情需要借助mqtt等物联网平台,进行收发消息,用了一些java开源的mqtt软件,挺方便的,但是痴迷网络编程的我决定自己写一个类似mqtt一样的发布订阅功能,就看好了netty作为整个通信的骨架,整个项目也就写了三天,项目雏形已现,可以进行测试了,后期将要对其进行改造,当前版本为v1.0,单机版本,后期我会在陆续发布一款适用于分布式的发布订阅系统,楼主也是一个刚毕业的小白,水平有限,忘大家见谅。

     1.项目v1.0架构

v1.0版本

       整个项目是一个代理Broker,和多个Client和Publisher共同组成,Broker负责客户端的订阅注册,负责监听所有连接的心跳,超过指定时限,会自动的剔除,broker还作为鉴权端,认证所有的连接者,认证成功的可以继续进行下一步操作,认证失败则断开连接。client是作为消息的监听者,负责主题的订阅和删除,publisher负责主题消息的发送。

    2.项目组织结构

     

 

   这是真个项目的源码结构图 ,下面将一一讲解。

2.1Broker原理实现

      1. 核心模块,netty服务端构建 BrokeServer   

public class BrokeServer implements Server {
	
	private NioEventLoopGroup worker=new NioEventLoopGroup();
	private NioEventLoopGroup boss=new NioEventLoopGroup();
	
	private final Logger LOGGER=Logger.getLogger(BrokeServer.class);
	
	public  final static BrokeServer INSTANCE=new BrokeServer();
	
	
	private  BrokeServer() {
		// TODO Auto-generated constructor stub
	}
	

	@Override
	public void start() {
		ServerBootstrap serverBootstrap = new ServerBootstrap();
		serverBootstrap.group(boss, worker).channel(NioServerSocketChannel.class)
		.childHandler(new ChanelInitializerHandler())
		.childOption(ChannelOption.SO_KEEPALIVE, true);
		try {
			ChannelFuture channelFuture = serverBootstrap.bind(9999).sync();
			channelFuture.addListener(new GenericFutureListener<Future<? super Void>>() {
				@Override
				public void operationComplete(Future<? super Void> future) throws Exception {
				   
					if(future.isSuccess()||future.isDone()){
						LOGGER.info("【broker已启动】ip->"+channelFuture.channel().remoteAddress()+" port->"+9999);
					}else{
						LOGGER.info("【broker】启动失败");
					}
					
				}
			});
			 channelFuture.channel().closeFuture().sync();
		} catch (InterruptedException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}finally {
			worker.shutdownGracefully();
			boss.shutdownGracefully();
		}
	}

	@Override
	public void stop() {
		if(worker!=null){
			worker.shutdownGracefully();
		}
		if(boss!=null){
			boss.shutdownGracefully();
		}
		
		LOGGER.info("【broker】关闭成功");
	}

}

    就是netty常用的模式,一个worker和boss,也就是Reactor模型,随后添加了监听器,处理器,处理器初始化是     ChanelInitializerHandler(),

public class ChanelInitializerHandler extends ChannelInitializer {

	@Override
	protected void initChannel(Channel ch) throws Exception {
	       ChannelPipeline pipeline = ch.pipeline();
	       pipeline.addLast(new IdleStateHandler(0,0,15));
	       pipeline.addLast(new IdleStateTrigger());
	       //设计包的格式 1字节固定包头  1字节功能码  1字节(判断是否包含topic字段) 4字节固定长度字段   12字节固定topic(非必须)  剩余字节数据
	       pipeline.addLast(new LengthFieldBasedFrameDecoder(2048, 3, 4, 0, 0));
	       pipeline.addLast(new MessageToPoDecoder());
	       //添加认证的处理器
	       pipeline.addLast("auth",new AuthenticationHandler());
	       pipeline.addLast(new MessageEncoder());
	}

}

         2.发送协议讲解        

    该项目的通信协议非常简单,包体格式如下:

固定包头(1byte)          0xA8功能码(1byte)topic判断字段(1byte)数据长度字段(4byte)固定主题字段(12byte)

  数据字段

(可变长)

 

      协议解析:1.固定包头,根据固定包头来解析数据,防止传入非法的数据包头,很简单有一个字符0xA8

                        2.功能码,用来确定该数据报的作用,是否是心跳,发布信息,订阅信息,等等描述功能动作

                        3.topic判断字段,确定是否包含有topic字段,默认 0 没有   1含有topic字段

                        4.数据长度,4个字节,其数值=固定主题字段长度+数据字段长度

                        5.固定主题字段,保存topic的信息

                        6.数据字段,用来传输数据

         3.编码器解析

     1. IdleStateHandler是一个读写空闲状态的监听器,具体原理很简单,大家可以自行百度

     2.IdleStateTrigger自定义类负责监听客户端连接是否有效

     3.LengthFieldBasedFrameDecoder(2048, 3, 4, 0, 0)是一个基于固定长度字段的帧格式的协议解码器,netty自带,非常常用

     4.MessageToPoDecoder自定义的将bytefuf解析为Message Po对象

     5.AuthenticationHandler鉴权认证器

    IdleStateTrigger

public class IdleStateTrigger extends ChannelInboundHandlerAdapter {
	
	private final Logger LOGGER=Logger.getLogger(IdleStateTrigger.class);


    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if(evt instanceof IdleStateEvent){
            IdleStateEvent event= (IdleStateEvent) evt;
            switch (event.state()){
                case READER_IDLE:
                	 //服务端触发事件,说明客户端已经掉线,关闭失效连接
                	LOGGER.error("【客户端已断开】"+ctx.channel().remoteAddress()+"  "+DateUtils.getCurrentDateTime());
                    ctx.close();
                    break;
            }
        }
        super.userEventTriggered(ctx, evt);
    }
}

  MessageToPoDecoder

 

public class MessageToPoDecoder extends ReplayingDecoder<Void> {
	
	private final byte BODY_HEAD=(byte) 0xA8;
    
	 //协议格式 1字节固定包头  1字节功能码  1字节(判断是否包含topic字段) 4字节固定长度字段   12字节固定topic(非必须)  剩余字节数据
	@Override
	protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception {
		//System.out.println("服务器收到");
		byte fixHead = in.readByte();//读取固定头部 0xA8
		if(!checkHead(fixHead)){
			System.out.println("服务器收到头部:"+fixHead);
			ctx.channel().close();
			throw new IllegalDataHeaderException(fixHead);
		}
		byte funCode = in.readByte();//读取功能码
		byte ishaveTopic = in.readByte(); //读取判断字段
		int bodyLength=in.readInt(); //读取固定数据长度字段 
	    byte[] topic=null;
	    byte[] body=null;
	    if(ishaveTopic==1){
	    	topic=new byte[12];
	    	in.readBytes(topic);
	    	bodyLength=bodyLength-12; //若有topic主题字段 则读取body体字段长度-12
	    }
	    	body=new byte[bodyLength];
	    	in.readBytes(body);
	    //System.out.println("添加MSG功能码:"+FuncodeEnum.getEumInstanceByType(funCode));
	    out.add(new Message(FuncodeEnum.getEumInstanceByType(funCode), ishaveTopic, topic,bodyLength, body));
		

		
	}	
	/**
	 * 包头校验
	 * @param b
	 * @return
	 */
	private boolean checkHead(byte b){
		if(b==BODY_HEAD)
			return true;
		else 
			return false;
	}

	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
		// TODO Auto-generated method stub
		super.exceptionCaught(ctx, cause);
        cause.printStackTrace();
	}
}

未完待续....................

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值