# 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中的直接使用,是一样的