最近公司在做一个远程控制的机器人,我这边主要负责了控制端的编写,在开发过程中因为要实现控制端的登陆验证问题,以及从服务器获取可操控设备列表,选择设备这一系列的操作,本来准备通信直接用原生的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