# Server

  • 抽象出 NetService 来处理主线程和网络线程通信

  • 抽象出来 AService 来处理监听,管理连接

  • 抽象出 AChannel 来处理消息收发

  • 多个 AService 注册到 NetService

  • 主线程封装 NetComponent 处理收到的连接、消息,错误

  • 主线程封装 Session 做主线程消息发送,以及 Rpc

发送:

客户端主线程 -> 客户端网络线程 -> 服务端网络线程 -> 服务端主线程

返回:

服务端主线程 -> 服务端网络线程 -> 客户端网络线程 -> 客户端主线程

# 需要用到线程安全队列的地方

主线程与网络线程的职责分配

主线程: NetworkComponent/Session

网络线程: AService/AChannel

哪些操作需要线程安全

主线程 -> 网络线程时:

  1. 创建连接创建 Channel
  2. 发送消息给 Channel 发送

网络线程 -> 主线程:

  1. 接收连接创建 Session
  2. 接收消息回调主线程
  3. 网络错误回调

主线程什么情况与网络线程交互

  • 主线程创建连接 Session 主线程 -> 网络线程
  • 主线程向服务端发送消息 主线程 -> 网络线程
  • 网络线程返回 accept 网络线程 -> 主线程
  • 网络线程返回 Response 或者 RPC 网络线程 -> 主线程
  • 消息发送错误处理 网络线程 -> 主线程

# 多线程开发注意事项

  1. 除了线程队列,一个成员永远只能由一个线程操作
  2. 非常清楚每个字段是哪个线程操作的
  3. Net 操作,跨线程 (主线程 -> 网络线程) 消息要注意 ET (NetOperator)
    1. 并非直接调用 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 死锁
編集日

*~( ̄▽ ̄)~[お茶]を一杯ください

Solvarg WeChat 支払う

WeChat 支払う

Solvarg Alipay

Alipay

Solvarg PayPal

PayPal