kcp 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/README.en.md +235 -0
- data/README.md +221 -0
- data/ext/kcp/extconf.rb +9 -0
- data/ext/kcp/ikcp.c +1423 -0
- data/ext/kcp/ikcp.h +452 -0
- data/ext/kcp/kcp_native.c +137 -0
- data/lib/kcp/version.rb +5 -0
- data/lib/kcp.rb +76 -0
- metadata +65 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: 7bf16edd731053438f070b9bd3c31917fc51a6c0c33b38f650fbc81b5fc2f8db
|
|
4
|
+
data.tar.gz: 7b04f35a6eca243cde48a7e8c7b1fa1818a93c50a5a9fb39df1f39ed51299f3c
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 16e4a8fafb2265f223b54fd3ad7d89b9d15ac4fbbc33793606164e6ec1b04f478f5db203b1b703a244067abfe380422d35c0d7ece1ff04d4c269f44be47e6880
|
|
7
|
+
data.tar.gz: fae9a45f110c3e0cbc9ccd4f364bcf71595c4e626bfc00921de7915343030d2ab830759a8d0871b3e1a6e429103d3f0755e679cdcd21cd1e7d7ab124eae3138c
|
data/README.en.md
ADDED
|
@@ -0,0 +1,235 @@
|
|
|
1
|
+
# kcp
|
|
2
|
+
|
|
3
|
+
[简体中文](README.md) | English
|
|
4
|
+
|
|
5
|
+
Ruby bindings for [KCP](https://github.com/skywind3000/kcp) (A Fast and Reliable ARQ Protocol),
|
|
6
|
+
provided as a **native C extension**. KCP is a fast, reliable transport protocol (ARQ) written in C,
|
|
7
|
+
offering lower latency and better real-time behaviour than TCP over lossy / weak networks.
|
|
8
|
+
|
|
9
|
+
## Features
|
|
10
|
+
|
|
11
|
+
- **Native C extension**: compiles the KCP C source (skywind3000/kcp) directly, with C-level performance
|
|
12
|
+
- **Cross-platform**: built via `mkmf` and the Ruby toolchain — **x86_64 / arm64**, **macOS / Linux / Windows**
|
|
13
|
+
- **Byte-stream mode** (`stream = 1`): data delivered as an ordered byte stream, no message framing
|
|
14
|
+
- **Pull-model API**: the engine does not touch sockets; you send/receive UDP datagrams yourself via `output`/`input`
|
|
15
|
+
- Zero runtime dependencies (Ruby >= 3.0 only)
|
|
16
|
+
|
|
17
|
+
## Installation
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
# Gemfile
|
|
21
|
+
gem 'kcp'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
gem install kcp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
The C sources under `ext/kcp` (`ikcp.c` + `kcp_native.c`) are compiled automatically at install time;
|
|
29
|
+
Ruby's `mkmf` picks the right compiler and architecture (MSVC/MinGW on Windows, clang/gcc on macOS/Linux,
|
|
30
|
+
arm/x64 matched automatically).
|
|
31
|
+
|
|
32
|
+
## Quick start
|
|
33
|
+
|
|
34
|
+
The KCP engine only guarantees "reliably deliver a byte stream to the peer". It does **not** handle
|
|
35
|
+
sockets, addressing, or encryption. Create one engine per endpoint and feed each other UDP datagrams:
|
|
36
|
+
|
|
37
|
+
```ruby
|
|
38
|
+
require 'kcp'
|
|
39
|
+
|
|
40
|
+
CONV = 0x1122_3344 # session id, must match on both ends
|
|
41
|
+
|
|
42
|
+
# ---- sender ----
|
|
43
|
+
engine = Kcp::Engine.new(CONV)
|
|
44
|
+
|
|
45
|
+
engine.send("hello kcp") # queue data
|
|
46
|
+
engine.flush # flush immediately
|
|
47
|
+
|
|
48
|
+
# pull encoded KCP packets and hand them to UDP sendto
|
|
49
|
+
while (pkt = engine.output)
|
|
50
|
+
udp_socket.send(pkt, 0, peer_host, peer_port)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
# ---- receiver (after receiving a UDP datagram) ----
|
|
54
|
+
engine.input(recv_datagram) # feed the received packet
|
|
55
|
+
|
|
56
|
+
# pull the ordered, delivered byte stream
|
|
57
|
+
data = engine.recv # => "hello kcp" (nil when no data)
|
|
58
|
+
|
|
59
|
+
# drive the clock periodically (retransmit / window update / keepalive)
|
|
60
|
+
engine.update(Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000)
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## API
|
|
64
|
+
|
|
65
|
+
### `Kcp::Engine.new(conv)`
|
|
66
|
+
|
|
67
|
+
Create an engine. `conv` is a 32-bit session id (`uint32`); **both ends of a connection must use the
|
|
68
|
+
same `conv`**. Use a different `conv` per concurrent connection.
|
|
69
|
+
|
|
70
|
+
```ruby
|
|
71
|
+
engine = Kcp::Engine.new(0x1234)
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### `send(data)`
|
|
75
|
+
|
|
76
|
+
Queue data for reliable delivery. Returns the number of bytes queued, or a negative value on error.
|
|
77
|
+
**Data is not sent immediately** — call `flush` (immediate) or `update` (interval) to produce packets.
|
|
78
|
+
|
|
79
|
+
```ruby
|
|
80
|
+
engine.send("some bytes")
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
### `flush`
|
|
84
|
+
|
|
85
|
+
Flush the send queue into encoded packets immediately (not throttled by `update`'s interval).
|
|
86
|
+
Use `output` afterwards to pull them.
|
|
87
|
+
|
|
88
|
+
```ruby
|
|
89
|
+
engine.send(data)
|
|
90
|
+
engine.flush
|
|
91
|
+
pkt = engine.output
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
### `output`
|
|
95
|
+
|
|
96
|
+
Pull pending encoded packets (a concatenated byte segment, ready for UDP `send`). Returns `nil` when
|
|
97
|
+
there is nothing to send. **The pulled bytes must be sent verbatim to the peer**, which feeds them
|
|
98
|
+
back via `input`.
|
|
99
|
+
|
|
100
|
+
```ruby
|
|
101
|
+
while (pkt = engine.output)
|
|
102
|
+
socket.send(pkt, 0, host, port)
|
|
103
|
+
end
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
### `input(data)`
|
|
107
|
+
|
|
108
|
+
Feed one received encoded packet (the contents of a low-level UDP datagram). KCP parses it and handles
|
|
109
|
+
ACKs / retransmits / data; afterwards use `recv` to read data and `output` to pull packets to send back
|
|
110
|
+
(e.g. ACKs).
|
|
111
|
+
|
|
112
|
+
```ruby
|
|
113
|
+
socket.recvfrom(65_536) do |data, addr|
|
|
114
|
+
engine.input(data)
|
|
115
|
+
end
|
|
116
|
+
```
|
|
117
|
+
|
|
118
|
+
### `recv(maxlen = 65536)`
|
|
119
|
+
|
|
120
|
+
Pull up to `maxlen` bytes of the peer's delivered, **ordered** byte stream. Returns `nil` when there is
|
|
121
|
+
no data.
|
|
122
|
+
|
|
123
|
+
```ruby
|
|
124
|
+
if (chunk = engine.recv)
|
|
125
|
+
# handle the received data
|
|
126
|
+
end
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
### `update(now_ms)`
|
|
130
|
+
|
|
131
|
+
Drive the KCP clock: interval-based retransmit, ACK, window probing. `now_ms` is a **monotonic
|
|
132
|
+
millisecond timestamp** (use `Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000`).
|
|
133
|
+
|
|
134
|
+
Call it periodically in the main loop, e.g. every 10–100ms:
|
|
135
|
+
|
|
136
|
+
```ruby
|
|
137
|
+
loop do
|
|
138
|
+
engine.update((Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i)
|
|
139
|
+
sleep 0.01
|
|
140
|
+
end
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
### `close`
|
|
144
|
+
|
|
145
|
+
Drop the reference to the C object (the underlying C memory is reclaimed by the Ruby GC via
|
|
146
|
+
`TypedData_Wrap_Struct`).
|
|
147
|
+
|
|
148
|
+
```ruby
|
|
149
|
+
engine.close
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## About `update` and `now_ms`
|
|
153
|
+
|
|
154
|
+
KCP has **no internal timer** and never reads the system clock itself. You must feed it the current
|
|
155
|
+
time (`now_ms`) periodically so it can drive all time-related logic:
|
|
156
|
+
|
|
157
|
+
- **Timeout retransmit**: each sent segment carries a `resendts` (next resend time); it is only resent
|
|
158
|
+
once `current` advances past it
|
|
159
|
+
- **RTT measurement**: uses `current - segment.ts` to compute round-trip time, and from it the RTO
|
|
160
|
+
- **Window probing**: sends WINS probes periodically when the peer's window is zero
|
|
161
|
+
- **Dead-link detection**: declares the link dead after a timeout with no response
|
|
162
|
+
|
|
163
|
+
`now_ms` must be a **monotonic millisecond timestamp** (`Process.clock_gettime(Process::CLOCK_MONOTONIC)`),
|
|
164
|
+
because the monotonic clock only moves forward and is unaffected by system-time jumps (NTP sync, manual
|
|
165
|
+
changes). It is a 32-bit millisecond value that wraps every ~49 days; KCP handles the wraparound with a
|
|
166
|
+
signed difference internally, which is expected.
|
|
167
|
+
|
|
168
|
+
### What happens if you don't `update`?
|
|
169
|
+
|
|
170
|
+
1. The first line of `ikcp_flush` is `if (kcp->updated == 0) return;` — and `updated` is only set by
|
|
171
|
+
`update`, so without `update` nothing can even be sent (this gem's `flush` calls `update` once as a
|
|
172
|
+
fallback, so `send + flush` alone still works).
|
|
173
|
+
2. Even if you bypass that, **retransmit relies on `current` advancing**: after a packet is lost, if you
|
|
174
|
+
don't `update`, `resendts` is never reached, so that single loss stalls the connection forever; ACKs
|
|
175
|
+
are also not flushed on interval, the peer's window never advances, and both directions freeze.
|
|
176
|
+
|
|
177
|
+
Bottom line: **`update` is KCP's heartbeat**. `send + flush` is "send immediately" (low latency);
|
|
178
|
+
`update` is "periodic drive" (retransmit / ACK / window / keepalive). Call it every 10–100ms — without
|
|
179
|
+
it, a single packet loss kills the connection.
|
|
180
|
+
|
|
181
|
+
## Full example (UDP echo)
|
|
182
|
+
|
|
183
|
+
The `examples/` directory ships runnable server and client:
|
|
184
|
+
|
|
185
|
+
```bash
|
|
186
|
+
ruby -I lib examples/server.rb 9000 # terminal 1: start the server
|
|
187
|
+
ruby -I lib examples/client.rb 127.0.0.1 9000 # terminal 2: type a line to echo, /quit to exit
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
The full code follows:
|
|
191
|
+
|
|
192
|
+
```ruby
|
|
193
|
+
require 'socket'
|
|
194
|
+
require 'kcp'
|
|
195
|
+
|
|
196
|
+
CONV = 0x1234
|
|
197
|
+
|
|
198
|
+
def now_ms
|
|
199
|
+
(Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# ---- server ----
|
|
203
|
+
server = UDPSocket.new
|
|
204
|
+
server.bind('0.0.0.0', 9000)
|
|
205
|
+
engine = Kcp::Engine.new(CONV)
|
|
206
|
+
|
|
207
|
+
loop do
|
|
208
|
+
data, addr = server.recvfrom(65_536)
|
|
209
|
+
engine.input(data)
|
|
210
|
+
|
|
211
|
+
while (chunk = engine.recv)
|
|
212
|
+
engine.send(chunk) # echo
|
|
213
|
+
end
|
|
214
|
+
engine.flush
|
|
215
|
+
while (pkt = engine.output)
|
|
216
|
+
server.send(pkt, 0, addr[3], addr[1])
|
|
217
|
+
end
|
|
218
|
+
engine.update(now_ms)
|
|
219
|
+
end
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
## Notes
|
|
223
|
+
|
|
224
|
+
1. **KCP only does reliable transport**; socket I/O, peer-address management, encryption and session
|
|
225
|
+
keepalive are up to the caller (the `mnet` gem in this repo builds a roaming transport layer that
|
|
226
|
+
survives IP changes on top of it).
|
|
227
|
+
2. `conv` must match on both ends; use a different `conv` per concurrent connection.
|
|
228
|
+
3. `flush` is immediate (low latency); `update` is interval-throttled (saves CPU). Use both for
|
|
229
|
+
interactive workloads.
|
|
230
|
+
4. KCP uses a 32-bit millisecond timestamp that wraps every ~49 days; this is expected (the protocol
|
|
231
|
+
handles the wraparound).
|
|
232
|
+
|
|
233
|
+
## License
|
|
234
|
+
|
|
235
|
+
MIT. KCP source copyright skywind3000.
|
data/README.md
ADDED
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# kcp
|
|
2
|
+
|
|
3
|
+
[English](README.en.md) | 简体中文
|
|
4
|
+
|
|
5
|
+
[KCP](https://github.com/skywind3000/kcp)(A Fast and Reliable ARQ Protocol)的 Ruby 绑定,
|
|
6
|
+
以**原生 C 扩展**形式提供。KCP 是一个用 C 实现的快速可靠传输协议(ARQ),
|
|
7
|
+
相比 TCP 在丢包/弱网场景下有更低的延迟和更好的实时性。
|
|
8
|
+
|
|
9
|
+
## 特性
|
|
10
|
+
|
|
11
|
+
- **原生 C 扩展**:直接编译 skywind3000/kcp 的 C 源码,性能与 C 一致
|
|
12
|
+
- **跨平台**:通过 `mkmf` + Ruby 工具链构建,支持 **x86_64 / arm64**,**macOS / Linux / Windows**
|
|
13
|
+
- **字节流模式**(`stream = 1`):数据按有序字节流交付,无消息边界
|
|
14
|
+
- **拉取式接口**:引擎不碰 socket,你通过 `output`/`input` 自己收发 UDP 数据报
|
|
15
|
+
- 零运行时依赖(除 Ruby >= 3.0)
|
|
16
|
+
|
|
17
|
+
## 安装
|
|
18
|
+
|
|
19
|
+
```ruby
|
|
20
|
+
# Gemfile
|
|
21
|
+
gem 'kcp'
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```bash
|
|
25
|
+
gem install kcp
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
构建时会自动编译 `ext/kcp` 下的 C 源码(`ikcp.c` + `kcp_native.c`),
|
|
29
|
+
由 Ruby 的 `mkmf` 负责选择对应平台的编译器与架构(Windows 用 MSVC/MinGW,macOS/Linux 用 clang/gcc,arm/x64 自动匹配)。
|
|
30
|
+
|
|
31
|
+
## 快速开始
|
|
32
|
+
|
|
33
|
+
KCP 引擎只管「把字节流可靠送达对端」,**不处理 socket、地址、加密**。
|
|
34
|
+
两端各创建一个引擎,通过 UDP 数据报互相喂包即可:
|
|
35
|
+
|
|
36
|
+
```ruby
|
|
37
|
+
require 'kcp'
|
|
38
|
+
|
|
39
|
+
CONV = 0x1122_3344 # 会话标识,两端必须一致
|
|
40
|
+
|
|
41
|
+
# ---- 发送端 ----
|
|
42
|
+
engine = Kcp::Engine.new(CONV)
|
|
43
|
+
|
|
44
|
+
engine.send("hello kcp") # 数据进入发送队列
|
|
45
|
+
engine.flush # 立即刷出
|
|
46
|
+
|
|
47
|
+
# 从引擎取出编码后的 KCP 包,交给 UDP sendto
|
|
48
|
+
while (pkt = engine.output)
|
|
49
|
+
udp_socket.send(pkt, 0, peer_host, peer_port)
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
# ---- 接收端(收到 UDP 数据报后)----
|
|
53
|
+
engine.input(recv_datagram) # 喂入收到的包
|
|
54
|
+
|
|
55
|
+
# 从引擎取出按序送达的字节流
|
|
56
|
+
data = engine.recv # => "hello kcp"(没有数据时返回 nil)
|
|
57
|
+
|
|
58
|
+
# 周期性驱动(重传 / 窗口更新 / 心跳)
|
|
59
|
+
engine.update(Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000)
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## 接口说明
|
|
63
|
+
|
|
64
|
+
### `Kcp::Engine.new(conv)`
|
|
65
|
+
|
|
66
|
+
创建引擎。`conv` 是 32 位会话标识(`uint32`),**同一条连接的两端必须使用相同的 `conv`**,
|
|
67
|
+
用于区分不同的会话。多个并发连接时应为每个连接分配不同的 `conv`。
|
|
68
|
+
|
|
69
|
+
```ruby
|
|
70
|
+
engine = Kcp::Engine.new(0x1234)
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
### `send(data)`
|
|
74
|
+
|
|
75
|
+
把数据排入发送队列,返回排入的字节数,出错返回负数。**数据不会立即发出**,
|
|
76
|
+
需要调用 `flush`(立即)或 `update`(按间隔)才会生成编码包。
|
|
77
|
+
|
|
78
|
+
```ruby
|
|
79
|
+
engine.send("some bytes")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
### `flush`
|
|
83
|
+
|
|
84
|
+
立即把发送队列里的数据刷成编码包(不受 `update` 的间隔节流)。刷完后用 `output` 取出。
|
|
85
|
+
|
|
86
|
+
```ruby
|
|
87
|
+
engine.send(data)
|
|
88
|
+
engine.flush
|
|
89
|
+
pkt = engine.output
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `output`
|
|
93
|
+
|
|
94
|
+
取出待发送的编码包(可能是一段拼接好的字节,直接交给 UDP `send`)。没有待发送数据时返回 `nil`。
|
|
95
|
+
**取出的字节必须原样发给对端**,对端收到后原样 `input`。
|
|
96
|
+
|
|
97
|
+
```ruby
|
|
98
|
+
while (pkt = engine.output)
|
|
99
|
+
socket.send(pkt, 0, host, port)
|
|
100
|
+
end
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
### `input(data)`
|
|
104
|
+
|
|
105
|
+
喂入一个收到的编码包(低层 UDP 数据报的内容)。KCP 会解析、处理 ACK/重传/数据,
|
|
106
|
+
随后可用 `recv` 取数据、用 `output` 取要回发的包(例如 ACK)。
|
|
107
|
+
|
|
108
|
+
```ruby
|
|
109
|
+
socket.recvfrom(65_536) do |data, addr|
|
|
110
|
+
engine.input(data)
|
|
111
|
+
end
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### `recv(maxlen = 65536)`
|
|
115
|
+
|
|
116
|
+
取出对端已送达的、**按序**的字节流(最多 `maxlen` 字节)。没有数据时返回 `nil`。
|
|
117
|
+
|
|
118
|
+
```ruby
|
|
119
|
+
if (chunk = engine.recv)
|
|
120
|
+
# 处理收到的数据
|
|
121
|
+
end
|
|
122
|
+
```
|
|
123
|
+
|
|
124
|
+
### `update(now_ms)`
|
|
125
|
+
|
|
126
|
+
驱动 KCP 时钟:触发按间隔的重传、ACK、窗口探测等。`now_ms` 是**单调毫秒时间戳**
|
|
127
|
+
(建议用 `Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000`)。
|
|
128
|
+
|
|
129
|
+
应在主循环里周期性调用,例如每 10~100ms 一次:
|
|
130
|
+
|
|
131
|
+
```ruby
|
|
132
|
+
loop do
|
|
133
|
+
engine.update((Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i)
|
|
134
|
+
sleep 0.01
|
|
135
|
+
end
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
### `close`
|
|
139
|
+
|
|
140
|
+
释放对 C 对象的引用(底层 C 内存由 Ruby GC 通过 `TypedData_Wrap_Struct` 自动回收)。
|
|
141
|
+
|
|
142
|
+
```ruby
|
|
143
|
+
engine.close
|
|
144
|
+
```
|
|
145
|
+
|
|
146
|
+
## 关于 `update` 与 `now_ms`
|
|
147
|
+
|
|
148
|
+
KCP **内部没有计时器**,不会自己读系统时间。你必须周期性地喂给它当前时间(`now_ms`),
|
|
149
|
+
它才能驱动所有和时间相关的逻辑:
|
|
150
|
+
|
|
151
|
+
- **超时重传**:每个已发出的段带一个 `resendts`(下次重传时刻),`current` 推进到它之后才会重传
|
|
152
|
+
- **RTT 测量**:用 `current - 段.ts` 算往返时间,进而算出 RTO
|
|
153
|
+
- **窗口探测**:对端窗口为 0 时,定时发送 WINS 探测
|
|
154
|
+
- **死链检测**:超时无响应则判死
|
|
155
|
+
|
|
156
|
+
`now_ms` 必须是**单调毫秒时间戳**(`Process.clock_gettime(Process::CLOCK_MONOTONIC)`),
|
|
157
|
+
因为单调时钟只前进、不受系统时间跳变(NTP 校时、手动改时间)干扰。它是 32 位毫秒,约 49 天回绕一次,
|
|
158
|
+
KCP 内部用有符号差值处理回绕,属正常现象。
|
|
159
|
+
|
|
160
|
+
### 不 `update` 会怎样
|
|
161
|
+
|
|
162
|
+
1. `ikcp_flush` 第一行是 `if (kcp->updated == 0) return;` —— 而 `updated` 只有 `update` 才置位,
|
|
163
|
+
所以不 update 时,连包都发不出去(本 gem 的 `flush` 内部会先兜底调一次 update,故单独 `send + flush` 仍可用)。
|
|
164
|
+
2. 即便绕过第 1 点,**重传依赖 `current` 推进**:丢了一个包后若不 update,`resendts` 永远到不了,
|
|
165
|
+
这一个丢包就会永久卡死;ACK 也不会按间隔刷出,对端窗口不前进,双向一起僵住。
|
|
166
|
+
|
|
167
|
+
结论:**`update` 就是 KCP 的心跳**。`send + flush` 是「立即发」(低延迟),`update` 是「周期性驱动」
|
|
168
|
+
(重传 / ACK / 窗口 / 保活)。每 10~100ms 调一次;缺了它,只要发生一次丢包连接就死。
|
|
169
|
+
|
|
170
|
+
## 完整示例(UDP 回显)
|
|
171
|
+
|
|
172
|
+
仓库内 `examples/` 下有可直接运行的服务端和客户端:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
ruby -I lib examples/server.rb 9000 # 终端 1:起服务端
|
|
176
|
+
ruby -I lib examples/client.rb 127.0.0.1 9000 # 终端 2:连上后输入即回显,/quit 退出
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
下面是对应的完整代码:
|
|
180
|
+
|
|
181
|
+
```ruby
|
|
182
|
+
require 'socket'
|
|
183
|
+
require 'kcp'
|
|
184
|
+
|
|
185
|
+
CONV = 0x1234
|
|
186
|
+
|
|
187
|
+
def now_ms
|
|
188
|
+
(Process.clock_gettime(Process::CLOCK_MONOTONIC) * 1000).to_i
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
# ---- 服务端 ----
|
|
192
|
+
server = UDPSocket.new
|
|
193
|
+
server.bind('0.0.0.0', 9000)
|
|
194
|
+
engine = Kcp::Engine.new(CONV)
|
|
195
|
+
|
|
196
|
+
loop do
|
|
197
|
+
data, addr = server.recvfrom(65_536)
|
|
198
|
+
engine.input(data)
|
|
199
|
+
|
|
200
|
+
while (chunk = engine.recv)
|
|
201
|
+
engine.send(chunk) # 回显
|
|
202
|
+
end
|
|
203
|
+
engine.flush
|
|
204
|
+
while (pkt = engine.output)
|
|
205
|
+
server.send(pkt, 0, addr[3], addr[1])
|
|
206
|
+
end
|
|
207
|
+
engine.update(now_ms)
|
|
208
|
+
end
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
## 注意事项
|
|
212
|
+
|
|
213
|
+
1. **KCP 只管可靠传输**,网络 I/O、对端地址管理、加密、会话保活都由上层负责
|
|
214
|
+
(本仓库的 `mnet` gem 在其上实现了「移动 IP 不中断」的漫游传输层)。
|
|
215
|
+
2. `conv` 两端必须一致;并发连接请用不同 `conv`(或在上层协议里用会话 ID 复用)。
|
|
216
|
+
3. `flush` 是立即刷出(低延迟);`update` 是按间隔节流(省 CPU)。高频交互场景两者配合使用。
|
|
217
|
+
4. KCP 使用 32 位毫秒时间戳,约 49 天回绕一次,属正常现象(协议内部处理了回绕)。
|
|
218
|
+
|
|
219
|
+
## 许可
|
|
220
|
+
|
|
221
|
+
MIT。KCP 源码版权归 skywind3000。
|
data/ext/kcp/extconf.rb
ADDED