跳转至

一 客户端基础Socket连接

整体逻辑决定用异步的逻辑去完成。 - 建立NetManager - 建立ByteBuffer字节缓冲区

二 协议MsgBase

一般用Json或者ProtoBuf做演示:

2.1 ☆协议划分--TCP封包与解码 (处理粘包问题)

  • 协议长度(2字节--short)
  • 协议名长度(2字节--short)
  • 协议名(根据协议名长度解码获得)
  • 协议消息体(根据协议长度-协议名长度获得)

2.2 接受数据与发包

  • 发送和接受都是异步完成的,注意消息列表和发送队列加锁。
  • 发送或者接受完一次后,通过回调判断列表和队列是否为空继续发送或者递归相关函数。

三 服务端Socket连接

采用C# .net 8.0编写服务端,特点: - 不需要写main函数 - 引用默认不为空,需要加?或者宏定义 - jsonutility,debug等unity函数库无法使用。 - 可以用.Net的库添加相关的工具包

3.1 构成

部分工作其实和客户端是一致的,比如 - ByteBuffer:字节缓冲区 - MsgBase:消息处理,封包解包 但也有不一样的,比如NetManager就要重构了,并且需要将·socket对应的fd和专门的ClientState对应构成字典。

四 服务端连接

大部分服务器还是运行在Linux端的,目前的异步和Select也是暂时学一遍,后面肯定都用epoll。

4.2 连接Connect过程

  • 建立服务端Socket
  • 设置IP和端口
  • Bind、Listen

  • 执行后续的逻辑

4.1多路复用Select

使用Select,通过轮询的方式进行多路复用。 - 初始化一个list,里面存放所有的socket(包括客户端和服务端的) - 循环检测while - 放入服务端socket到sockets列表中 - 遍历客户端字典,将客户端Socket放入到sockets列表中,后续进行IO多路复用 - 传入socket.select,先暂时只用设置第一个参数(三个参数:读、写、报错),目前只需要读。 - 遍历所有socket - 先判断当前socket为服务端或者客户端,分别执行不同的逻辑

4.2服务端Socket接受连接逻辑Accept

主要逻辑:Accept()并创建客户端对象 - 如果多路复用有,代表需要连接客户端了 - 先通过服务端socket的accept,创建新的socket对象,作为客户端的socket - 同时创建对应的客户端状态ClientState对象 - 作为键值对放入客户端字典当中

4.3 客户端Socket接受发送消息的连接逻辑Receive

  • 主要逻辑:
    • 判断缓冲区空间是否足足够
    • 接受消息,更新字节缓冲数组,并拿到消息字节大小
    • 传入对应客户端对象对消息进行处理
    • 处理完后移动字节缓冲数组

4.4 服务端处理消息

逻辑上其实和客户端差不多,都是处理封包的过程,主要需要从对应的客户端对象上拿字节数组。 - 如果消息没处理完,继续调用消息处理方法。

4.5 服务端发送消息Send

原理也基本不变,但是用的时候需要指定客户端对象,用客户端的socket去发送Send消息。(因为客户端只用发送给服务器,而服务器需要发送给所有的客户端);

五 测试连接

  • 解析消息后,就可以通过消息执行相关的逻辑了
  • 消息解析类继承自MsgBase:但是两边都最好要有相同的类去处理。

六 客户端关闭Close().

注意,Close有两种关闭的情况

一般来说也不会直接关闭,会判断一些特殊情况。 - 正在连接中:isConnecting表示正在连接但还没连接完的状态。‘ - 此时再次调用Connect函数应该报错并返回,不应该多次连接。 - Close:在正在连接的时候,不应该关闭 - 正在关闭中:还有其他的消息未处理(发送)完(writeQueue.Count > 0) - 设置一个isClosing,用来记录已经开始关闭的流程 - 对于Send - isConnecting和isClosing都将阻止继续编码封包发送消息 - Send如果正在发送消息,先等他递归发送(SendCallback)将writeQueue处理完成。 - 最后如果正在isClosing,那么此时发送完了,就直接用socket.Close关闭流程了。 - 没有上述情况,就直接在函数里关闭Socket了。

服务端关闭

这个就多了,错误、失败都直接关闭就行。

七 网络事件管理

7.1 定义枚举网络事件NetEvent:

  • ConnectSucc = 1,
  • ConnectFail = 2,
  • Close,

7.2 定义委托 EventListener

定义一个委托delegate,叫做EventListener,需要传入一个string参数作为错误信息。(但实际上可以不用这个参数,只有失败才会用)

7.3 定义字典

  • 键值对为(网络事件,EventListener)

7.4 添加事件

传入类型和委托参数 - 先根据事件类型判断有无Key,有的话value +=。 - 没有的话ADD键值对

7.5 删除事件

参数传入类型和委托 - 有Key 则-=listener。 - 没key则字典Remove

7.6 分发(执行)事件

传入事件类型和字符串err - 字典里有key就直接执行委托eventListener[netEvent](err)

7.7 写事件

注意,Close有两种关闭的情况 - 在Main函数里添加三个网络事件及其对应处理方式 - 替换之前直接的Debug.Log,用事件的方式输出对应连接结果

八 消息管理

整体其实和上面类似。 只不过负责字典是以【string,委托】的形式进行存储的,方便自定义消息。

处理消息

可以设置一帧处理消息的数量。比如一帧处理10个消息

九 客户端心跳机制

总的来说就是 - 客户端间隔Interval发送Ping,并更新lastPingtime - 服务端收到ping发送Pong回应 - 若超过间隔4倍时间没收到,断开对应的客户端连接 - 客户端收到Pong消息后,更新lastPongTime - 若超过间隔4倍时间没收到,断开自己的socket连接

测试:时间间隔改为2秒,平时30秒。

9.1 Bool变量定义ping、pong消息

新建个文件夹专门存消息,继承自Msgbase

9.2 心跳设置

  • 是否启用心跳机制
  • 上次Ping发送时间
  • 上次收到Pong时间
  • 心跳间隔时间

9.3 心跳更新PingUpdate

每帧都需要进行更新时间,若当前时间-上一帧更新时间>30s(自己设置的时间)。则发送Ping消息。 - 这个和之前的读消息函数MsgUpdate一起放到NetManager的Update里,交由Main函数的Update循环进行调用。 - 断开处理:和服务端lastpongTime的时间差大于间隔的四倍。

9.4 心跳机制,接受Pong消息

  • pong消息处理函数一开始就作为函数,在Init时就传到Addlistener里面,实现方法注册,方便后续调用

十 服务端心跳机制

10.1 拷贝客户端的消息

ping、pong

10.2 消息处理类

用来封装各种消息的处理,确保参数都是【客户端+消息】的形式,方便通过反射快速处理。 - Ping,向对应客户端发送消息 - Pong,

10.3 通过反射调用客户端发过来的协议与方法

  • MethodInfo:方法元信息。
  • 通过protoName协议名来找到对应typeof(MsgHandler)里注册的消息处理方法mi。
  • 如果mi不为空
    • 将要传入的参数统一封装到一个Object类型的数组中
    • mi.Invoke调用方法。

10.4 lastPingTime,超时间隔

服务端同样需要设置超时间隔,只不过lastPing时间就需要单独设置到服务端连接的每个客户端了。 - lastPingTime,直接 - 当超时大于服务端设置的间隔的四倍,直接关闭Close。 - 服务端这边关闭就:直接socket关闭+客户端socket字典就行。

十一 protoBuf协议

11.1 基本语法

  • syntax:指定协议版本号,没有则默认proto2版本(尽量标记一下)
  • message:定义消息类型,相当于struct。
  • enum:定义枚举类型,相当于enum。

概述:

  • message用于定义一个数据结构类型,由一系列字段构成
  • 每个字段的定义由一定格式构成:修饰符+数据类型+字段名称 = 字段标识+字段默认值
  • 其中数据类型、字段名称、字段标识是必须的

举例

  • protoBuf文件:
    syntax = "proto2";
    message MsgPing
    {
        optional string protoName=1[default="MsgPing"];
    }
    

11.2 run编译protobuf的bat文件

  • -i:表示输入路径
  • -o:表示输出路径
    protogen.exe -i:proto\MsgPing.proto -o:cs\MsgPing.cs
    protogen.exe -i:proto\MsgPong.proto -o:cs\MsgPong.cs
    pause
    

或者用通配符,对于大量的proto文件进行批处理

@echo off
for %%i in (proto\*.proto) do (
    protogen.exe -i:%%i -o:cs\%%~ni.cs
)
pause

11.3 导入项目

protobuf包

C#的NuGet库,找到protoBuf-Net插件

导入proto的C#文件

首先将生成的protobuf相关消息文件添加到项目的新的文件夹中

11.4 封包和解码

  • 参数,继承自IExtensible(新的消息基类)

编码

  • 用流的方式写入MemoryStream