# Server
-
抽象出 NetService 来处理主线程和网络线程通信
-
抽象出来
AService
来处理监听,管理连接 -
抽象出
AChannel
来处理消息收发 -
多个
AService
注册到NetService
-
主线程封装
NetComponent
处理收到的连接、消息,错误 -
主线程封装
Session
做主线程消息发送,以及 Rpc
发送:
客户端主线程 -> 客户端网络线程 -> 服务端网络线程 -> 服务端主线程
返回:
服务端主线程 -> 服务端网络线程 -> 客户端网络线程 -> 客户端主线程
# 需要用到线程安全队列的地方
主线程与网络线程的职责分配
主线程: NetworkComponent/Session
网络线程: AService/AChannel
哪些操作需要线程安全
主线程 -> 网络线程时:
- 创建连接创建 Channel
- 发送消息给 Channel 发送
网络线程 -> 主线程:
- 接收连接创建 Session
- 接收消息回调主线程
- 网络错误回调
主线程什么情况与网络线程交互
- 主线程创建连接 Session 主线程 -> 网络线程
- 主线程向服务端发送消息 主线程 -> 网络线程
- 网络线程返回 accept 网络线程 -> 主线程
- 网络线程返回 Response 或者 RPC 网络线程 -> 主线程
- 消息发送错误处理 网络线程 -> 主线程
# 多线程开发注意事项
- 除了线程队列,一个成员永远只能由一个线程操作
- 非常清楚每个字段是哪个线程操作的
- Net 操作,跨线程 (主线程 -> 网络线程) 消息要注意 ET (NetOperator)
- 并非直接调用 NetChannel 进行分发,而是通过线程安全队列去分发消息
# TCP
# TCP 特点
- 有连接 /syn ack fin
- 可靠有序
- 滑动窗口 拥塞窗口 (控制发包速度)
- 存在问题:一旦丢包,窗口指数级下降,但是恢复很慢
- 流式协议
TCP 实现方式
- CPP 封装 dll
- C# BeginSend EndSend API (GC 太大,基本都放弃了)
- TCPListener TcpClient NetworkStream (有少量 GC)
- SocketAsyncEventArgs 异步回调,没有 GC
- System.IO.Pipelines 相比于第 4 种没有本质的提升
因为 TCP 是流式协议,他并不保证发送的资源一定会一次性到来,但是会保证所有的数据可以按顺序到来
所以就出现了所谓粘包分包的问题,但其实是没有这种问题的
所以通常 Socket 协议会是 长度 | opCode | 内容
这种结构
# SocketAsyncEventArgs
- TcpService Listen
- TChannel Read Write
可以参考 ET.Core 里的 TService 实现,注意一点,SocketAsyncEventArgs 的回调会在另外一个 IO 线程处理 (IOCP),所以处理返回消息时需要处理线程安全
# CircularBuffer
比如 Socket 缓冲区收到了数据,可以一边往尾部写 一边队头读 (? 很普遍的数据结构,并非环形
# PacketParser
即对 TCP 发来的包进行解析,每个 Packet 一定会解析到当前包体满足包头大小为止
# TCP 的问题
- 有连接,无法定制底层的实现,如它的断开是操作系统控制的
- 拥塞控制算法年代久远,过于落后,会被其他的应用抢带宽,以及会因为这个机制导致延迟增高
- 丢包重传算法,数据冗余
- ICMP 有些时候如果 TCP 包发不到的时候,操作系统会发送一个 ICMP,同时断开 TCP,但有的时候我们不想断开
- 容易破解
- 受到内核控制 不好控制 (如调整拥塞控制就没办法)
# UDP
# UDP 特点
- 无连接
- 不可靠
- 无序
# KCP
由于 UDP 的不可靠和无序不太可以容忍,所以经常通过 UDP 近似 TCP 来实现可靠 UDP,KCP 就是其中的一种,即应用层网络连接算法
- 应用层连接
- 可靠有序
- 数据冗余、降低丢包影响
- 拥塞控制
- 好定制
- http3.0
# Actor
问题:多个线程操作同一个变量
方式 1:
每个线程都访问自己的变量,如果某个线程需要访问其他线程变量,通过消息去获取
- 但是这种方式仍然可以从代码上让某个线程访问到其他线程变量
方式 2 (Erlang):
抽象出一种 虚拟进程->即每个虚拟进程无法直接拿到对方的变量
线程不直接保存变量,只保存 虚拟进程
,每个 虚拟进程
有自己的 虚拟栈
所有 虚拟进程
的交互通过 MailBox
进行数据的交互
即线程调度 虚拟进程
,编程则与线程无关了
上述的 虚拟进程只是简单的数据结构,并非真正的进程
# ET Actor
- 实例 ID
- 场景的实例 ID
- 服务器所有内部消息发送都是通过 Actor 发送,gate 转发也是通过 Actor 发送
- MailboxComponent 有三种 MailboxType
- 加入队列、等待 (如 RPC,一个消息没完成,前面的消息会进行等待)
- 加入队列、不等待
- 直接向 session 发送
- Actor 死锁