基于protobuf的netty服务端实现

1.项目目录

基于protobuf的netty服务端实现_第1张图片

2.pom引入netty和protobuf

 
    io.netty
    netty-all
    4.1.22.Final
 
 
	com.google.protobuf
	protobuf-java
    3.5.1
 

 

3.先写个netty的启动类 NettyServer,其中@PostConstruct是springboot启动命令,当你运行项目是就会运行下面的代码,ServerBootstrap和InetSockerAddress则从第4步获取bean注入

/**
 * netty服务端
 * @author 85862
 *
 */
@Component
public class NettyServer {
    @Autowired
    @Qualifier("serverBootstrap")
    private ServerBootstrap b;

    @Autowired
    @Qualifier("tcpSocketAddress")
    private InetSocketAddress tcpPort;

    private ChannelFuture serverChannelFuture;

    @PostConstruct
    public void start() throws Exception {
        System.out.println("Starting server at " + tcpPort);
        serverChannelFuture = b.bind(tcpPort).sync();
    }

    @PreDestroy
    public void stop() throws Exception {
        serverChannelFuture.channel().closeFuture().sync();
    }

    public ServerBootstrap getB() {
        return b;
    }

    public void setB(ServerBootstrap b) {
        this.b = b;
    }

    public InetSocketAddress getTcpPort() {
        return tcpPort;
    }

    public void setTcpPort(InetSocketAddress tcpPort) {
        this.tcpPort = tcpPort;
    }
}

4. 配置bean ,先在application.properties设置配置

#netty 服务端监听的端口
tcp.port=8091
# bossGroup的线程数
boss.thread.count=2
# worker的线程数
worker.thread.count=2
#是否使用长连接
so.keepalive=true
so.backlog=100

然后继续配置bean,其中主要看serverBootstrap,设置ServerChannelInitalizer,设置编码格式等,参考步骤5

/**
 * netty配置类
 * @author 85862
 *
 */
@Configuration
public class NettyConfig {
    
    //读取yml中配置 
    @Value("${boss.thread.count}")
    private int bossCount;

    @Value("${worker.thread.count}")
    private int workerCount;

    @Value("${tcp.port}")
    private int tcpPort;

    @Value("${so.keepalive}")
    private boolean keepAlive;

    @Value("${so.backlog}")
    private int backlog;
    
    @Autowired
    private EventLoopGroup bossGroup;
    
    @Autowired
    private EventLoopGroup workerGroup;

    @Autowired
    private ServerChannelInitalizer serverChannelInitializer;
    
    @SuppressWarnings("unchecked")
    @Bean(name = "serverBootstrap")
    public ServerBootstrap bootstrap() {
        ServerBootstrap b = new ServerBootstrap();
        b.group(bossGroup, workerGroup)
//        b.group(bossGroup(), workerGroup())
                .channel(NioServerSocketChannel.class)
                .childHandler(serverChannelInitializer);
        Map, Object> tcpChannelOptions = tcpChannelOptions();
        Set> keySet = tcpChannelOptions.keySet();
        for (@SuppressWarnings("rawtypes")
                ChannelOption option : keySet) {
            b.option(option, tcpChannelOptions.get(option));
        }
        return b;
    }

    @Bean(name = "bossGroup", destroyMethod = "shutdownGracefully")
    public NioEventLoopGroup bossGroup() {
    	NioEventLoopGroup bossGroup = new NioEventLoopGroup(bossCount);
        return bossGroup;
    }

    @Bean(name = "workerGroup", destroyMethod = "shutdownGracefully")
    public NioEventLoopGroup workerGroup() {
    	NioEventLoopGroup workerGroup = new NioEventLoopGroup(workerCount);
        return workerGroup;
    }

    @Bean(name = "tcpSocketAddress")
    public InetSocketAddress tcpPort() {
        return new InetSocketAddress(tcpPort);
    }

    @Bean(name = "tcpChannelOptions")
    public Map, Object> tcpChannelOptions() {
        Map, Object> options = new HashMap, Object>();
        options.put(ChannelOption.SO_KEEPALIVE, keepAlive);
        options.put(ChannelOption.SO_BACKLOG, backlog);
        return options;
    }

    @Bean(name = "stringEncoder")
    public StringEncoder stringEncoder() {
        return new StringEncoder();
    }

    @Bean(name = "stringDecoder")
    public StringDecoder stringDecoder() {
        return new StringDecoder();
    }

    /**
     * Necessary to make the Value annotations work.
     *
     * @return
     */
    @Bean
    public static PropertySourcesPlaceholderConfigurer propertyPlaceholderConfigurer() {
        return new PropertySourcesPlaceholderConfigurer();
    }
}

5.ChannelPipeline配置,编码格式采用protobuf

/**
 * netty服务端ChannelPipeline配置(客户端的配置需要与服务端一致,编解码方式等)
 * @author 85862
 *
 */
@Component
public class ServerChannelInitalizer extends ChannelInitializer {

    @Autowired
    ServerHandler serverHandler;

    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ChannelPipeline pipeline = ch.pipeline();
        //服务端心跳监听事件间隔
        pipeline.addLast(new IdleStateHandler(80, 0, 0, TimeUnit.SECONDS));
        
        // 解码和编码,应和客户端一致
        //传输的协议 Protobuf
        pipeline.addLast(new ProtobufVarint32FrameDecoder());
        pipeline.addLast(new ProtobufDecoder(NettyMessage.Content.getDefaultInstance()));
        pipeline.addLast(new ProtobufVarint32LengthFieldPrepender());
        pipeline.addLast(new ProtobufEncoder());
        
        
//        pipeline.addLast("decoder", stringDecoder);
//        pipeline.addLast("encoder", stringEncoder);
        pipeline.addLast("handler", serverHandler);
        
    }


}

6. handler,用于消息发送读取,channelActive服务端和客户端初次建立连接走的方法;channelRead当客户端有信息过来,服务端读取数据的方法;userEventTriggered用于处理心跳机制,在case ALL_IDLE自行定义方法

package com.xbstar.fuse.clientcenter.netty;

import com.xbstar.fuse.clientcenter.netty.protobuf.NettyMessage;
import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleStateEvent;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;

/**
 * netty 服务端业务处理
 * @author 85862
 */
@Component
@ChannelHandler.Sharable
public class ServerHandler extends ChannelInboundHandlerAdapter{
	
    private static final Logger logger = LoggerFactory.getLogger(ServerHandler.class);
    
    //初次建立连接
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
    	
        logger.info("\nRemoteAddress : " + ctx.channel().remoteAddress() + " active !");
        //通知客户端连接已建立
        NettyMessage.Content handShake = NettyMessage.Content.newBuilder().
						        		setMethod(NettyConstant.REQUEST).
						        		setOperateType(NettyConstant.HAND_SHAKE).
    									build();
        
        logger.info("\nsend handshake to client:\n{}",handShake);
        ctx.writeAndFlush(handShake);
        
        super.channelActive(ctx);
    }
    
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
        logger.info("\nreceive message from client: \n{}",msg);
        if(!(msg instanceof NettyMessage.Content)) {
        	logger.error("\ninvalid message");
        	return;
        }
    	NettyMessage.Content message = (NettyMessage.Content)msg;
    	String operateType = message.getOperateType();
    	NettyMessage.Content.Builder responseBuilder = NettyMessage.Content.newBuilder();
    	NettyMessage.Content response = null;//服务端返回值
    	
    	switch (operateType) {
		case NettyConstant.RULE_INIT:
			break;
		case NettyConstant.REQUEST_DATA_SEND:
			break;
		case NettyConstant.REQUEST_DATA_SEND_EMERGENT:
			break;
		case NettyConstant.REQUEST_OTHER_DATA_SAVE:
			break;
		default:
			break;
		}
    }
    
    /**
	 * 事件触发处理业务逻辑
	 */
	@Override
	public void userEventTriggered(ChannelHandlerContext ctx, Object eve) throws Exception {
		if (eve instanceof IdleStateEvent) {//心跳超时处理
			IdleStateEvent event = (IdleStateEvent) eve;
			switch (event.state()) {
			case READER_IDLE:
				break;
			case WRITER_IDLE:
				break;
			case ALL_IDLE:
				logger.error("Netty server has lost client heartbeat within 5s");
//				如果通道失去心跳连接超过x次,服务端可以选择关闭该不活跃通道
//				if (idle_count > x) {
//					logger.info("Netty server disconnect an inactive client");
//					ctx.channel().close();
//				}
//				idle_count++;
				break;
			default:
				break;
			}
		} else {
			super.userEventTriggered(ctx, eve);
		}
	}


    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        logger.error("服务端发送异常:{}",cause.getMessage());
        /*ctx.close();*/
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        logger.error("\n失去客户端连接,Channel is disconnected");
        super.channelInactive(ctx);
    }
    
}

7. idea生成protobuf

https://www.cnblogs.com/liugh/p/7505533.html

 

你可能感兴趣的:(基于protobuf的netty服务端实现)