我与计算机编程 - [Today is Java] - [Netty与Protobuf]

最近公司在做一个远程控制的机器人,我这边主要负责了控制端的编写,在开发过程中因为要实现控制端的登陆验证问题,以及从服务器获取可操控设备列表,选择设备这一系列的操作,本来准备通信直接用原生的tcp还有json来实现,但是因为考虑到字段其实不少,并且后期可能扩展,为了更方便维护,决定使用protobuf,java服务器端使用netty来代替原生tcp。以下是java使用netty编写的一个小demo给我们的java服务器小哥使用。

首先从服务器的启动来看。

public class Server {

	private static final Logger logger = LoggerFactory.getLogger(Server.class);

	public static Server instance;

	private ApplicationContext applicationContext;

	/**
	 * 构造方法,启动了连接器和接收器
	 */
	public Server() {
		applicationContext = new ClassPathXmlApplicationContext("applicationContext.xml");

		@SuppressWarnings("unchecked")
		PacketTransfer<ClientPacket> clientPacketTransfer = applicationContext.getBean("clientPacketTransfer", PacketTransfer.class);

		// 监听玩家客户端连接
		Acceptor<ClientPacket, ClientAcceptorProtocolHandler> clientAcceptor = new Acceptor<>();
		clientAcceptor
		.port(Config.CLIENT_PORT)
		.parentSize(Config.CLIENT_ACCEPTOR_THREAD)
		.childrenSize(Config.CLIENT_IO_THREAD)
		.handleClass(ClientAcceptorProtocolHandler.class)
		.packetClass(ClientPacket.class)
		.clientFactory(new PlayerClientFactory())
		.transfer(clientPacketTransfer);
		try {
			clientAcceptor.start();
		} catch (Exception e) {
			logger.error("", e);
			System.exit(1);
		}
	}

	public static void main(String[] args) {
		instance = new Server();
	}

}

从main中启动,创建server,获取上下文的配置,通过getBean来获取clientPacketTransfer。这个东西的主要作用就是,对客户端传过来的消息id,来获取对应的处理类。我们可以来看看。

/**
 * 转换器,用于获取协议对应处理器
 * @param <I> 协议类型
 */
public interface PacketTransfer<I extends GeneratedMessage> {

	/**
	 * 读取之前绑定的映射关系,获取绑定协议的处理器
	 * @param channel 连接对象
	 * @param in 要处理的消息
	 * @return
	 * @throws Exception
	 */
	public <T extends GeneratedMessage> PacketHandler<T> transfer(Channel channel, I in) throws Exception;

}
/**
 * 客户端协议转换器,用于获取客户端协议对应处理器
 */
public class ClientPacketTransfer implements PacketTransfer<ClientPacket> {

	/**
	 * 协议-处理器映射容器
	 */
	private PacketHandlerMapperMgr mgr;

	/**
	 * 构造方法
	 * @param mgr 对应协议的映射容器
	 */
	public ClientPacketTransfer(PacketHandlerMapperMgr mgr) {
		this.mgr = mgr;
	}

	@SuppressWarnings("unchecked")
	@Override
	public <T extends GeneratedMessage> AbstractClientPacketHandler<T> transfer(Channel channel, ClientPacket packet) throws Exception {

		ClientPacketHeader header = packet.getHeader();

		PacketHandlerMapper mapper = mgr.getMapper(header.getType());
		if (mapper == null) {
			return null;
		}
		Class<?> msgClass = mapper.getMsgClass();
		Class<?> handleClass = mapper.getHandleClass();

		Method parser = msgClass.getMethod("parseFrom", ByteString.class);
		parser.setAccessible(true);

		ByteString data = packet.getContent();
		T o = (T) parser.invoke(null, data);

		AbstractClientPacketHandler<T> handle = (AbstractClientPacketHandler<T>) handleClass.newInstance();
		handle.setHeader(header);
		handle.setMsg(o);
		handle.setChannel(channel);

		return handle;

	}
}

其中,transfer方法,通过protobuf文件的header来得知,客户端传过来的具体是哪条消息。然后通过PacketHandlerMapperMgr来获取对应的mapper对象,也就是PacketHandlerMapper,这个类中存储了客户端请求对应的protobuf消息体,还有对应处理的handler,获取到mapper之后就可以获取protobuf消息体,还有handler。其中要对protobuf消息提进行反序列化。最后创建处理对象handler的实例,并给handler赋值,设置消息头,消息体,还有通信的channel。我们可以看看PacketHandlerMapperMgr还有PacketHandlerMapper。

/**
 * PacketHandlerMapper映射管理器基类
 */
public abstract class AbstractPacketHandlerMapperMgr implements PacketHandlerMapperMgr {

	private final Map<Integer, PacketHandlerMapper> mappers = new HashMap<>();

	@Override
	public void registerMapper(PacketHandlerMapper mapper) {
		mappers.put(mapper.getPid(), mapper);
	}

	@Override
	public PacketHandlerMapper getMapper(int pid) {
		return mappers.get(pid);
	}

}
/**
 * 编码方式映射管理器
 */
public abstract class CoderPacketHandlerMapperMgr extends AbstractPacketHandlerMapperMgr {

	/**
	 * 注册映射
	 * @param pid 协议ID
	 * @param msgClass 协议类型
	 * @param handleClass 处理器类型
	 */
	public void registerMapper(int pid, Class<?> msgClass, Class<?> handleClass) {
		PacketHandlerMapper mapper = new PacketHandlerMapper();
		mapper.setPid(pid);
		mapper.setMsgClass(msgClass);
		mapper.setHandleClass(handleClass);
		registerMapper(mapper);
	}

}
/**
 * 消息编号,消息结构与消息处理映射。
 * @see com.syg.netcore.packethandler.mapper.PacketHandlerMapperMgr
 */
public class PacketHandlerMapper {

	private int pid;
	private Class<?> handleClass;
	private Class<?> msgClass;

	/**
	 * 获取绑定的协议号
	 * @return 协议号
	 */
	public int getPid() {
		return pid;
	}
	
	/**
	 * 设置绑定协议号
	 * @param pid 协议号
	 */
	public void setPid(int pid) {
		this.pid = pid;
	}

	/**
	 * 获取对应处理器类型
	 * @return 处理器类型
	 */
	public Class<?> getHandleClass() {
		return handleClass;
	}

	/**
	 * 设置对应处理器类型
	 * @param handleClass 处理器类型
	 */
	public void setHandleClass(Class<?> handleClass) {
		this.handleClass = handleClass;
	}

	/**
	 * 获取消息类型
	 * @return 消息类型
	 */
	public Class<?> getMsgClass() {
		return msgClass;
	}

	/**
	 * 设置消息类型
	 * @param msgClass 消息类型
	 */
	public void setMsgClass(Class<?> msgClass) {
		this.msgClass = msgClass;
	}
}

然后重新回来server的启动中。

		// 监听玩家客户端连接
		Acceptor<ClientPacket, ClientAcceptorProtocolHandler> clientAcceptor = new Acceptor<>();
		clientAcceptor
		.port(Config.CLIENT_PORT)
		.parentSize(Config.CLIENT_ACCEPTOR_THREAD)
		.childrenSize(Config.CLIENT_IO_THREAD)
		.handleClass(ClientAcceptorProtocolHandler.class)
		.packetClass(ClientPacket.class)
		.clientFactory(new PlayerClientFactory())
		.transfer(clientPacketTransfer);

这里初始化了Acceptor对象,对象中两个泛型分别是,protobuf的客户端包,还有对应处理该客户端包的处理handler。

然后给其设置值,分别是端口号,池子大小,处理handler,客户端对象,还有就是上边讲过了的transfer。

这里看下ClientAcceptorProtocolHandler的实现。

首先看基类:

/**
 * 协议处理器
 * @param <I> 读取到的消息类型
 * @param <O> 对应的处理器类型
 */
public abstract class ProtocolHandler<I extends GeneratedMessage> extends ChannelInboundHandlerAdapter implements PacketDispacther<I> {

	private static Logger logger=LoggerFactory.getLogger(ProtocolHandler.class);
	
	private PacketTransfer<I> transfer;

	/**
	 * 构造方法
	 * @param transfer 转换器,用协议ID获取处理器对象
	 */
	public ProtocolHandler(PacketTransfer<I> transfer) {
		super();
		this.setTransfer(transfer);
	}

	@SuppressWarnings("unchecked")
	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
		if (msg instanceof GeneratedMessage) {
			Channel channel = ctx.channel();
			dispatch(channel, (I) msg);
		} else {
			ctx.fireChannelRead(msg);
		}
	}

	public void dispatch(Channel channel, I packet) {
		try {
			PacketHandler<?> handler = getTransfer().transfer(channel, packet);
			if (handler != null) {
				handler.run();
			}
		} catch (Exception e) {
			logger.error("协议处理异常",e);
		}
	}

	/**
	 * 获取转换器
	 * @return 转换器
	 */
	public PacketTransfer<I> getTransfer() {
		return transfer;
	}

	/**
	 * 设置转换器
	 * @param transfer 转换器
	 */
	public void setTransfer(PacketTransfer<I> transfer) {
		this.transfer = transfer;
	}

}

其重写了netty的channelRead方法,这个方法就是读取客户端发过来的包,当然这个包是已经被java的netty管线处理过的,已经完成了长度解码,数据反序列化,所以参数中的msg就是一个clientpacket。clientpacket是继承自GeneratedMessage的,这是protobuf的规则。然后执行dispatch方法,dispatch方法中,通过获取前面讲解的transfer来获取该消息对应的处理类。然后执行run函数来执行对应的逻辑。

/**
 * 接收协议处理器
 * @param <I> 接收协议的类型
 */
public abstract class AcceptorProtocolHandler<I extends GeneratedMessage> extends ProtocolHandler<I> {

	private IClientFactory<? extends AbstractClient> clientFactory;

	/**
	 * 构造方法
	 * @param clientFactory 连接客户端对象的工厂
	 * @param transfer 转换器,用ID获取处理器对象
	 */
	public AcceptorProtocolHandler(IClientFactory<? extends AbstractClient> clientFactory, PacketTransfer<I> transfer) {
		super(transfer);
		this.clientFactory = clientFactory;
	}

	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
		Channel channel = ctx.channel();
		AbstractClient client = clientFactory.create(channel);
		channel.attr(AbstractClient.CLIENT_KEY).set(client);
		client.clientCreated();
	}

	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
		Channel channel = ctx.channel();
		AbstractClient client = (AbstractClient) channel.attr(AbstractClient.CLIENT_KEY).get();
		client.clientClosed();
	}

}

这个AcceptorProtocolHandler继承了上边的ProtocolHandler,重写了netty的channelActive和channelInactive来处理客户端的连接和断线。

/**
 * 处理玩家发送过来的协议
 */
public class ClientAcceptorProtocolHandler extends AcceptorProtocolHandler<ClientPacket> {

	private static final Logger logger = LoggerFactory.getLogger(ClientAcceptorProtocolHandler.class);

	/**
	 * 构造函数
	 * @param clientFactory 客户端信息类工厂
	 * @param transfer 转换器
	 */
	public ClientAcceptorProtocolHandler(IClientFactory<PlayerClient> clientFactory, PacketTransfer<ClientPacket> transfer) {
		super(clientFactory, transfer);
	}

	@Override
	public void dispatch(Channel channel, ClientPacket packet) {
		ClientPacketHeader clientHeader = packet.getHeader();
		int pid = clientHeader.getType();
		logger.info("[SYG] From Client, Pid:[{}]",pid);
		super.dispatch(channel, packet);
		
		//添加多服务器在后边添加
	}
}

这个类继承了AcceptorProtocolHandler,重写了dispatch方法,这里看起来只是输出了pid,其实这里作为可以扩展服务器集群的入手点。最后调用父类的dispatch,找到处理类执行run方法。

继续回到server的启动中。当设置完成参数的时候。就可以启动了。

		try {
			clientAcceptor.start();
		} catch (Exception e) {
			logger.error("", e);
			System.exit(1);
		}

我们来看看 Accept中的start方法。

	/**
	 * 接收器启动方法,设置启动器各项参数并绑定接收端口
	 * @throws Exception
	 */
	public void start() throws Exception {

		check();

		NioEventLoopGroup parent = new NioEventLoopGroup(parentSize);
		NioEventLoopGroup children = new NioEventLoopGroup(childrenSize);

		ServerBootstrap serverBootstrap = new ServerBootstrap();
		serverBootstrap.group(parent, children);
		serverBootstrap.channel(NioServerSocketChannel.class);
		serverBootstrap.childHandler(new ChannelInitializer<NioSocketChannel>() {

			@Override
			protected void initChannel(NioSocketChannel ch) throws Exception {

				ChannelPipeline p = ch.pipeline();
				// 解码
				Method method = packetClass.getMethod("getDefaultInstance");
				@SuppressWarnings("unchecked")
				I value = (I) method.invoke(null);

				p.addLast("lengthDecoder", new ProtobufLengthDecoder());
				p.addLast("decoder", new ProtobufDecoder(value));
				p.addLast("lengthEecoder", new ProtobufLengthEncoder());
				p.addLast("encoder", new ProtobufEncoder());

				Constructor<H> constructor = handleClass.getConstructor(IClientFactory.class, PacketTransfer.class);
				// 业务逻辑处理
				p.addLast("handler", constructor.newInstance(clientFactory, transfer));
			}

		});

		serverBootstrap.bind(port).addListener(new GenericFutureListener<Future<? super Void>>() {

			public void operationComplete(Future<? super Void> future) throws Exception {
				if (!future.isSuccess()) {
					logger.error("监听端口[" + port + "]失败", future.cause());
					System.exit(1);
				}
				logger.info("监听端口:[{}]成功", port);
			}
		});
	}
	

主要完成的工作是,检查前边设置的参数。初始化netty。添加处理流水线。

				ChannelPipeline p = ch.pipeline();
				// 解码
				Method method = packetClass.getMethod("getDefaultInstance");
				@SuppressWarnings("unchecked")
				I value = (I) method.invoke(null);

				p.addLast("lengthDecoder", new ProtobufLengthDecoder());
				p.addLast("decoder", new ProtobufDecoder(value));
				p.addLast("lengthEecoder", new ProtobufLengthEncoder());
				p.addLast("encoder", new ProtobufEncoder());

				Constructor<H> constructor = handleClass.getConstructor(IClientFactory.class, PacketTransfer.class);
				// 业务逻辑处理
				p.addLast("handler", constructor.newInstance(clientFactory, transfer));

可以看到里面含有消息长度编解码,protobuf编解码的流水线节点。

最后是绑定端口,这样一个服务器就启动了。

然后当客户端发来消息的时候,处理流程就是,首先进行长度的解码,然后进行protobuf的反序列化,获取到的数据,通过ProtocolHandler中重写netty的channelRead方法进行处理,然后通过调用dispatch进行分发,然后执行子类的dispatch打印消息id,然后执行ProtocolHandler的dispatch,找到对应的处理类。执行run方法执行逻辑。

下面我们来看下执行逻辑的类。

public class LoginHandler extends ClientPacketHandler<c2s_login>{
	
	private static final Logger logger = LoggerFactory.getLogger(LoginHandler.class);
	@Override
	protected void runImpl() throws Throwable {
		// TODO Auto-generated method stub
		c2s_login requert = getMsg();
		String name = requert.getUsername();
		String password = requert.getUserpassword();
		if (name != "123" && password != "123") {
			//正确
			s2c_login.Builder builder = s2c_login.newBuilder();
			builder.setResult(1);
			for (int i = 0; i < 4; i++) {
				kinghua.protodemo.Login.device_info.Builder deviceBuilder = kinghua.protodemo.Login.device_info.newBuilder();
				deviceBuilder.setBattary(5);
				deviceBuilder.setDeviceId(i);
				deviceBuilder.setDeviceIp("192.168.1.81");
				deviceBuilder.setDeviceName("三号线1号机组");
				builder.addDeviceList(deviceBuilder);
			}
			getClient().SendClientPacket(PID.s2c_Login, builder.build());
			//writeMsg(PID.s2c_Login, builder.build());
		}else {
			//错误
			s2c_login.Builder builder = s2c_login.newBuilder();
			builder.setResult(0);
			builder.setReason(1);
			getClient().SendClientPacket(PID.s2c_Login, builder.build());
			//writeMsg(PID.s2c_Login, builder.build());
		}
	}
	@Override
	protected void setTimeThreadLocal(ThreadLocal<Long> timeThreadLocal) {
		// TODO Auto-generated method stub
		
	}
}

这个就是对应的处理登陆请求的handler。我们看看他们的父类。

/**
 * 服务器消息处理器
 * @param <T> 消息类型,应该是ClientPacket子类
 */
public abstract class ClientPacketHandler<T extends GeneratedMessage> extends AbstractClientPacketHandler<T> {

	public PlayerClient getClient() {
		return (PlayerClient) getChannel().attr(AbstractClient.CLIENT_KEY).get();
	}
}
/**
 * gate<-->client消息处理器
 * @param <T> 消息类型,因为是发给游戏客户端的消息,应该是ClientPacket的子类
 */
public abstract class AbstractClientPacketHandler<T extends GeneratedMessage> extends PacketHandler<T>{
	
	@Override
	public ClientPacketHeader getHeader() {
		return (ClientPacketHeader) super.getHeader();
	}

	/**
	 * 向客户端发送消息
	 * @param reqId 消息序列ID,用于判断顺序
	 * @param pid 协议ID
	 * @param msg 要发送的消息
	 */
	public void writeMsg(int pid, GeneratedMessage msg) {
		ClientPacket.Builder packet = ClientPacket.newBuilder();
		ClientPacketHeader.Builder newHeader = ClientPacketHeader.newBuilder();
		
		newHeader.setType(pid);
		packet.setContent(msg.toByteString());
		getChannel().writeAndFlush(packet);
		logger.info("[SYG]To Client,Pid:[{}]",pid);
	}
	
}
/**
 * 消息处理器
 * @param <T> 消息类型
 */
public abstract class PacketHandler<T extends GeneratedMessage> implements Runnable {

	/**
	 * 消息发送的时间,用于判断消息的顺序
	 */
	public static ThreadLocal<Long> timeThreadLocal = new ThreadLocal<Long>();

	protected static final Logger logger = LoggerFactory.getLogger(PacketHandler.class);

	/**
	 * 要处理消息的消息头
	 */
	private GeneratedMessage header;
	/**
	 * 消息体
	 */
	private T msg;
	/**
	 * 消息来源channel
	 */
	private Channel channel;
	
	public void run() {
		try {
			setTimeThreadLocal(timeThreadLocal);
			runImpl();
		}catch(GameException e){

		}catch (Throwable e) {
			logger.error("execute packet handler error.", e);
		}
	}

	/**
	 * 实际处理逻辑,由子类实现
	 * @throws Throwable
	 */
	protected abstract void runImpl() throws Throwable;

	/**
	 * 设置收到消息的时间,用于判断消息的顺序
	 * @param timeThreadLocal 收到消息的时间戳
	 */
	protected abstract void setTimeThreadLocal(ThreadLocal<Long> timeThreadLocal);

	/**
	 * 获取消息头
	 * @return header 消息头
	 */
	public GeneratedMessage getHeader() {
		return header;
	}

	/**
	 * 设置消息头
	 * @param header 消息头
	 */
	public void setHeader(GeneratedMessage header) {
		this.header = header;
	}

	/**
	 * 获取连接对象
	 * @return channel连接对象
	 */
	public Channel getChannel() {
		return channel;
	}

	/**
	 * 设置连接对象
	 * @param channel
	 */
	public void setChannel(Channel channel) {
		this.channel = channel;
	}

	/**
	 * 设置消息体
	 * @param msg
	 */
	public void setMsg(T msg) {
		this.msg = msg;
	}

	/**
	 * 获取消息体
	 * @return
	 */
	public T getMsg() {
		return msg;
	}
	
	public abstract void writeMsg(int pid, GeneratedMessage msg);

}

基类中主要是负责获取设置消息头,消息体。在子类中重写了像客户端发送消息的方法。

其中,处理消息处理类handler和消息id是通过这样的方式绑定的。


public class ServerCodePacketHandlerMapperMgr extends CoderPacketHandlerMapperMgr{
	@Override
	public void init() throws Exception {
		registerMapper(PID.c2s_Login, c2s_login.class, LoginHandler.class);
	}
}

registerMapper就是添加id和ptorobuf包还有处理类handler的映射关系的方法。

最后贴一下,部分proto文件。

message ClientPacketHeader{
	required int32 type=1;
}


message ClientPacket{
	required ClientPacketHeader header=1;
	required bytes content=2;
}


message ServerPacketHeader{
	required int32 type=1;
}


message ServerPacket{
	required ServerPacketHeader header=1;
	required bytes content=2;
}

项目代码:https://github.com/HanGaaaaa?tab=repositories

CSDN地址:https://download.csdn.net/download/weixin_43409627/11791602

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值