├── LICENSE ├── README.md ├── Source ├── Network │ ├── KCP.cs │ ├── LatencySimulator.cs │ ├── Socket.cs │ └── Utils.cs ├── Program.cs └── server.go └── kcp.csproj /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Johnnie Chen 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kcp-dotnet 2 | A [KCP](https://github.com/skywind3000/kcp) C# implementation for .net core 3 | 4 | **CAUTION** this project is NOT production ready! 5 | 6 | ## Prerequisites 7 | 8 | * .net core (https://dotnet.github.io/) 9 | * Golang SDK(optional) (http://golang.org) 10 | 11 | ## How to Use 12 | 13 | ### Run test case 14 | 15 | ``` 16 | git clone https://github.com/qchencc/kcp-dotnet 17 | cd kcp-dotnet 18 | dotnet run 19 | ``` 20 | 21 | ### Communicate with kcp-go 22 | 23 | server 24 | 25 | ``` 26 | go get -v github.com/xtaci/kcp-go 27 | go run server.go 28 | ``` 29 | 30 | client 31 | 32 | ``` 33 | dotnet run socket 34 | ``` 35 | -------------------------------------------------------------------------------- /Source/Network/KCP.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 simon@qchen.fun All rights reserved. 2 | // Distributed under the terms and conditions of the MIT License. 3 | // See accompanying files LICENSE. 4 | 5 | using System; 6 | using System.Diagnostics; 7 | using System.Collections.Generic; 8 | 9 | namespace Network 10 | { 11 | public class KCP 12 | { 13 | public const int IKCP_RTO_NDL = 30; // no delay min rto 14 | public const int IKCP_RTO_MIN = 100; // normal min rto 15 | public const int IKCP_RTO_DEF = 200; 16 | public const int IKCP_RTO_MAX = 60000; 17 | public const int IKCP_CMD_PUSH = 81; // cmd: push data 18 | public const int IKCP_CMD_ACK = 82; // cmd: ack 19 | public const int IKCP_CMD_WASK = 83; // cmd: window probe (ask) 20 | public const int IKCP_CMD_WINS = 84; // cmd: window size (tell) 21 | public const int IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK 22 | public const int IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS 23 | public const int IKCP_WND_SND = 32; 24 | public const int IKCP_WND_RCV = 32; 25 | public const int IKCP_MTU_DEF = 1400; 26 | public const int IKCP_ACK_FAST = 3; 27 | public const int IKCP_INTERVAL = 100; 28 | public const int IKCP_OVERHEAD = 24; 29 | public const int IKCP_DEADLINK = 20; 30 | public const int IKCP_THRESH_INIT = 2; 31 | public const int IKCP_THRESH_MIN = 2; 32 | public const int IKCP_PROBE_INIT = 7000; // 7 secs to probe window size 33 | public const int IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window 34 | 35 | public const int IKCP_LOG_OUTPUT = 0x1; 36 | public const int IKCP_LOG_INPUT = 0x2; 37 | public const int IKCP_LOG_SEND = 0x4; 38 | public const int IKCP_LOG_RECV = 0x8; 39 | public const int IKCP_LOG_IN_DATA = 0x10; 40 | public const int IKCP_LOG_IN_ACK = 0x20; 41 | public const int IKCP_LOG_IN_PROBE = 0x40; 42 | public const int IKCP_LOG_IN_WINS = 0x80; 43 | public const int IKCP_LOG_OUT_DATA = 0x100; 44 | public const int IKCP_LOG_OUT_ACK = 0x200; 45 | public const int IKCP_LOG_OUT_PROBE = 0x400; 46 | public const int IKCP_LOG_OUT_WINS = 0x800; 47 | 48 | 49 | // encode 8 bits unsigned int 50 | public static void ikcp_encode8u(byte[] p, int offset, byte c) 51 | { 52 | p[offset] = c; 53 | } 54 | 55 | // decode 8 bits unsigned int 56 | public static byte ikcp_decode8u(byte[] p, ref int offset) 57 | { 58 | return p[offset++]; 59 | } 60 | 61 | // encode 16 bits unsigned int (lsb) 62 | public static void ikcp_encode16u(byte[] p, int offset, UInt16 v) 63 | { 64 | p[offset] = (byte)(v & 0xFF); 65 | p[offset + 1] = (byte)(v >> 8); 66 | } 67 | 68 | // decode 16 bits unsigned int (lsb) 69 | public static UInt16 ikcp_decode16u(byte[] p, ref int offset) 70 | { 71 | int pos = offset; 72 | offset += 2; 73 | return (UInt16)((UInt16)p[pos] | (UInt16)(p[pos + 1] << 8)); 74 | } 75 | 76 | // encode 32 bits unsigned int (lsb) 77 | public static void ikcp_encode32u(byte[] p, int offset, UInt32 l) 78 | { 79 | p[offset] = (byte)(l & 0xFF); 80 | p[offset + 1] = (byte)(l >> 8); 81 | p[offset + 2] = (byte)(l >> 16); 82 | p[offset + 3] = (byte)(l >> 24); 83 | } 84 | 85 | // decode 32 bits unsigned int (lsb) 86 | public static UInt32 ikcp_decode32u(byte[] p, ref int offset) 87 | { 88 | int pos = offset; 89 | offset += 4; 90 | return ((UInt32)p[pos] | (UInt32)(p[pos + 1] << 8) 91 | | (UInt32)(p[pos + 2] << 16) | (UInt32)(p[pos + 3] << 24)); 92 | } 93 | 94 | public static UInt32 _imin_(UInt32 a, UInt32 b) 95 | { 96 | return a <= b ? a : b; 97 | } 98 | 99 | public static UInt32 _imax_(UInt32 a, UInt32 b) 100 | { 101 | return a >= b ? a : b; 102 | } 103 | 104 | public static UInt32 _ibound_(UInt32 lower, UInt32 middle, UInt32 upper) 105 | { 106 | return _imin_(_imax_(lower, middle), upper); 107 | } 108 | 109 | public static Int32 _itimediff(UInt32 later, UInt32 earlier) 110 | { 111 | return (Int32)(later - earlier); 112 | } 113 | 114 | internal class Segment 115 | { 116 | internal UInt32 conv = 0; 117 | internal UInt32 cmd = 0; 118 | internal UInt32 frg = 0; 119 | internal UInt32 wnd = 0; 120 | internal UInt32 ts = 0; 121 | internal UInt32 sn = 0; 122 | internal UInt32 una = 0; 123 | internal UInt32 resendts = 0; 124 | internal UInt32 rto = 0; 125 | internal UInt32 faskack = 0; 126 | internal UInt32 xmit = 0; 127 | internal byte[] data; 128 | 129 | internal Segment(int size = 0) 130 | { 131 | data = new byte[size]; 132 | } 133 | 134 | internal void Encode(byte[] ptr, ref int offset) 135 | { 136 | UInt32 len = (UInt32)data.Length; 137 | ikcp_encode32u(ptr, offset, conv); 138 | ikcp_encode8u(ptr, offset + 4, (byte)cmd); 139 | ikcp_encode8u(ptr, offset + 5, (byte)frg); 140 | ikcp_encode16u(ptr, offset + 6, (UInt16)wnd); 141 | ikcp_encode32u(ptr, offset + 8, ts); 142 | ikcp_encode32u(ptr, offset + 12, sn); 143 | ikcp_encode32u(ptr, offset + 16, una); 144 | ikcp_encode32u(ptr, offset + 20, len); 145 | offset += IKCP_OVERHEAD; 146 | } 147 | 148 | } 149 | 150 | UInt32 conv_ = 0; 151 | UInt32 mtu_ = 0; 152 | UInt32 mss_ = 0; 153 | UInt32 state_ = 0; 154 | 155 | UInt32 snd_una_ = 0; 156 | UInt32 snd_nxt_ = 0; 157 | UInt32 rcv_nxt_ = 0; 158 | 159 | UInt32 ts_recent_ = 0; 160 | UInt32 ts_lastack_ = 0; 161 | UInt32 ssthresh_ = 0; 162 | 163 | Int32 rx_rttval_ = 0; 164 | Int32 rx_srtt_ = 0; 165 | Int32 rx_rto_ = 0; 166 | Int32 rx_minrto_ = 0; 167 | 168 | UInt32 snd_wnd_ = 0; 169 | UInt32 rcv_wnd_ = 0; 170 | UInt32 rmt_wnd_ = 0; 171 | UInt32 cwnd_ = 0; 172 | UInt32 probe_ = 0; 173 | 174 | UInt32 current_ = 0; 175 | UInt32 interval_ = 0; 176 | UInt32 ts_flush_ = 0; 177 | UInt32 xmit_ = 0; 178 | 179 | UInt32 nrcv_buf_ = 0; 180 | UInt32 nsnd_buf_ = 0; 181 | UInt32 nrcv_que_ = 0; 182 | UInt32 nsnd_que_ = 0; 183 | 184 | UInt32 nodelay_ = 0; 185 | UInt32 updated_ = 0; 186 | UInt32 ts_probe_ = 0; 187 | UInt32 probe_wait_ = 0; 188 | UInt32 dead_link_ = 0; 189 | UInt32 incr_ = 0; 190 | 191 | LinkedList snd_queue_; 192 | LinkedList rcv_queue_; 193 | LinkedList snd_buf_; 194 | LinkedList rcv_buf_; 195 | 196 | UInt32[] acklist_; 197 | UInt32 ackcount_ = 0; 198 | UInt32 ackblock_ = 0; 199 | 200 | byte[] buffer_; 201 | object user_; 202 | 203 | Int32 fastresend_ = 0; 204 | Int32 nocwnd_ = 0; 205 | 206 | public delegate void OutputDelegate(byte[] data, int size, object user); 207 | OutputDelegate output_; 208 | 209 | // create a new kcp control object, 'conv' must equal in two endpoint 210 | // from the same connection. 'user' will be passed to the output callback 211 | // output callback can be setup like this: 'kcp->output = my_udp_output' 212 | public KCP(UInt32 conv, object user) 213 | { 214 | Debug.Assert(BitConverter.IsLittleEndian); // we only support little endian device 215 | 216 | user_ = user; 217 | conv_ = conv; 218 | snd_wnd_ = IKCP_WND_SND; 219 | rcv_wnd_ = IKCP_WND_RCV; 220 | rmt_wnd_ = IKCP_WND_RCV; 221 | mtu_ = IKCP_MTU_DEF; 222 | mss_ = mtu_ - IKCP_OVERHEAD; 223 | rx_rto_ = IKCP_RTO_DEF; 224 | rx_minrto_ = IKCP_RTO_MIN; 225 | interval_ = IKCP_INTERVAL; 226 | ts_flush_ = IKCP_INTERVAL; 227 | ssthresh_ = IKCP_THRESH_INIT; 228 | dead_link_ = IKCP_DEADLINK; 229 | buffer_ = new byte[(mtu_ + IKCP_OVERHEAD) * 3]; 230 | snd_queue_ = new LinkedList(); 231 | rcv_queue_ = new LinkedList(); 232 | snd_buf_ = new LinkedList(); 233 | rcv_buf_ = new LinkedList(); 234 | } 235 | 236 | // release kcp control object 237 | public void Release() 238 | { 239 | snd_buf_.Clear(); 240 | rcv_buf_.Clear(); 241 | snd_queue_.Clear(); 242 | rcv_queue_.Clear(); 243 | nrcv_buf_ = 0; 244 | nsnd_buf_ = 0; 245 | nrcv_que_ = 0; 246 | nsnd_que_ = 0; 247 | ackblock_ = 0; 248 | ackcount_ = 0; 249 | buffer_ = null; 250 | acklist_ = null; 251 | } 252 | 253 | // set output callback, which will be invoked by kcp 254 | public void SetOutput(OutputDelegate output) 255 | { 256 | output_ = output; 257 | } 258 | 259 | // user/upper level recv: returns size, returns below zero for EAGAIN 260 | public int Recv(byte[] buffer, int offset, int len) 261 | { 262 | int ispeek = (len < 0 ? 1 : 0); 263 | int recover = 0; 264 | 265 | if (rcv_queue_.Count == 0) 266 | return -1; 267 | 268 | if (len < 0) 269 | len = -len; 270 | 271 | int peeksize = PeekSize(); 272 | if (peeksize < 0) 273 | return -2; 274 | 275 | if (peeksize > len) 276 | return -3; 277 | 278 | if (nrcv_que_ >= rcv_wnd_) 279 | recover = 1; 280 | 281 | // merge fragment 282 | len = 0; 283 | LinkedListNode next = null; 284 | for (var node = rcv_queue_.First; node != null; node = next) 285 | { 286 | int fragment = 0; 287 | var seg = node.Value; 288 | next = node.Next; 289 | 290 | if (buffer != null) 291 | { 292 | Buffer.BlockCopy(seg.data, 0, buffer, offset, seg.data.Length); 293 | offset += seg.data.Length; 294 | } 295 | len += seg.data.Length; 296 | fragment = (int)seg.frg; 297 | 298 | Log(IKCP_LOG_RECV, "recv sn={0}", seg.sn); 299 | 300 | if (ispeek == 0) 301 | { 302 | rcv_queue_.Remove(node); 303 | nrcv_que_--; 304 | } 305 | 306 | if (fragment == 0) 307 | break; 308 | } 309 | 310 | Debug.Assert(len == peeksize); 311 | 312 | // move available data from rcv_buf -> rcv_queue 313 | while (rcv_buf_.Count > 0) 314 | { 315 | var node = rcv_buf_.First; 316 | var seg = node.Value; 317 | if (seg.sn == rcv_nxt_ && nrcv_que_ < rcv_wnd_) 318 | { 319 | rcv_buf_.Remove(node); 320 | nrcv_buf_--; 321 | rcv_queue_.AddLast(node); 322 | nrcv_que_++; 323 | rcv_nxt_++; 324 | } 325 | else 326 | { 327 | break; 328 | } 329 | } 330 | 331 | // fast recover 332 | if (nrcv_que_ < rcv_wnd_ && recover != 0) 333 | { 334 | // ready to send back IKCP_CMD_WINS in ikcp_flush 335 | // tell remote my window size 336 | probe_ |= IKCP_ASK_TELL; 337 | } 338 | 339 | return len; 340 | } 341 | 342 | // check the size of next message in the recv queue 343 | public int PeekSize() 344 | { 345 | if (rcv_queue_.Count == 0) 346 | return -1; 347 | 348 | var node = rcv_queue_.First; 349 | var seg = node.Value; 350 | if (seg.frg == 0) 351 | return seg.data.Length; 352 | 353 | if (nrcv_que_ < seg.frg + 1) 354 | return -1; 355 | 356 | int length = 0; 357 | for (node = rcv_queue_.First; node != null; node = node.Next) 358 | { 359 | seg = node.Value; 360 | length += seg.data.Length; 361 | if (seg.frg == 0) 362 | break; 363 | } 364 | return length; 365 | } 366 | 367 | // user/upper level send, returns below zero for error 368 | public int Send(byte[] buffer, int offset, int len) 369 | { 370 | Debug.Assert(mss_ > 0); 371 | if (len < 0) 372 | return -1; 373 | 374 | // 375 | // not implement streaming mode here as ikcp.c 376 | // 377 | 378 | int count = 0; 379 | if (len <= (int)mss_) 380 | count = 1; 381 | else 382 | count = (len + (int)mss_ - 1) / (int)mss_; 383 | 384 | if (count > 255) // maximum value `frg` can present 385 | return -2; 386 | 387 | if (count == 0) 388 | count = 1; 389 | 390 | // fragment 391 | for (int i = 0; i < count; i++) 392 | { 393 | int size = len > (int)mss_ ? (int)mss_ : len; 394 | var seg = new Segment(size); 395 | if (buffer != null && len > 0) 396 | { 397 | Buffer.BlockCopy(buffer, offset, seg.data, 0, size); 398 | offset += size; 399 | } 400 | seg.frg = (UInt32)(count - i - 1); 401 | snd_queue_.AddLast(seg); 402 | nsnd_que_++; 403 | len -= size; 404 | } 405 | return 0; 406 | } 407 | 408 | // parse ack 409 | void UpdateACK(Int32 rtt) 410 | { 411 | if (rx_srtt_ == 0) 412 | { 413 | rx_srtt_ = rtt; 414 | rx_rttval_ = rtt / 2; 415 | } 416 | else 417 | { 418 | Int32 delta = rtt - rx_srtt_; 419 | if (delta < 0) 420 | delta = -delta; 421 | 422 | rx_rttval_ = (3 * rx_rttval_ + delta) / 4; 423 | rx_srtt_ = (7 * rx_srtt_ + rtt) / 8; 424 | if (rx_srtt_ < 1) 425 | rx_srtt_ = 1; 426 | } 427 | 428 | var rto = rx_srtt_ + _imax_(interval_, (UInt32)(4 * rx_rttval_)); 429 | rx_rto_ = (Int32)_ibound_((UInt32)rx_minrto_, (UInt32)rto, IKCP_RTO_MAX); 430 | } 431 | 432 | void ShrinkBuf() 433 | { 434 | var node = snd_buf_.First; 435 | if (node != null) 436 | { 437 | var seg = node.Value; 438 | snd_una_ = seg.sn; 439 | } 440 | else 441 | { 442 | snd_una_ = snd_nxt_; 443 | } 444 | } 445 | 446 | void ParseACK(UInt32 sn) 447 | { 448 | if (_itimediff(sn, snd_una_) < 0 || _itimediff(sn, snd_nxt_) >= 0) 449 | return; 450 | 451 | LinkedListNode next = null; 452 | for (var node = snd_buf_.First; node != null; node = next) 453 | { 454 | var seg = node.Value; 455 | next = node.Next; 456 | if (sn == seg.sn) 457 | { 458 | snd_buf_.Remove(node); 459 | nsnd_buf_--; 460 | break; 461 | } 462 | if (_itimediff(sn, seg.sn) < 0) 463 | break; 464 | } 465 | } 466 | 467 | void ParseUNA(UInt32 una) 468 | { 469 | LinkedListNode next = null; 470 | for (var node = snd_buf_.First; node != null; node = next) 471 | { 472 | var seg = node.Value; 473 | next = node.Next; 474 | if (_itimediff(una, seg.sn) > 0) 475 | { 476 | snd_buf_.Remove(node); 477 | nsnd_buf_--; 478 | } 479 | else 480 | { 481 | break; 482 | } 483 | } 484 | } 485 | 486 | void ParseFastACK(UInt32 sn) 487 | { 488 | if (_itimediff(sn, snd_una_) < 0 || _itimediff(sn, snd_nxt_) >= 0) 489 | return; 490 | 491 | LinkedListNode next = null; 492 | for (var node = snd_buf_.First; node != null; node = next) 493 | { 494 | var seg = node.Value; 495 | next = node.Next; 496 | if (_itimediff(sn, seg.sn) < 0) 497 | { 498 | break; 499 | } 500 | else if (sn != seg.sn) 501 | { 502 | seg.faskack++; 503 | } 504 | } 505 | } 506 | 507 | // ack append 508 | void ACKPush(UInt32 sn, UInt32 ts) 509 | { 510 | var newsize = ackcount_ + 1; 511 | if (newsize > ackblock_) 512 | { 513 | UInt32 newblock = 8; 514 | for (; newblock < newsize; newblock <<= 1) 515 | ; 516 | 517 | var acklist = new UInt32[newblock * 2]; 518 | if (acklist_ != null) 519 | { 520 | for (var i = 0; i < ackcount_; i++) 521 | { 522 | acklist[i * 2] = acklist_[i * 2]; 523 | acklist[i * 2 + 1] = acklist_[i * 2 + 1]; 524 | } 525 | } 526 | acklist_ = acklist; 527 | ackblock_ = newblock; 528 | } 529 | acklist_[ackcount_ * 2] = sn; 530 | acklist_[ackcount_ * 2 + 1] = ts; 531 | ackcount_++; 532 | } 533 | 534 | void ACKGet(int pos, ref UInt32 sn, ref UInt32 ts) 535 | { 536 | sn = acklist_[pos * 2]; 537 | ts = acklist_[pos * 2 + 1]; 538 | } 539 | 540 | // parse data 541 | void ParseData(Segment newseg) 542 | { 543 | UInt32 sn = newseg.sn; 544 | int repeat = 0; 545 | 546 | if (_itimediff(sn, rcv_nxt_ + rcv_wnd_) >= 0 || 547 | _itimediff(sn, rcv_nxt_) < 0) 548 | { 549 | return; 550 | } 551 | 552 | LinkedListNode node = null; 553 | LinkedListNode prev = null; 554 | for (node = rcv_buf_.Last; node != null; node = prev) 555 | { 556 | var seg = node.Value; 557 | prev = node.Previous; 558 | if (seg.sn == sn) 559 | { 560 | repeat = 1; 561 | break; 562 | } 563 | if (_itimediff(sn, seg.sn) > 0) 564 | { 565 | break; 566 | } 567 | } 568 | if (repeat == 0) 569 | { 570 | if (node != null) 571 | { 572 | rcv_buf_.AddAfter(node, newseg); 573 | } 574 | else 575 | { 576 | rcv_buf_.AddFirst(newseg); 577 | } 578 | nrcv_buf_++; 579 | } 580 | 581 | // move available data from rcv_buf -> rcv_queue 582 | while (rcv_buf_.Count > 0) 583 | { 584 | node = rcv_buf_.First; 585 | var seg = node.Value; 586 | if (seg.sn == rcv_nxt_ && nrcv_que_ < rcv_wnd_) 587 | { 588 | rcv_buf_.Remove(node); 589 | nrcv_buf_--; 590 | rcv_queue_.AddLast(node); 591 | nrcv_que_++; 592 | rcv_nxt_++; 593 | } 594 | else 595 | { 596 | break; 597 | } 598 | } 599 | } 600 | 601 | // when you received a low level packet (eg. UDP packet), call it 602 | public int Input(byte[] data, int offset, int size) 603 | { 604 | UInt32 maxack = 0; 605 | int flag = 0; 606 | 607 | Log(IKCP_LOG_INPUT, "[RI] {0} bytes", size); 608 | 609 | if (data == null || size < IKCP_OVERHEAD) 610 | return -1; 611 | 612 | while (true) 613 | { 614 | if (size < IKCP_OVERHEAD) 615 | break; 616 | 617 | UInt32 conv = ikcp_decode32u(data, ref offset); 618 | if (conv_ != conv) 619 | return -1; 620 | UInt32 cmd = ikcp_decode8u(data, ref offset); 621 | UInt32 frg = ikcp_decode8u(data, ref offset); 622 | UInt32 wnd = ikcp_decode16u(data, ref offset); 623 | UInt32 ts = ikcp_decode32u(data, ref offset); 624 | UInt32 sn = ikcp_decode32u(data, ref offset); 625 | UInt32 una = ikcp_decode32u(data, ref offset); 626 | UInt32 len = ikcp_decode32u(data, ref offset); 627 | 628 | size -= IKCP_OVERHEAD; 629 | if (size < len) 630 | return -2; 631 | 632 | if (cmd != IKCP_CMD_PUSH && cmd != IKCP_CMD_ACK && 633 | cmd != IKCP_CMD_WASK && cmd != IKCP_CMD_WINS) 634 | return -3; 635 | 636 | rmt_wnd_ = wnd; 637 | ParseUNA(una); 638 | ShrinkBuf(); 639 | 640 | if (cmd == IKCP_CMD_ACK) 641 | { 642 | if (_itimediff(current_, ts) >= 0) 643 | { 644 | UpdateACK(_itimediff(current_, ts)); 645 | } 646 | ParseACK(sn); 647 | ShrinkBuf(); 648 | if (flag == 0) 649 | { 650 | flag = 1; 651 | maxack = sn; 652 | } 653 | else 654 | { 655 | if (_itimediff(sn, maxack) > 0) 656 | { 657 | maxack = sn; 658 | } 659 | } 660 | Log(IKCP_LOG_IN_DATA, "input ack: sn={0} rtt={1} rto={2}", 661 | sn, _itimediff(current_, ts), rx_rto_); 662 | } 663 | else if (cmd == IKCP_CMD_PUSH) 664 | { 665 | Log(IKCP_LOG_IN_DATA, "input psh: sn={0} ts={1}", sn, ts); 666 | if (_itimediff(sn, rcv_nxt_ + rcv_wnd_) < 0) 667 | { 668 | ACKPush(sn, ts); 669 | if (_itimediff(sn, rcv_nxt_) >= 0) 670 | { 671 | var seg = new Segment((int)len); 672 | seg.conv = conv; 673 | seg.cmd = cmd; 674 | seg.frg = frg; 675 | seg.wnd = wnd; 676 | seg.ts = ts; 677 | seg.sn = sn; 678 | seg.una = una; 679 | if (len > 0) 680 | { 681 | Buffer.BlockCopy(data, offset, seg.data, 0, (int)len); 682 | } 683 | ParseData(seg); 684 | } 685 | } 686 | } 687 | else if (cmd == IKCP_CMD_WASK) 688 | { 689 | // ready to send back IKCP_CMD_WINS in ikcp_flush 690 | // tell remote my window size 691 | probe_ |= IKCP_ASK_TELL; 692 | Log(IKCP_LOG_IN_PROBE, "input probe"); 693 | } 694 | else if (cmd == IKCP_CMD_WINS) 695 | { 696 | // do nothing 697 | Log(IKCP_LOG_IN_WINS, "input wins: {0}", wnd); 698 | } 699 | else 700 | { 701 | return -3; 702 | } 703 | 704 | offset += (int)len; 705 | size -= (int)len; 706 | } 707 | 708 | if (flag != 0) 709 | { 710 | ParseFastACK(maxack); 711 | } 712 | 713 | UInt32 unack = snd_una_; 714 | if (_itimediff(snd_una_, unack) > 0) 715 | { 716 | if (cwnd_ < rmt_wnd_) 717 | { 718 | if (cwnd_ < ssthresh_) 719 | { 720 | cwnd_++; 721 | incr_ += mss_; 722 | } 723 | else 724 | { 725 | if (incr_ < mss_) 726 | incr_ = mss_; 727 | incr_ += (mss_ * mss_) / incr_ + (mss_ / 16); 728 | if ((cwnd_ + 1) * mss_ <= incr_) 729 | cwnd_++; 730 | } 731 | if (cwnd_ > rmt_wnd_) 732 | { 733 | cwnd_ = rmt_wnd_; 734 | incr_ = rmt_wnd_ * mss_; 735 | } 736 | } 737 | } 738 | 739 | return 0; 740 | } 741 | 742 | int WndUnused() 743 | { 744 | if (nrcv_que_ < rcv_wnd_) 745 | return (int)(rcv_wnd_ - nrcv_que_); 746 | return 0; 747 | } 748 | 749 | // flush pending data 750 | void Flush() 751 | { 752 | int change = 0; 753 | int lost = 0; 754 | int offset = 0; 755 | 756 | // 'ikcp_update' haven't been called. 757 | if (updated_ == 0) 758 | return; 759 | 760 | var seg = new Segment 761 | { 762 | conv = conv_, 763 | cmd = IKCP_CMD_ACK, 764 | wnd = (UInt32)WndUnused(), 765 | una = rcv_nxt_, 766 | }; 767 | 768 | // flush acknowledges 769 | int count = (int)ackcount_; 770 | for (int i = 0; i < count; i++) 771 | { 772 | if ((offset + IKCP_OVERHEAD) > mtu_) 773 | { 774 | output_(buffer_, offset, user_); 775 | offset = 0; 776 | } 777 | ACKGet(i, ref seg.sn, ref seg.ts); 778 | seg.Encode(buffer_, ref offset); 779 | } 780 | 781 | ackcount_ = 0; 782 | 783 | // probe window size (if remote window size equals zero) 784 | if (rmt_wnd_ == 0) 785 | { 786 | if (probe_wait_ == 0) 787 | { 788 | probe_wait_ = IKCP_PROBE_INIT; 789 | ts_probe_ = current_ + probe_wait_; 790 | } 791 | else 792 | { 793 | if (_itimediff(current_, ts_probe_) >= 0) 794 | { 795 | if (probe_wait_ < IKCP_PROBE_INIT) 796 | probe_wait_ = IKCP_PROBE_INIT; 797 | probe_wait_ += probe_wait_ / 2; 798 | if (probe_wait_ > IKCP_PROBE_LIMIT) 799 | probe_wait_ = IKCP_PROBE_LIMIT; 800 | ts_probe_ = current_ + probe_wait_; 801 | probe_ |= IKCP_ASK_SEND; 802 | } 803 | } 804 | } 805 | else 806 | { 807 | ts_probe_ = 0; 808 | probe_wait_ = 0; 809 | } 810 | 811 | // flush window probing commands 812 | if ((probe_ & IKCP_ASK_SEND) > 0) 813 | { 814 | seg.cmd = IKCP_CMD_WASK; 815 | if ((offset + IKCP_OVERHEAD) > mtu_) 816 | { 817 | output_(buffer_, offset, user_); 818 | offset = 0; 819 | } 820 | seg.Encode(buffer_, ref offset); 821 | } 822 | 823 | // flush window probing commands 824 | if ((probe_ & IKCP_ASK_TELL) > 0) 825 | { 826 | seg.cmd = IKCP_CMD_WINS; 827 | if ((offset + IKCP_OVERHEAD) > mtu_) 828 | { 829 | output_(buffer_, offset, user_); 830 | offset = 0; 831 | } 832 | seg.Encode(buffer_, ref offset); 833 | } 834 | 835 | probe_ = 0; 836 | 837 | // calculate window size 838 | UInt32 cwnd = _imin_(snd_wnd_, rmt_wnd_); 839 | if (nocwnd_ == 0) 840 | cwnd = _imin_(cwnd_, cwnd); 841 | 842 | // move data from snd_queue to snd_buf 843 | while (_itimediff(snd_nxt_, snd_una_ + cwnd) < 0) 844 | { 845 | if (snd_queue_.Count == 0) 846 | break; 847 | 848 | var node = snd_queue_.First; 849 | var newseg = node.Value; 850 | snd_queue_.Remove(node); 851 | snd_buf_.AddLast(node); 852 | nsnd_que_--; 853 | nsnd_buf_++; 854 | 855 | newseg.conv = conv_; 856 | newseg.cmd = IKCP_CMD_PUSH; 857 | newseg.wnd = seg.wnd; 858 | newseg.ts = current_; 859 | newseg.sn = snd_nxt_++; 860 | newseg.una = rcv_nxt_; 861 | newseg.resendts = current_; 862 | newseg.rto = (UInt32)rx_rto_; 863 | newseg.faskack = 0; 864 | newseg.xmit = 0; 865 | } 866 | 867 | // calculate resent 868 | UInt32 resent = (fastresend_ > 0 ? (UInt32)fastresend_ : 0xffffffff); 869 | UInt32 rtomin = (nodelay_ == 0 ? (UInt32)(rx_rto_ >> 3) : 0); 870 | 871 | // flush data segments 872 | for (var node = snd_buf_.First; node != null; node = node.Next) 873 | { 874 | var segment = node.Value; 875 | int needsend = 0; 876 | if (segment.xmit == 0) 877 | { 878 | needsend = 1; 879 | segment.xmit++; 880 | segment.rto = (UInt32)rx_rto_; 881 | segment.resendts = current_ + segment.rto + rtomin; 882 | } 883 | else if (_itimediff(current_, segment.resendts) >= 0) 884 | { 885 | needsend = 1; 886 | segment.xmit++; 887 | xmit_++; 888 | if (nodelay_ == 0) 889 | segment.rto += (UInt32)rx_rto_; 890 | else 891 | segment.rto += (UInt32)rx_rto_ / 2; 892 | segment.resendts = current_ + segment.rto; 893 | lost = 1; 894 | } 895 | else if (segment.faskack >= resent) 896 | { 897 | needsend = 1; 898 | segment.xmit++; 899 | segment.faskack = 0; 900 | segment.resendts = current_ + segment.rto; 901 | change++; 902 | } 903 | 904 | if (needsend > 0) 905 | { 906 | segment.ts = current_; 907 | segment.wnd = seg.wnd; 908 | segment.una = rcv_nxt_; 909 | 910 | int need = IKCP_OVERHEAD; 911 | if (segment.data != null) 912 | need += segment.data.Length; 913 | 914 | if (offset + need > mtu_) 915 | { 916 | output_(buffer_, offset, user_); 917 | offset = 0; 918 | } 919 | segment.Encode(buffer_, ref offset); 920 | if (segment.data.Length > 0) 921 | { 922 | Buffer.BlockCopy(segment.data, 0, buffer_, offset, segment.data.Length); 923 | offset += segment.data.Length; 924 | } 925 | if (segment.xmit >= dead_link_) 926 | state_ = 0xffffffff; 927 | } 928 | } 929 | 930 | // flush remain segments 931 | if (offset > 0) 932 | { 933 | output_(buffer_, offset, user_); 934 | offset = 0; 935 | } 936 | 937 | // update ssthresh 938 | if (change > 0) 939 | { 940 | UInt32 inflight = snd_nxt_ - snd_una_; 941 | ssthresh_ = inflight / 2; 942 | if (ssthresh_ < IKCP_THRESH_MIN) 943 | ssthresh_ = IKCP_THRESH_MIN; 944 | cwnd_ = ssthresh_ + resent; 945 | incr_ = cwnd_ * mss_; 946 | } 947 | 948 | if (lost > 0) 949 | { 950 | ssthresh_ = cwnd / 2; 951 | if (ssthresh_ < IKCP_THRESH_MIN) 952 | ssthresh_ = IKCP_THRESH_MIN; 953 | cwnd_ = 1; 954 | incr_ = mss_; 955 | } 956 | 957 | if (cwnd_ < 1) 958 | { 959 | cwnd_ = 1; 960 | incr_ = mss_; 961 | } 962 | } 963 | 964 | // update state (call it repeatedly, every 10ms-100ms), or you can ask 965 | // ikcp_check when to call it again (without ikcp_input/_send calling). 966 | // 'current' - current timestamp in millisec. 967 | public void Update(UInt32 current) 968 | { 969 | current_ = current; 970 | if (updated_ == 0) 971 | { 972 | updated_ = 1; 973 | ts_flush_ = current; 974 | } 975 | 976 | Int32 slap = _itimediff(current_, ts_flush_); 977 | if (slap >= 10000 || slap < -10000) 978 | { 979 | ts_flush_ = current; 980 | slap = 0; 981 | } 982 | 983 | if (slap >= 0) 984 | { 985 | ts_flush_ += interval_; 986 | if (_itimediff(current_, ts_flush_) >= 0) 987 | ts_flush_ = current_ + interval_; 988 | 989 | Flush(); 990 | } 991 | } 992 | 993 | // Determine when should you invoke ikcp_update: 994 | // returns when you should invoke ikcp_update in millisec, if there 995 | // is no ikcp_input/_send calling. you can call ikcp_update in that 996 | // time, instead of call update repeatly. 997 | // Important to reduce unnacessary ikcp_update invoking. use it to 998 | // schedule ikcp_update (eg. implementing an epoll-like mechanism, 999 | // or optimize ikcp_update when handling massive kcp connections) 1000 | public UInt32 Check(UInt32 current) 1001 | { 1002 | UInt32 ts_flush = ts_flush_; 1003 | Int32 tm_flush = 0x7fffffff; 1004 | Int32 tm_packet = 0x7fffffff; 1005 | 1006 | if (updated_ == 0) 1007 | return current; 1008 | 1009 | if (_itimediff(current, ts_flush) >= 10000 || 1010 | _itimediff(current, ts_flush) < -10000) 1011 | { 1012 | ts_flush = current; 1013 | } 1014 | 1015 | if (_itimediff(current, ts_flush) >= 0) 1016 | return current; 1017 | 1018 | tm_flush = _itimediff(ts_flush, current); 1019 | 1020 | for (var node = snd_buf_.First; node != null; node = node.Next) 1021 | { 1022 | var seg = node.Value; 1023 | Int32 diff = _itimediff(seg.resendts, current); 1024 | if (diff <= 0) 1025 | return current; 1026 | 1027 | if (diff < tm_packet) 1028 | tm_packet = diff; 1029 | } 1030 | 1031 | UInt32 minimal = (UInt32)(tm_packet < tm_flush ? tm_packet : tm_flush); 1032 | if (minimal >= interval_) 1033 | minimal = interval_; 1034 | 1035 | return current + minimal; 1036 | } 1037 | 1038 | // change MTU size, default is 1400 1039 | public int SetMTU(int mtu) 1040 | { 1041 | if (mtu < 50 || mtu < IKCP_OVERHEAD) 1042 | return -1; 1043 | 1044 | var buffer = new byte[(mtu + IKCP_OVERHEAD) * 3]; 1045 | mtu_ = (UInt32)mtu; 1046 | mss_ = mtu_ - IKCP_OVERHEAD; 1047 | buffer_ = buffer; 1048 | return 0; 1049 | } 1050 | 1051 | public int Interval(int interval) 1052 | { 1053 | if (interval > 5000) 1054 | interval = 5000; 1055 | else if (interval < 10) 1056 | interval = 10; 1057 | 1058 | interval_ = (UInt32)interval; 1059 | return 0; 1060 | } 1061 | 1062 | // fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) 1063 | // nodelay: 0:disable(default), 1:enable 1064 | // interval: internal update timer interval in millisec, default is 100ms 1065 | // resend: 0:disable fast resend(default), 1:enable fast resend 1066 | // nc: 0:normal congestion control(default), 1:disable congestion control 1067 | public int NoDelay(int nodelay, int interval, int resend, int nc) 1068 | { 1069 | if (nodelay >= 0) 1070 | { 1071 | nodelay_ = (UInt32)nodelay; 1072 | if (nodelay > 0) 1073 | { 1074 | rx_minrto_ = IKCP_RTO_NDL; 1075 | } 1076 | else 1077 | { 1078 | rx_minrto_ = IKCP_RTO_MIN; 1079 | } 1080 | } 1081 | if (interval >= 0) 1082 | { 1083 | if (interval > 5000) 1084 | interval = 5000; 1085 | else if (interval < 10) 1086 | interval = 10; 1087 | 1088 | interval_ = (UInt32)interval; 1089 | } 1090 | 1091 | if (resend >= 0) 1092 | fastresend_ = resend; 1093 | 1094 | if (nc >= 0) 1095 | nocwnd_ = nc; 1096 | 1097 | return 0; 1098 | } 1099 | 1100 | // set maximum window size: sndwnd=32, rcvwnd=32 by default 1101 | public int WndSize(int sndwnd, int rcvwnd) 1102 | { 1103 | if (sndwnd > 0) 1104 | snd_wnd_ = (UInt32)sndwnd; 1105 | if (rcvwnd > 0) 1106 | rcv_wnd_ = (UInt32)rcvwnd; 1107 | return 0; 1108 | } 1109 | 1110 | // get how many packet is waiting to be sent 1111 | public int WaitSnd() 1112 | { 1113 | return (int)(nsnd_buf_ + nsnd_que_); 1114 | } 1115 | 1116 | // read conv 1117 | public UInt32 GetConv() 1118 | { 1119 | return conv_; 1120 | } 1121 | 1122 | public UInt32 GetState() 1123 | { 1124 | return state_; 1125 | } 1126 | 1127 | public void SetMinRTO(int minrto) 1128 | { 1129 | rx_minrto_ = minrto; 1130 | } 1131 | 1132 | public void SetFastResend(int resend) 1133 | { 1134 | fastresend_ = resend; 1135 | } 1136 | 1137 | void Log(int mask, string format, params object[] args) 1138 | { 1139 | // Console.WriteLine(mask + String.Format(format, args)); 1140 | } 1141 | } 1142 | } 1143 | -------------------------------------------------------------------------------- /Source/Network/LatencySimulator.cs: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/qi7chen/kcp-dotnet/d68701cebf55be51f398efddf1f73821ef712666/Source/Network/LatencySimulator.cs -------------------------------------------------------------------------------- /Source/Network/Socket.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 simon@qchen.fun All rights reserved. 2 | // Distributed under the terms and conditions of the MIT License. 3 | // See accompanying files LICENSE. 4 | 5 | using System; 6 | using System.IO; 7 | using System.Net; 8 | using System.Net.Sockets; 9 | using System.Collections.Generic; 10 | using System.Diagnostics; 11 | 12 | namespace Network 13 | { 14 | internal struct UDPSocketStateObject 15 | { 16 | internal Socket socket; 17 | internal EndPoint remote; 18 | internal KCPSocket obj; 19 | internal byte[] buf; 20 | } 21 | 22 | public class KCPSocket 23 | { 24 | private Socket socket; 25 | private EndPoint remoteEnd; 26 | private KCP kcp; 27 | 28 | // recv buffer 29 | private byte[] udpRcvBuf; 30 | private byte[] kcpRcvBuf; 31 | private Queue rcvQueue; 32 | private Queue forground; 33 | private Queue errors; 34 | 35 | // time-out control 36 | private Int64 lastRecvTime = 0; 37 | private int recvTimeoutSec = 0; 38 | 39 | private bool needUpdate = false; 40 | private UInt32 nextUpdateTime = 0; 41 | 42 | Action handler; 43 | 44 | 45 | public KCPSocket(int timeoutSec = 60) 46 | { 47 | recvTimeoutSec = timeoutSec; 48 | udpRcvBuf = new byte[(KCP.IKCP_MTU_DEF + KCP.IKCP_OVERHEAD) * 3]; 49 | kcpRcvBuf = new byte[(KCP.IKCP_MTU_DEF + KCP.IKCP_OVERHEAD) * 3]; 50 | rcvQueue = new Queue(64); 51 | forground = new Queue(64); 52 | errors = new Queue(8); 53 | } 54 | 55 | public void SetHandler(Action cb) 56 | { 57 | handler = cb; 58 | } 59 | 60 | public KCP GetKCPObject() 61 | { 62 | return kcp; 63 | } 64 | 65 | public void Connect(UInt32 conv, string host, UInt16 port) 66 | { 67 | var addr = IPAddress.Parse(host); 68 | remoteEnd = new IPEndPoint(addr, port); 69 | socket = new Socket(addr.AddressFamily, SocketType.Dgram, ProtocolType.Udp); 70 | socket.Connect(remoteEnd); 71 | 72 | kcp = new KCP(conv, this); 73 | kcp.SetOutput(OutputKCP); 74 | 75 | // fast mode 76 | kcp.NoDelay(1, 10, 2, 1); 77 | kcp.WndSize(128, 128); 78 | } 79 | 80 | public void Close() 81 | { 82 | socket.Close(); 83 | kcp.Release(); 84 | } 85 | 86 | void OutputKCP(byte[] data, int size, object ud) 87 | { 88 | UDPSendTo(data, 0, size); 89 | } 90 | 91 | public void PushToRecvQueue(byte[] data) 92 | { 93 | lock(rcvQueue) 94 | { 95 | rcvQueue.Enqueue(data); 96 | } 97 | } 98 | 99 | // if `rcvqueue` is not empty, swap it with `forground` 100 | public Queue SwitchRecvQueue() 101 | { 102 | lock(rcvQueue) 103 | { 104 | if (rcvQueue.Count > 0) 105 | { 106 | var tmp = rcvQueue; 107 | rcvQueue = forground; 108 | forground = tmp; 109 | } 110 | } 111 | return forground; 112 | } 113 | 114 | // dirty write 115 | public void PushError(Exception ex) 116 | { 117 | lock(errors) 118 | { 119 | errors.Enqueue(ex); 120 | } 121 | } 122 | 123 | // dirty read 124 | public Exception GetError() 125 | { 126 | Exception ex = null; 127 | lock(errors) 128 | { 129 | if (errors.Count > 0) 130 | { 131 | ex = errors.Dequeue(); 132 | } 133 | } 134 | return ex; 135 | } 136 | 137 | public void StartRead() 138 | { 139 | UDPSocketStateObject state = new UDPSocketStateObject 140 | { 141 | obj = this, 142 | socket = socket, 143 | buf = udpRcvBuf, 144 | remote = remoteEnd, 145 | }; 146 | PostReadRequest(state); 147 | } 148 | 149 | public void Send(byte[] data, int offset, int count) 150 | { 151 | kcp.Send(data, offset, count); 152 | needUpdate = true; 153 | } 154 | 155 | void PostReadRequest(UDPSocketStateObject state) 156 | { 157 | socket.BeginReceiveFrom(state.buf, 0, state.buf.Length, 0, ref remoteEnd, 158 | new AsyncCallback(RecvCallback), state); 159 | } 160 | 161 | public static void RecvCallback(IAsyncResult ar) 162 | { 163 | UDPSocketStateObject state = (UDPSocketStateObject)ar.AsyncState; 164 | try 165 | { 166 | int bytesRead = state.socket.EndReceiveFrom(ar, ref state.remote); 167 | if (bytesRead <= 0) 168 | { 169 | var ex = new EndOfStreamException("socket closed by peer"); 170 | state.obj.PushError(ex); 171 | return; 172 | } 173 | var data = new byte[bytesRead]; 174 | Buffer.BlockCopy(state.buf, 0, data, 0, bytesRead); 175 | state.obj.PushToRecvQueue(data); 176 | state.obj.PostReadRequest(state); 177 | } 178 | catch (SocketException ex) 179 | { 180 | state.obj.PushError(ex); 181 | } 182 | } 183 | 184 | void UDPSendTo(byte[] data, int offset, int size) 185 | { 186 | UDPSocketStateObject state = new UDPSocketStateObject 187 | { 188 | obj = this, 189 | socket = socket, 190 | }; 191 | socket.BeginSendTo(data, offset, size, 0, remoteEnd, new AsyncCallback(WriteCallback), state); 192 | } 193 | 194 | public static void WriteCallback(IAsyncResult ar) 195 | { 196 | UDPSocketStateObject state = (UDPSocketStateObject)ar.AsyncState; 197 | try 198 | { 199 | state.socket.EndSendTo(ar); 200 | } 201 | catch(SocketException ex) 202 | { 203 | state.obj.PushError(ex); 204 | } 205 | } 206 | 207 | void CheckTimeout(UInt32 current) 208 | { 209 | if (lastRecvTime == 0) 210 | { 211 | lastRecvTime = current; 212 | } 213 | if (current - lastRecvTime > recvTimeoutSec * 1000) 214 | { 215 | var ex = new TimeoutException("socket recv timeout"); 216 | PushError(ex); 217 | } 218 | } 219 | 220 | public void ProcessRecv(UInt32 current) 221 | { 222 | var queue = SwitchRecvQueue(); 223 | while (queue.Count > 0) 224 | { 225 | lastRecvTime = current; 226 | var data = queue.Dequeue(); 227 | int r = kcp.Input(data, 0, data.Length); 228 | Debug.Assert(r >= 0); 229 | needUpdate = true; 230 | while (true) 231 | { 232 | var size = kcp.PeekSize(); 233 | if (size > 0) 234 | { 235 | r = kcp.Recv(kcpRcvBuf, 0, kcpRcvBuf.Length); 236 | if (r <= 0) 237 | { 238 | break; 239 | } 240 | handler(kcpRcvBuf, size); 241 | } 242 | else 243 | { 244 | break; 245 | } 246 | } 247 | } 248 | } 249 | 250 | public void Update(UInt32 current) 251 | { 252 | ProcessRecv(current); 253 | var err = GetError(); 254 | if (err != null) 255 | { 256 | throw err; 257 | } 258 | if (needUpdate || current > nextUpdateTime) 259 | { 260 | kcp.Update(current); 261 | nextUpdateTime = kcp.Check(current); 262 | needUpdate = false; 263 | } 264 | CheckTimeout(current); 265 | } 266 | } 267 | } -------------------------------------------------------------------------------- /Source/Network/Utils.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017simon@qchen.fun All rights reserved. 2 | // Distributed under the terms and conditions of the MIT License. 3 | // See accompanying files LICENSE. 4 | 5 | using System; 6 | 7 | namespace Network 8 | { 9 | public class Utils 10 | { 11 | private static readonly DateTime epoch = new DateTime(1970, 1, 1); 12 | private static readonly DateTime twepoch = new DateTime(2000, 1, 1); 13 | 14 | public static UInt32 iclock() 15 | { 16 | var now = Convert.ToInt64(DateTime.Now.Subtract(twepoch).TotalMilliseconds); 17 | return (UInt32)(now & 0xFFFFFFFF); 18 | } 19 | 20 | public static Int64 LocalUnixTime() 21 | { 22 | return Convert.ToInt64(DateTime.Now.Subtract(epoch).TotalMilliseconds); 23 | } 24 | 25 | // local datetime to unix timestamp 26 | public static Int64 ToUnixTimestamp(DateTime t) 27 | { 28 | var timespan = t.ToUniversalTime().Subtract(epoch); 29 | return (Int64)Math.Truncate(timespan.TotalSeconds); 30 | } 31 | } 32 | } -------------------------------------------------------------------------------- /Source/Program.cs: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 simon@qchen.fun All rights reserved. 2 | // Distributed under the terms and conditions of the MIT License. 3 | // See accompanying files LICENSE. 4 | 5 | using System; 6 | using System.Text; 7 | using System.Threading; 8 | using System.Diagnostics; 9 | using Network; 10 | 11 | namespace UnitTest 12 | { 13 | class Program 14 | { 15 | static LatencySimulator vnet; 16 | 17 | static void udp_output(byte[] data, int size, object ud) 18 | { 19 | int peer = (int)ud; 20 | vnet.Send(peer, data, size); 21 | } 22 | 23 | // 测试用例 24 | static void KCPTest(int mode) 25 | { 26 | // 创建模拟网络:丢包率10%,Rtt 60ms~125ms 27 | vnet = new LatencySimulator(10, 60, 125); 28 | 29 | // 创建两个端点的 kcp对象,第一个参数 conv是会话编号,同一个会话需要相同 30 | // 最后一个是 user参数,用来传递标识 31 | var kcp1 = new KCP(0x11223344, 1); 32 | var kcp2 = new KCP(0x11223344, 2); 33 | 34 | // 设置kcp的下层输出,这里为 udp_output,模拟udp网络输出函数 35 | kcp1.SetOutput(udp_output); 36 | kcp2.SetOutput(udp_output); 37 | 38 | UInt32 current = Utils.iclock(); 39 | UInt32 slap = current + 20; 40 | UInt32 index = 0; 41 | UInt32 next = 0; 42 | Int64 sumrtt = 0; 43 | int count = 0; 44 | int maxrtt = 0; 45 | 46 | // 配置窗口大小:平均延迟200ms,每20ms发送一个包, 47 | // 而考虑到丢包重发,设置最大收发窗口为128 48 | kcp1.WndSize(128, 128); 49 | kcp2.WndSize(128, 128); 50 | 51 | if (mode == 0) // 默认模式 52 | { 53 | kcp1.NoDelay(0, 10, 0, 0); 54 | kcp2.NoDelay(0, 10, 0, 0); 55 | } 56 | else if (mode == 1) // 普通模式,关闭流控等 57 | { 58 | kcp1.NoDelay(0, 10, 0, 1); 59 | kcp2.NoDelay(0, 10, 0, 1); 60 | } 61 | else // 启动快速模式 62 | { 63 | // 第1个参数 nodelay-启用以后若干常规加速将启动 64 | // 第2个参数 interval为内部处理时钟,默认设置为 10ms 65 | // 第3个参数 resend为快速重传指标,设置为2 66 | // 第4个参数 为是否禁用常规流控,这里禁止 67 | kcp1.NoDelay(1, 10, 2, 1); 68 | kcp2.NoDelay(1, 10, 2, 1); 69 | kcp1.SetMinRTO(10); 70 | kcp1.SetFastResend(1); 71 | } 72 | 73 | var buffer = new byte[2000]; 74 | int hr = 0; 75 | UInt32 ts1 = Utils.iclock(); 76 | 77 | while (true) 78 | { 79 | Thread.Sleep(1); 80 | current = Utils.iclock(); 81 | kcp1.Update(current); 82 | kcp2.Update(current); 83 | 84 | // 每隔 20ms,kcp1发送数据 85 | for (; current >= slap; slap += 20) 86 | { 87 | KCP.ikcp_encode32u(buffer, 0, index++); 88 | KCP.ikcp_encode32u(buffer, 4, current); 89 | 90 | // 发送上层协议包 91 | kcp1.Send(buffer, 0, 8); 92 | } 93 | 94 | // 处理虚拟网络:检测是否有udp包从p1->p2 95 | while (true) 96 | { 97 | hr = vnet.Recv(1, buffer, 2000); 98 | if (hr < 0) 99 | break; 100 | 101 | // 如果 p2收到udp,则作为下层协议输入到kcp2 102 | hr = kcp2.Input(buffer, 0, hr); 103 | Debug.Assert(hr >= 0); 104 | } 105 | 106 | // 处理虚拟网络:检测是否有udp包从p2->p1 107 | while (true) 108 | { 109 | hr = vnet.Recv(0, buffer, 2000); 110 | if (hr < 0) 111 | break; 112 | 113 | // 如果 p1收到udp,则作为下层协议输入到kcp1 114 | hr = kcp1.Input(buffer, 0, hr); 115 | Debug.Assert(hr >= 0); 116 | } 117 | 118 | // kcp2接收到任何包都返回回去 119 | while (true) 120 | { 121 | hr = kcp2.Recv(buffer, 0, 10); 122 | if (hr < 0) 123 | break; 124 | 125 | // 如果收到包就回射 126 | hr = kcp2.Send(buffer, 0, hr); 127 | Debug.Assert(hr >= 0); 128 | } 129 | 130 | // kcp1收到kcp2的回射数据 131 | while (true) 132 | { 133 | hr = kcp1.Recv(buffer, 0, 10); 134 | if (hr < 0) // 没有收到包就退出 135 | break; 136 | 137 | int offset = 0; 138 | UInt32 sn = KCP.ikcp_decode32u(buffer, ref offset); 139 | UInt32 ts = KCP.ikcp_decode32u(buffer, ref offset); 140 | UInt32 rtt = current - ts; 141 | 142 | if (sn != next) 143 | { 144 | // 如果收到的包不连续 145 | Console.WriteLine(String.Format("ERROR sn {0}<->{1}", count, next)); 146 | return; 147 | } 148 | next++; 149 | sumrtt += rtt; 150 | count++; 151 | if (rtt > maxrtt) 152 | maxrtt = (int)rtt; 153 | 154 | Console.WriteLine(String.Format("[RECV] mode={0} sn={1} rtt={2}", mode, sn, rtt)); 155 | } 156 | if (next > 1000) 157 | break; 158 | } 159 | ts1 = Utils.iclock() - ts1; 160 | var names = new string[3] { "default", "normal", "fast" }; 161 | Console.WriteLine("{0} mode result ({1}ms):", names[mode], ts1); 162 | Console.WriteLine("avgrtt={0} maxrtt={1} tx={2}", sumrtt / count, maxrtt, vnet.tx1); 163 | Console.WriteLine("Press any key to next..."); 164 | Console.Read(); 165 | } 166 | 167 | 168 | static void Main(string[] args) 169 | { 170 | string testcase = "kcp"; 171 | if (args.Length > 0) 172 | { 173 | testcase = args[0]; 174 | } 175 | 176 | if (testcase == "kcp") 177 | { 178 | KCPTest(0); // 默认模式,类似 TCP:正常模式,无快速重传,常规流控 179 | KCPTest(1); // 普通模式,关闭流控等 180 | KCPTest(2); // 快速模式,所有开关都打开,且关闭流控 181 | } 182 | else if (testcase == "socket") 183 | { 184 | TestSocket(); 185 | } 186 | } 187 | 188 | 189 | static void TestSocket() 190 | { 191 | UInt32 conv = 0x12345678; 192 | var counter = 1; 193 | var originText = "a quick brown fox jumps over the lazy dog"; 194 | var rawbytes = Encoding.UTF8.GetBytes(String.Format("{0} {1}", originText, counter)); 195 | 196 | KCPSocket sock = new KCPSocket(); 197 | sock.SetHandler((byte[] data, int size) => 198 | { 199 | Console.WriteLine(Encoding.UTF8.GetString(data, 0, size)); 200 | 201 | Thread.Sleep(500); 202 | rawbytes = Encoding.UTF8.GetBytes(String.Format("{0} {1}", originText, ++counter)); 203 | sock.Send(rawbytes, 0, rawbytes.Length); 204 | }); 205 | 206 | sock.Connect(conv, "127.0.0.1", 9527); 207 | sock.StartRead(); 208 | sock.Send(rawbytes, 0, rawbytes.Length); 209 | 210 | while (true) 211 | { 212 | Thread.Sleep(100); 213 | try 214 | { 215 | sock.Update(Utils.iclock()); 216 | } 217 | catch(Exception ex) 218 | { 219 | sock.Close(); 220 | Console.WriteLine("Exception: {0}", ex); 221 | break; 222 | } 223 | } 224 | } 225 | } 226 | } 227 | -------------------------------------------------------------------------------- /Source/server.go: -------------------------------------------------------------------------------- 1 | // Copyright (C) 2017 simon@qchen.fun All rights reserved. 2 | // Distributed under the terms and conditions of the MIT License. 3 | // See accompanying files LICENSE. 4 | 5 | package main 6 | 7 | import ( 8 | "log" 9 | "net" 10 | "os" 11 | "time" 12 | 13 | "github.com/xtaci/kcp-go" 14 | ) 15 | 16 | func main() { 17 | var host = "127.0.0.1:9527" 18 | if len(os.Args) > 1 { 19 | host = os.Args[1] 20 | } 21 | listener, err := kcp.Listen(host) 22 | if err != nil { 23 | log.Fatalf("Listen: %v", err) 24 | } 25 | log.Printf("start listen at %s\n", host) 26 | for { 27 | conn, err := listener.Accept() 28 | if err != nil { 29 | log.Fatalf("Accept: %v", err) 30 | } 31 | go handleConn(conn) 32 | } 33 | } 34 | 35 | // Echo every thing back 36 | func handleConn(conn net.Conn) { 37 | defer conn.Close() 38 | var udpConn = conn.(*kcp.UDPSession) 39 | conn.SetReadDeadline(time.Now().Add(60 * time.Second)) 40 | var buffer = make([]byte, 4096) 41 | for { 42 | bytesRead, err := conn.Read(buffer) 43 | if err != nil { 44 | log.Printf("Read: %v", err) 45 | break 46 | } 47 | log.Printf("%d(%v): %s\n", udpConn.GetConv(), udpConn.RemoteAddr(), buffer[:bytesRead]) 48 | conn.Write(buffer[:bytesRead]) 49 | } 50 | } 51 | -------------------------------------------------------------------------------- /kcp.csproj: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Exe 5 | netcoreapp2.0 6 | 7 | 8 | 9 | --------------------------------------------------------------------------------