# project-d **Repository Path**: ArcherHua/project-d ## Basic Information - **Project Name**: project-d - **Description**: No description available - **Primary Language**: Java - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 2 - **Forks**: 1 - **Created**: 2020-07-09 - **Last Updated**: 2021-03-03 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # Project d network ## 1.什么是project d network 在游戏中存在大量的tcp传输以及udp传输,不同于http协议,基于url进行资源定位,tcp需要使用消息id来进行消息分发,因为具有大量的重复代码,为了使开发人员,减少网络层的编码解码等工作的关心,以及协议的解析,更加关注业务逻辑。 ## 2.project d network的使用 ### 2.1 maven引用 #### network ~~~xml org.project.d network ${network.version} ~~~ #### springboot ~~~xml org.project.d.starter network-springboot-starter ${network.version} ~~~ ### 2.2 使用 #### 2.2.1 启动 Tcpserver ~~~java public static void main(String[] args) { TcpServer tcpServer = new TcpServer(8080,"127.0.0.1"); tcpServer.init(); tcpServer.start(); } ~~~ UdpServer ~~~java public static void main(String[] args) { UdpServer udpServer = new UdpServer(8080,"127.0.0.1"); udpServer.init(); udpServer.start(); } ~~~ 目前统一的配置启动服务器,以上2个例子作为启动方式,主要关注pipeline的添加,默认 在tcp中使用`PacketDecoder`,`PacketEncoder`进行编码解码,也可以使用`TcpCombinedChannelDuplexHandler` 在udp中使用`UDPPacketDecoder`,`UDPPacketDecoder`进行编码解码,也可以使用`UdpCombinedChannelDuplexHandler` `ConnectionHandler`是主要关键的把链接封装为`Connection`中,其中`ConnectionEvent`用于接受链接一段时间没被使用后更变时的信息,主要用于心跳检测,定义如下 ~~~java @Data @Builder public class ConnectionEvent { /** * 缓存的链接({@link Connection})改变后会触发该函数 * key= ip:port v={@link Connection} * */ @Builder.Default private RemovalListener removalListener=(s)->{}; /** * tcp 心跳通知事件 udp设置后也不生效 * udp 心跳事件通过 removalListener 实现 udp底层是使用的cache缓存链接,udp实际却并不是一个链接,所以需要特殊处理,在netty中udp的channel至始至终只有一个,而tcp的服务器channel一个链接就会创建一个channel * */ private HeartEvent heartEvent; /** * 标示用于什么链接,可以不填 * */ @Builder.Default private Protocol protocol=Protocol.TCP_AND_UDP; /** * udp的过期时间,到了过期时间后,会清除该链接({@link Connection}) * key= ip:port v={@link Connection} * */ @Builder.Default private long udpExpireDuration=Long.parseLong((System.getProperty("project.d.udpExpireDuration","4"))); /** * udp的过期时间,到了过期时间后,会清除该链接({@link Connection}) * key= ip:port v={@link Connection} * */ @Builder.Default private TimeUnit udpExpireUnit=TimeUnit.SECONDS; /** * 是否使用内置的定时任务线程池,对udp进行cleanUp,因为UDP用的是LFU算法,所以需要定时cleanUp * 如果不实用内置的定时任务线程池,请使用netty的IdleStateHandler 可以减少cleanUp的次数 * */ @Builder.Default private boolean useBuiltInTimingThreadPool=true; } ~~~ `MsgDispatcherHandler`是消息分发器,根据前端传输的消息id找寻相应的消息处理器 #### 2.2.2 消息映射处理 ##### 直接使用 ###### DispatchManager `DispatchManager` 是用于存放消息id和对应的消息处理类的绑定关系 ###### Dispatch `Dispatch`就是我们需要创建的映射,定义如下 ```java @Data @Builder public class Dispatch { @NonNull private CommandCode commandCode; @NonNull private MsgHandler msgHandler; private Class msgClass; private DeSerialized deSerialized; } ``` 其中`CommandCode`就是消息id,`MsgHandler`是消息处理器,`msgClass`是接受的消息对象的class,当需要有特殊的反序列化方式时,可以指定`DeSerialized`,如果不指定,则会使用客户端在协议中传输的序列化方式进行反序列化 ###### MsgHandler `MsgHandler` 定义如下 ```java public interface MsgHandler { Transmission doHandler(Message message); } ``` 当需要给客户端发送消息时可以使用 ```java message.getConnection() .send((b) -> b.message(new byte[]{}).commandCode(() -> (short) 561)); ``` 发送`Transmission`对象,当然因为`MsgHandler`接口定义了返回对象为`Transmission`,所以可以直接返回`Transmission`不需要获取`Connection`发送。 如果返回`Transmission`为null则不会回复消息。 简单例子如下 ```java DispatchManager.addDispatch((d) -> d.msgClass(Integer.class).commandCode(() -> (short) 560) .msgHandler( (message) -> { message.getConnection() .send((b) -> b.message(new byte[]{}).commandCode(() -> (short) 561)); return null; })); ``` ###### Transmission 返回的`Transmission`对象定义如下 ```java @Builder @Data public class Transmission{ private Byte serializationType; @NonNull private CommandCode commandCode; @NonNull private Object message; private Serialized serialized; public void setCommandCode(CommandCode commandCode) { this.commandCode = commandCode; } public void setMessage(Object message) { this.message = message; } public CommandCode getCommandCode() { return commandCode; } public Object getMessage() { return message; } } ``` ###### **SerializationType** 其中`serializationType`表示传输对象的序列化方式,具体的序列化对应的值和方式如下,1为json,2位protobuf,0为不处理 ```java @Getter public enum SerializationType { UNKNOWN((byte)0,new NoSerialization()), JSON((byte) 1, new JsonSerialization()), PROTOBUF((byte)2,new ProtobufSerialization()) ; private final byte type; private final Serialization serialization; SerializationType(byte type, Serialization serialization) { this.type = type; this.serialization=serialization; } public static SerializationType SerializationType(byte type) { SerializationType[] enumConstants = SerializationType.class.getEnumConstants(); for (SerializationType enumConstant : enumConstants) { if (type==enumConstant.getType()){ return enumConstant; } } return SerializationType.UNKNOWN; } } ``` ##### 与springboot结合 只需要引入2.2.1中的start jar包即可 使用例子如下 ```java @org.project.d.starter.network.annotation.MsgHandler public class LockStepHandler{ @Autowired private LockStepService lockStepService; @HandlerMapping(value = 25,deSerializa = C2SMsgPlayerInput.class) public Transmission lockStepHandler(Message message) { Optional bodyObj = message.getBodyObj(); C2SMsgPlayerInput c2SMsgPlayerInput = bodyObj.orElse(message.decodeBody(C2SMsgPlayerInput.class)); Connection connection = message.getConnection(); if (lockStepService!=null){ lockStepService.onNextFrameEvent(c2SMsgPlayerInput, connection); } return null; } @HandlerMapping(value = 30,deSerializa = ReissueFrame.class) public Transmission reissueFrameHandler(Message message) { Optional bodyObj = message.getBodyObj(); ReissueFrame reissueFrame = bodyObj.orElseGet(()->message.decodeBody(ReissueFrame.class)); Connection connection = message.getConnection(); if (lockStepService!=null){ lockStepService.ReissueFrame(reissueFrame, connection); } return null; } @HandlerMapping(value = 32) public Transmission gameOver(Message message){ Optional bodyObj = message.getBodyObj(); GameOver gameOver = bodyObj.orElseGet(()->message.decodeBody(GameOver.class)); lockStepService.end(gameOver.getRoomId()); return Transmission.builder().commandCode(Command.OK).message("").build(); } } ``` ###### @MsgHandler 标示该对象为一个`MsgHandler`,并托管于spring ###### @HandlerMapping `@HandlerMapping`有两个参数,一个为接受的消息id,一个为需要特殊反序列化`DeSerialized`的class对象 被`@HandlerMapping`标记的方法返回参数需要是`Transmission`对象,接收对象如果没有指定特殊反序列化方式,则可以直接接受`Message`对象,也可以你需要的实体对象,当指定了`DeSerializa`后必须要使用如下格式获取到传输对象。例如下面获取`ReissueFrame`对象 ~~~java Optional bodyObj = message.getBodyObj(); ReissueFrame reissueFrame = bodyObj.orElseGet(()->message.decodeBody(ReissueFrame.class)); ~~~ 返回的`Transmission`使用方法,和2.2.2中的直接使用,是一样的