├── .gitignore ├── KCP ├── ByteBuffer.cs ├── KCP.cs └── UDPSession.cs ├── KcpProject.csproj ├── LICENSE ├── Properties └── AssemblyInfo.cs ├── README.md └── Sample └── Main.cs /.gitignore: -------------------------------------------------------------------------------- 1 | # ---> C Sharp 2 | # Build Folders (you can keep bin if you'd like, to store dlls and pdbs) 3 | [Bb]in/ 4 | [Oo]bj/ 5 | 6 | # mstest test results 7 | TestResults 8 | 9 | ## Ignore Visual Studio temporary files, build results, and 10 | ## files generated by popular Visual Studio add-ons. 11 | 12 | # User-specific files 13 | *.suo 14 | *.user 15 | *.sln.docstates 16 | 17 | # Build results 18 | [Dd]ebug/ 19 | [Rr]elease/ 20 | x64/ 21 | *_i.c 22 | *_p.c 23 | *.ilk 24 | *.obj 25 | *.pch 26 | *.pdb 27 | *.pgc 28 | *.pgd 29 | *.rsp 30 | *.sbr 31 | *.tlb 32 | *.tli 33 | *.tlh 34 | *.tmp 35 | *.log 36 | *.vspscc 37 | *.vssscc 38 | .builds 39 | 40 | # Visual C++ cache files 41 | ipch/ 42 | *.aps 43 | *.ncb 44 | *.opensdf 45 | *.sdf 46 | 47 | # Visual Studio profiler 48 | *.psess 49 | *.vsp 50 | *.vspx 51 | 52 | # Guidance Automation Toolkit 53 | *.gpState 54 | 55 | # ReSharper is a .NET coding add-in 56 | _ReSharper* 57 | 58 | # NCrunch 59 | *.ncrunch* 60 | .*crunch*.local.xml 61 | 62 | # Installshield output folder 63 | [Ee]xpress 64 | 65 | # DocProject is a documentation generator add-in 66 | DocProject/buildhelp/ 67 | DocProject/Help/*.HxT 68 | DocProject/Help/*.HxC 69 | DocProject/Help/*.hhc 70 | DocProject/Help/*.hhk 71 | DocProject/Help/*.hhp 72 | DocProject/Help/Html2 73 | DocProject/Help/html 74 | 75 | # Click-Once directory 76 | publish 77 | 78 | # Publish Web Output 79 | *.Publish.xml 80 | 81 | # NuGet Packages Directory 82 | packages 83 | 84 | # Windows Azure Build Output 85 | csx 86 | *.build.csdef 87 | 88 | # Windows Store app package directory 89 | AppPackages/ 90 | 91 | # Others 92 | [Bb]in 93 | [Oo]bj 94 | sql 95 | TestResults 96 | [Tt]est[Rr]esult* 97 | *.Cache 98 | ClientBin 99 | [Ss]tyle[Cc]op.* 100 | ~$* 101 | *.dbmdl 102 | Generated_Code #added for RIA/Silverlight projects 103 | 104 | # Backup & report files from converting an old project file to a newer 105 | # Visual Studio version. Backup files are not needed, because we have git ;-) 106 | _UpgradeReport_Files/ 107 | Backup*/ 108 | UpgradeLog*.XML 109 | 110 | # ---> VisualStudio 111 | ## Ignore Visual Studio temporary files, build results, and 112 | ## files generated by popular Visual Studio add-ons. 113 | 114 | # User-specific files 115 | *.suo 116 | *.user 117 | *.userosscache 118 | *.sln.docstates 119 | 120 | # User-specific files (MonoDevelop/Xamarin Studio) 121 | *.userprefs 122 | 123 | # Build results 124 | [Dd]ebug/ 125 | [Dd]ebugPublic/ 126 | [Rr]elease/ 127 | [Rr]eleases/ 128 | x64/ 129 | build/ 130 | bld/ 131 | [Bb]in/ 132 | [Oo]bj/ 133 | 134 | # Visual Studio 2015 cache/options directory 135 | .vs/ 136 | # Uncomment if you have tasks that create the project's static files in wwwroot 137 | #wwwroot/ 138 | 139 | # MSTest test Results 140 | [Tt]est[Rr]esult*/ 141 | [Bb]uild[Ll]og.* 142 | 143 | # NUNIT 144 | *.VisualState.xml 145 | TestResult.xml 146 | 147 | # Build Results of an ATL Project 148 | [Dd]ebugPS/ 149 | [Rr]eleasePS/ 150 | dlldata.c 151 | 152 | # DNX 153 | project.lock.json 154 | artifacts/ 155 | 156 | *_i.c 157 | *_p.c 158 | *_i.h 159 | *.ilk 160 | *.obj 161 | *.pch 162 | *.pgc 163 | *.pgd 164 | *.sbr 165 | *.tlb 166 | *.tli 167 | *.tlh 168 | *.tmp 169 | *.tmp_proj 170 | *.log 171 | *.vspscc 172 | *.vssscc 173 | .builds 174 | *.pidb 175 | *.svclog 176 | *.scc 177 | 178 | # Chutzpah Test files 179 | _Chutzpah* 180 | 181 | # Visual C++ cache files 182 | ipch/ 183 | *.aps 184 | *.ncb 185 | *.opensdf 186 | *.sdf 187 | *.cachefile 188 | 189 | # Visual Studio profiler 190 | *.psess 191 | *.vsp 192 | *.vspx 193 | *.sap 194 | 195 | # TFS 2012 Local Workspace 196 | $tf/ 197 | 198 | # Guidance Automation Toolkit 199 | *.gpState 200 | 201 | # ReSharper is a .NET coding add-in 202 | _ReSharper*/ 203 | *.[Rr]e[Ss]harper 204 | *.DotSettings.user 205 | 206 | # JustCode is a .NET coding add-in 207 | .JustCode 208 | 209 | # TeamCity is a build add-in 210 | _TeamCity* 211 | 212 | # DotCover is a Code Coverage Tool 213 | *.dotCover 214 | 215 | # NCrunch 216 | _NCrunch_* 217 | .*crunch*.local.xml 218 | nCrunchTemp_* 219 | 220 | # MightyMoose 221 | *.mm.* 222 | AutoTest.Net/ 223 | 224 | # Web workbench (sass) 225 | .sass-cache/ 226 | 227 | # Installshield output folder 228 | [Ee]xpress/ 229 | 230 | # DocProject is a documentation generator add-in 231 | DocProject/buildhelp/ 232 | DocProject/Help/*.HxT 233 | DocProject/Help/*.HxC 234 | DocProject/Help/*.hhc 235 | DocProject/Help/*.hhk 236 | DocProject/Help/*.hhp 237 | DocProject/Help/Html2 238 | DocProject/Help/html 239 | 240 | # Click-Once directory 241 | publish/ 242 | 243 | # Publish Web Output 244 | *.[Pp]ublish.xml 245 | *.azurePubxml 246 | # TODO: Comment the next line if you want to checkin your web deploy settings 247 | # but database connection strings (with potential passwords) will be unencrypted 248 | *.pubxml 249 | *.publishproj 250 | 251 | # NuGet Packages 252 | *.nupkg 253 | # The packages folder can be ignored because of Package Restore 254 | **/packages/* 255 | # except build/, which is used as an MSBuild target. 256 | !**/packages/build/ 257 | # Uncomment if necessary however generally it will be regenerated when needed 258 | #!**/packages/repositories.config 259 | 260 | # Windows Azure Build Output 261 | csx/ 262 | *.build.csdef 263 | 264 | # Windows Store app package directory 265 | AppPackages/ 266 | 267 | # Visual Studio cache files 268 | # files ending in .cache can be ignored 269 | *.[Cc]ache 270 | # but keep track of directories ending in .cache 271 | !*.[Cc]ache/ 272 | 273 | # Others 274 | ClientBin/ 275 | [Ss]tyle[Cc]op.* 276 | ~$* 277 | *~ 278 | *.dbmdl 279 | *.dbproj.schemaview 280 | *.pfx 281 | *.publishsettings 282 | node_modules/ 283 | orleans.codegen.cs 284 | 285 | # RIA/Silverlight projects 286 | Generated_Code/ 287 | 288 | # Backup & report files from converting an old project file 289 | # to a newer Visual Studio version. Backup files are not needed, 290 | # because we have git ;-) 291 | _UpgradeReport_Files/ 292 | Backup*/ 293 | UpgradeLog*.XML 294 | UpgradeLog*.htm 295 | 296 | # SQL Server files 297 | *.mdf 298 | *.ldf 299 | 300 | # Business Intelligence projects 301 | *.rdl.data 302 | *.bim.layout 303 | *.bim_*.settings 304 | 305 | # Microsoft Fakes 306 | FakesAssemblies/ 307 | 308 | # Node.js Tools for Visual Studio 309 | .ntvs_analysis.dat 310 | 311 | # Visual Studio 6 build log 312 | *.plg 313 | 314 | # Visual Studio 6 workspace options file 315 | *.opt 316 | 317 | # Visual Studio LightSwitch build output 318 | **/*.HTMLClient/GeneratedArtifacts 319 | **/*.DesktopClient/GeneratedArtifacts 320 | **/*.DesktopClient/ModelManifest.xml 321 | **/*.Server/GeneratedArtifacts 322 | **/*.Server/ModelManifest.xml 323 | _Pvt_Extensions 324 | 325 | # ---> Unity 326 | /[Ll]ibrary/ 327 | /[Tt]emp/ 328 | /[Oo]bj/ 329 | /[Bb]uild/ 330 | 331 | # Autogenerated VS/MD solution and project files 332 | *.csproj 333 | *.unityproj 334 | *.sln 335 | *.suo 336 | *.tmp 337 | *.user 338 | *.userprefs 339 | *.pidb 340 | *.booproj 341 | 342 | # Unity3D generated meta files 343 | *.pidb.meta 344 | 345 | # Unity3D Generated File On Crash Reports 346 | sysinfo.txt 347 | 348 | # IL2CPP 349 | SwitchIL2CPPCache 350 | SwitchIL2CPPStats -------------------------------------------------------------------------------- /KCP/ByteBuffer.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | 6 | namespace KcpProject 7 | { 8 | class ByteBuffer : ICloneable 9 | { 10 | //字节缓存区 11 | private byte[] buf; 12 | //读取索引 13 | private int readIndex = 0; 14 | //写入索引 15 | private int writeIndex = 0; 16 | //读取索引标记 17 | private int markReadIndex = 0; 18 | //写入索引标记 19 | private int markWirteIndex = 0; 20 | //缓存区字节数组的长度 21 | private int capacity; 22 | 23 | //对象池 24 | private static List pool = new List(); 25 | private static int poolMaxCount = 200; 26 | 27 | //此对象是否池化 28 | private bool isPool = false; 29 | 30 | /// 31 | /// 构造方法 32 | /// 33 | /// 初始容量 34 | private ByteBuffer(int capacity) 35 | { 36 | this.buf = new byte[capacity]; 37 | this.capacity = capacity; 38 | this.readIndex = 0; 39 | this.writeIndex = 0; 40 | } 41 | 42 | /// 43 | /// 构造方法 44 | /// 45 | /// 初始字节数组 46 | private ByteBuffer(byte[] bytes) 47 | { 48 | this.buf = new byte[bytes.Length]; 49 | Array.Copy(bytes, 0, buf, 0, buf.Length); 50 | this.capacity = buf.Length; 51 | this.readIndex = 0; 52 | this.writeIndex = bytes.Length + 1; 53 | } 54 | 55 | /// 56 | /// 构建一个capacity长度的字节缓存区ByteBuffer对象 57 | /// 58 | /// 初始容量 59 | /// 60 | /// true表示获取一个池化的ByteBuffer对象,池化的对象必须在调用Dispose后才会推入池中,此方法为线程安全的。 61 | /// 当为true时,从池中获取的对象的实际capacity值。 62 | /// 63 | /// ByteBuffer对象 64 | public static ByteBuffer Allocate(int capacity, bool fromPool = false) 65 | { 66 | if (!fromPool) 67 | { 68 | return new ByteBuffer(capacity); 69 | } 70 | lock (pool) 71 | { 72 | ByteBuffer bbuf; 73 | if (pool.Count == 0) 74 | { 75 | bbuf = new ByteBuffer(capacity) 76 | { 77 | isPool = true 78 | }; 79 | return bbuf; 80 | } 81 | int lastIndex = pool.Count - 1; 82 | bbuf = pool[lastIndex]; 83 | pool.RemoveAt(lastIndex); 84 | if (!bbuf.isPool) 85 | { 86 | bbuf.isPool = true; 87 | } 88 | return bbuf; 89 | } 90 | } 91 | 92 | /// 93 | /// 构建一个以bytes为字节缓存区的ByteBuffer对象,一般不推荐使用 94 | /// 95 | /// 初始字节数组 96 | /// 97 | /// true表示获取一个池化的ByteBuffer对象,池化的对象必须在调用Dispose后才会推入池中,此方法为线程安全的。 98 | /// 99 | /// ByteBuffer对象 100 | public static ByteBuffer Allocate(byte[] bytes, bool fromPool = false) 101 | { 102 | if (!fromPool) 103 | { 104 | return new ByteBuffer(bytes); 105 | } 106 | lock (pool) 107 | { 108 | ByteBuffer bbuf; 109 | if (pool.Count == 0) 110 | { 111 | bbuf = new ByteBuffer(bytes) 112 | { 113 | isPool = true 114 | }; 115 | return bbuf; 116 | } 117 | int lastIndex = pool.Count - 1; 118 | bbuf = pool[lastIndex]; 119 | bbuf.WriteBytes(bytes); 120 | pool.RemoveAt(lastIndex); 121 | if (!bbuf.isPool) 122 | { 123 | bbuf.isPool = true; 124 | } 125 | return bbuf; 126 | } 127 | } 128 | 129 | /// 130 | /// 根据value,确定大于此length的最近的2次方数,如length=7,则返回值为8;length=12,则返回16 131 | /// 132 | /// 参考容量 133 | /// 比参考容量大的最接近的2次方数 134 | private int FixLength(int value) 135 | { 136 | if (value == 0) 137 | { 138 | return 1; 139 | } 140 | value--; 141 | value |= value >> 1; 142 | value |= value >> 2; 143 | value |= value >> 4; 144 | value |= value >> 8; 145 | value |= value >> 16; 146 | return value + 1; 147 | //int n = 2; 148 | //int b = 2; 149 | //while (b < length) 150 | //{ 151 | // b = 2 << n; 152 | // n++; 153 | //} 154 | //return b; 155 | } 156 | 157 | /// 158 | /// 翻转字节数组,如果本地字节序列为高字节序列,则进行翻转以转换为低字节序列 159 | /// 160 | /// 待转为高字节序的字节数组 161 | /// 低字节序列的字节数组 162 | private byte[] Flip(byte[] bytes) 163 | { 164 | //if (BitConverter.IsLittleEndian) 165 | //{ 166 | // Array.Reverse(bytes); 167 | //} 168 | return bytes; 169 | } 170 | 171 | /// 172 | /// 确定内部字节缓存数组的大小 173 | /// 174 | /// 当前容量 175 | /// 将来的容量 176 | /// 当前缓冲区的最大容量 177 | private int FixSizeAndReset(int currLen, int futureLen) 178 | { 179 | if (futureLen > currLen) 180 | { 181 | //以原大小的2次方数的两倍确定内部字节缓存区大小 182 | int size = FixLength(currLen) * 2; 183 | if (futureLen > size) 184 | { 185 | //以将来的大小的2次方的两倍确定内部字节缓存区大小 186 | size = FixLength(futureLen) * 2; 187 | } 188 | byte[] newbuf = new byte[size]; 189 | Array.Copy(buf, 0, newbuf, 0, currLen); 190 | buf = newbuf; 191 | capacity = size; 192 | } 193 | return futureLen; 194 | } 195 | 196 | /// 197 | /// 确保有这么多字节可以用来写入 198 | /// 199 | /// 200 | public void EnsureWritableBytes(int minBytes) 201 | { 202 | // 如果没有足够的空间进行写入了 203 | if (WritableBytes < minBytes) 204 | { 205 | 206 | // 优先整理空间 207 | if (ReaderIndex >= minBytes) 208 | { 209 | // 整理出来可用空间 210 | TrimReadedBytes(); 211 | } 212 | else 213 | { 214 | // 空间不足时,重新分配内存 215 | FixSizeAndReset(buf.Length, buf.Length + minBytes); 216 | } 217 | } 218 | } 219 | 220 | public void TrimReadedBytes() 221 | { 222 | Buffer.BlockCopy(buf, readIndex, buf, 0, writeIndex - readIndex); 223 | writeIndex -= readIndex; 224 | readIndex = 0; 225 | } 226 | 227 | /// 228 | /// 将bytes字节数组从startIndex开始的length字节写入到此缓存区 229 | /// 230 | /// 待写入的字节数据 231 | /// 写入的开始位置 232 | /// 写入的长度 233 | public void WriteBytes(byte[] bytes, int startIndex, int length) 234 | { 235 | if (length <= 0 || startIndex < 0) return; 236 | 237 | int total = length + writeIndex; 238 | int len = buf.Length; 239 | FixSizeAndReset(len, total); 240 | Array.Copy(bytes, startIndex, buf, writeIndex, length); 241 | writeIndex = total; 242 | } 243 | 244 | /// 245 | /// 将字节数组中从0到length的元素写入缓存区 246 | /// 247 | /// 待写入的字节数据 248 | /// 写入的长度 249 | public void WriteBytes(byte[] bytes, int length) 250 | { 251 | WriteBytes(bytes, 0, length); 252 | } 253 | 254 | /// 255 | /// 将字节数组全部写入缓存区 256 | /// 257 | /// 待写入的字节数据 258 | public void WriteBytes(byte[] bytes) 259 | { 260 | WriteBytes(bytes, bytes.Length); 261 | } 262 | 263 | /// 264 | /// 将一个ByteBuffer的有效字节区写入此缓存区中 265 | /// 266 | /// 待写入的字节缓存区 267 | public void Write(ByteBuffer buffer) 268 | { 269 | if (buffer == null) return; 270 | if (buffer.ReadableBytes <= 0) return; 271 | WriteBytes(buffer.ToArray()); 272 | } 273 | 274 | /// 275 | /// 写入一个int16数据 276 | /// 277 | /// short数据 278 | public void WriteShort(short value) 279 | { 280 | WriteBytes(Flip(BitConverter.GetBytes(value))); 281 | } 282 | 283 | /// 284 | /// 写入一个uint16数据 285 | /// 286 | /// ushort数据 287 | public void WriteUshort(ushort value) 288 | { 289 | WriteBytes(Flip(BitConverter.GetBytes(value))); 290 | } 291 | 292 | /// 293 | /// 写入一个int32数据 294 | /// 295 | /// int数据 296 | public void WriteInt(int value) 297 | { 298 | //byte[] array = new byte[4]; 299 | //for (int i = 3; i >= 0; i--) 300 | //{ 301 | // array[i] = (byte)(value & 0xff); 302 | // value = value >> 8; 303 | //} 304 | //Array.Reverse(array); 305 | //Write(array); 306 | WriteBytes(Flip(BitConverter.GetBytes(value))); 307 | } 308 | 309 | /// 310 | /// 写入一个uint32数据 311 | /// 312 | /// uint数据 313 | public void WriteUint(uint value) 314 | { 315 | WriteBytes(Flip(BitConverter.GetBytes(value))); 316 | } 317 | 318 | /// 319 | /// 写入一个int64数据 320 | /// 321 | /// long数据 322 | public void WriteLong(long value) 323 | { 324 | WriteBytes(Flip(BitConverter.GetBytes(value))); 325 | } 326 | 327 | /// 328 | /// 写入一个uint64数据 329 | /// 330 | /// ulong数据 331 | public void WriteUlong(ulong value) 332 | { 333 | WriteBytes(Flip(BitConverter.GetBytes(value))); 334 | } 335 | 336 | /// 337 | /// 写入一个float数据 338 | /// 339 | /// float数据 340 | public void WriteFloat(float value) 341 | { 342 | WriteBytes(Flip(BitConverter.GetBytes(value))); 343 | } 344 | 345 | /// 346 | /// 写入一个byte数据 347 | /// 348 | /// byte数据 349 | public void WriteByte(byte value) 350 | { 351 | int afterLen = writeIndex + 1; 352 | int len = buf.Length; 353 | FixSizeAndReset(len, afterLen); 354 | buf[writeIndex] = value; 355 | writeIndex = afterLen; 356 | } 357 | 358 | /// 359 | /// 写入一个byte数据 360 | /// 361 | /// byte数据 362 | public void WriteByte(int value) 363 | { 364 | byte b = (byte)value; 365 | WriteByte(b); 366 | } 367 | 368 | /// 369 | /// 写入一个double类型数据 370 | /// 371 | /// double数据 372 | public void WriteDouble(double value) 373 | { 374 | WriteBytes(Flip(BitConverter.GetBytes(value))); 375 | } 376 | 377 | /// 378 | /// 写入一个字符 379 | /// 380 | /// 381 | public void WriteChar(char value) 382 | { 383 | WriteBytes(Flip(BitConverter.GetBytes(value))); 384 | } 385 | 386 | /// 387 | /// 写入一个布尔型数据 388 | /// 389 | /// 390 | public void WriteBoolean(bool value) 391 | { 392 | WriteBytes(Flip(BitConverter.GetBytes(value))); 393 | } 394 | 395 | /// 396 | /// 读取一个字节 397 | /// 398 | /// 字节数据 399 | public byte ReadByte() 400 | { 401 | byte b = buf[readIndex]; 402 | readIndex++; 403 | return b; 404 | } 405 | 406 | /// 407 | /// 获取从index索引处开始len长度的字节 408 | /// 409 | /// 410 | /// 411 | /// 412 | private byte[] Get(int index, int len) 413 | { 414 | byte[] bytes = new byte[len]; 415 | Array.Copy(buf, index, bytes, 0, len); 416 | return Flip(bytes); 417 | } 418 | 419 | /// 420 | /// 从读取索引位置开始读取len长度的字节数组 421 | /// 422 | /// 待读取的字节长度 423 | /// 字节数组 424 | private byte[] Read(int len) 425 | { 426 | byte[] bytes = Get(readIndex, len); 427 | readIndex += len; 428 | return bytes; 429 | } 430 | 431 | /// 432 | /// 读取一个uint16数据 433 | /// 434 | /// ushort数据 435 | public ushort ReadUshort() 436 | { 437 | return BitConverter.ToUInt16(Read(2), 0); 438 | } 439 | 440 | /// 441 | /// 读取一个int16数据 442 | /// 443 | /// short数据 444 | public short ReadShort() 445 | { 446 | return BitConverter.ToInt16(Read(2), 0); 447 | } 448 | 449 | /// 450 | /// 读取一个uint32数据 451 | /// 452 | /// uint数据 453 | public uint ReadUint() 454 | { 455 | return BitConverter.ToUInt32(Read(4), 0); 456 | } 457 | 458 | /// 459 | /// 读取一个int32数据 460 | /// 461 | /// int数据 462 | public int ReadInt() 463 | { 464 | return BitConverter.ToInt32(Read(4), 0); 465 | } 466 | 467 | /// 468 | /// 读取一个uint64数据 469 | /// 470 | /// ulong数据 471 | public ulong ReadUlong() 472 | { 473 | return BitConverter.ToUInt64(Read(8), 0); 474 | } 475 | 476 | /// 477 | /// 读取一个long数据 478 | /// 479 | /// long数据 480 | public long ReadLong() 481 | { 482 | return BitConverter.ToInt64(Read(8), 0); 483 | } 484 | 485 | /// 486 | /// 读取一个float数据 487 | /// 488 | /// float数据 489 | public float ReadFloat() 490 | { 491 | return BitConverter.ToSingle(Read(4), 0); 492 | } 493 | 494 | /// 495 | /// 读取一个double数据 496 | /// 497 | /// double数据 498 | public double ReadDouble() 499 | { 500 | return BitConverter.ToDouble(Read(8), 0); 501 | } 502 | 503 | /// 504 | /// 读取一个字符 505 | /// 506 | /// 507 | public char ReadChar() 508 | { 509 | return BitConverter.ToChar(Read(2), 0); 510 | } 511 | 512 | /// 513 | /// 读取布尔型数据 514 | /// 515 | /// 516 | public bool ReadBoolean() 517 | { 518 | return BitConverter.ToBoolean(Read(1), 0); 519 | } 520 | 521 | /// 522 | /// 从读取索引位置开始读取len长度的字节到disbytes目标字节数组中 523 | /// 524 | /// 读取的字节将存入此字节数组 525 | /// 目标字节数组的写入索引 526 | /// 读取的长度 527 | public void ReadBytes(byte[] disbytes, int disstart, int len) 528 | { 529 | int size = disstart + len; 530 | for (int i = disstart; i < size; i++) 531 | { 532 | disbytes[i] = this.ReadByte(); 533 | } 534 | } 535 | 536 | public byte[] ReadBytes(int len) 537 | { 538 | return ReadBytes(readIndex, len); 539 | } 540 | 541 | public byte[] ReadBytes(int index, int len) 542 | { 543 | if (ReadableBytes < len) 544 | throw new Exception("no more readable bytes"); 545 | 546 | var buffer = new byte[len]; 547 | Array.Copy(buf, index, buffer, 0, len); 548 | readIndex += len; 549 | return buffer; 550 | } 551 | 552 | /// 553 | /// 获取一个字节 554 | /// 555 | /// 556 | /// 557 | public byte GetByte(int index) 558 | { 559 | return buf[index]; 560 | } 561 | 562 | /// 563 | /// 获取一个字节 564 | /// 565 | /// 566 | public byte GetByte() 567 | { 568 | return GetByte(readIndex); 569 | } 570 | 571 | /// 572 | /// 获取一个双精度浮点数据,不改变数据内容 573 | /// 574 | /// 字节索引 575 | /// 576 | public double GetDouble(int index) 577 | { 578 | return BitConverter.ToDouble(Get(index, 8), 0); 579 | } 580 | 581 | /// 582 | /// 获取一个双精度浮点数据,不改变数据内容 583 | /// 584 | /// 585 | public double GetDouble() 586 | { 587 | return GetDouble(readIndex); 588 | } 589 | 590 | /// 591 | /// 获取一个浮点数据,不改变数据内容 592 | /// 593 | /// 字节索引 594 | /// 595 | public float GetFloat(int index) 596 | { 597 | return BitConverter.ToSingle(Get(index, 4), 0); 598 | } 599 | 600 | /// 601 | /// 获取一个浮点数据,不改变数据内容 602 | /// 603 | /// 604 | public float GetFloat() 605 | { 606 | return GetFloat(readIndex); 607 | } 608 | 609 | /// 610 | /// 获取一个长整形数据,不改变数据内容 611 | /// 612 | /// 字节索引 613 | /// 614 | public long GetLong(int index) 615 | { 616 | return BitConverter.ToInt64(Get(index, 8), 0); 617 | } 618 | 619 | /// 620 | /// 获取一个长整形数据,不改变数据内容 621 | /// 622 | /// 623 | public long GetLong() 624 | { 625 | return GetLong(readIndex); 626 | } 627 | 628 | /// 629 | /// 获取一个长整形数据,不改变数据内容 630 | /// 631 | /// 字节索引 632 | /// 633 | public ulong GetUlong(int index) 634 | { 635 | return BitConverter.ToUInt64(Get(index, 8), 0); 636 | } 637 | 638 | /// 639 | /// 获取一个长整形数据,不改变数据内容 640 | /// 641 | /// 642 | public ulong GetUlong() 643 | { 644 | return GetUlong(readIndex); 645 | } 646 | 647 | /// 648 | /// 获取一个整形数据,不改变数据内容 649 | /// 650 | /// 字节索引 651 | /// 652 | public int GetInt(int index) 653 | { 654 | return BitConverter.ToInt32(Get(index, 4), 0); 655 | } 656 | 657 | /// 658 | /// 获取一个整形数据,不改变数据内容 659 | /// 660 | /// 661 | public int GetInt() 662 | { 663 | return GetInt(readIndex); 664 | } 665 | 666 | /// 667 | /// 获取一个整形数据,不改变数据内容 668 | /// 669 | /// 字节索引 670 | /// 671 | public uint GetUint(int index) 672 | { 673 | return BitConverter.ToUInt32(Get(index, 4), 0); 674 | } 675 | 676 | /// 677 | /// 获取一个整形数据,不改变数据内容 678 | /// 679 | /// 680 | public uint GetUint() 681 | { 682 | return GetUint(readIndex); 683 | } 684 | 685 | /// 686 | /// 获取一个短整形数据,不改变数据内容 687 | /// 688 | /// 字节索引 689 | /// 690 | public int GetShort(int index) 691 | { 692 | return BitConverter.ToInt16(Get(index, 2), 0); 693 | } 694 | 695 | /// 696 | /// 获取一个短整形数据,不改变数据内容 697 | /// 698 | /// 699 | public int GetShort() 700 | { 701 | return GetShort(readIndex); 702 | } 703 | 704 | /// 705 | /// 获取一个短整形数据,不改变数据内容 706 | /// 707 | /// 字节索引 708 | /// 709 | public int GetUshort(int index) 710 | { 711 | return BitConverter.ToUInt16(Get(index, 2), 0); 712 | } 713 | 714 | /// 715 | /// 获取一个短整形数据,不改变数据内容 716 | /// 717 | /// 718 | public int GetUshort() 719 | { 720 | return GetUshort(readIndex); 721 | } 722 | 723 | /// 724 | /// 获取一个char数据,不改变数据内容 725 | /// 726 | /// 字节索引 727 | /// 728 | public char GetChar(int index) 729 | { 730 | return BitConverter.ToChar(Get(index, 2), 0); 731 | } 732 | 733 | /// 734 | /// 获取一个char数据,不改变数据内容 735 | /// 736 | /// 737 | public char GetChar() 738 | { 739 | return GetChar(readIndex); 740 | } 741 | 742 | /// 743 | /// 获取一个布尔数据,不改变数据内容 744 | /// 745 | /// 字节索引 746 | /// 747 | public bool GetBoolean(int index) 748 | { 749 | return BitConverter.ToBoolean(Get(index, 1), 0); 750 | } 751 | 752 | /// 753 | /// 获取一个布尔数据,不改变数据内容 754 | /// 755 | /// 756 | public bool GetBoolean() 757 | { 758 | return GetBoolean(readIndex); 759 | } 760 | 761 | /// 762 | /// 清除已读字节并重建缓存区 763 | /// 764 | public void DiscardReadBytes() 765 | { 766 | if (readIndex <= 0) return; 767 | int len = buf.Length - readIndex; 768 | byte[] newbuf = new byte[len]; 769 | Array.Copy(buf, readIndex, newbuf, 0, len); 770 | buf = newbuf; 771 | writeIndex -= readIndex; 772 | markReadIndex -= readIndex; 773 | if (markReadIndex < 0) 774 | { 775 | //markReadIndex = readIndex; 776 | markReadIndex = 0; 777 | } 778 | markWirteIndex -= readIndex; 779 | if (markWirteIndex < 0 || markWirteIndex < readIndex || markWirteIndex < markReadIndex) 780 | { 781 | markWirteIndex = writeIndex; 782 | } 783 | readIndex = 0; 784 | } 785 | 786 | /// 787 | /// 设置/获取读指针位置 788 | /// 789 | public int ReaderIndex 790 | { 791 | get 792 | { 793 | return readIndex; 794 | } 795 | set 796 | { 797 | if (value < 0) return; 798 | readIndex = value; 799 | } 800 | } 801 | 802 | /// 803 | /// 设置/获取写指针位置 804 | /// 805 | public int WriterIndex 806 | { 807 | get 808 | { 809 | return writeIndex; 810 | } 811 | set 812 | { 813 | if (value < 0) return; 814 | writeIndex = value; 815 | } 816 | } 817 | 818 | /// 819 | /// 标记读取的索引位置 820 | /// 821 | public void MarkReaderIndex() 822 | { 823 | markReadIndex = readIndex; 824 | } 825 | 826 | /// 827 | /// 标记写入的索引位置 828 | /// 829 | public void MarkWriterIndex() 830 | { 831 | markWirteIndex = writeIndex; 832 | } 833 | 834 | /// 835 | /// 将读取的索引位置重置为标记的读取索引位置 836 | /// 837 | public void ResetReaderIndex() 838 | { 839 | readIndex = markReadIndex; 840 | } 841 | 842 | /// 843 | /// 将写入的索引位置重置为标记的写入索引位置 844 | /// 845 | public void ResetWriterIndex() 846 | { 847 | writeIndex = markWirteIndex; 848 | } 849 | 850 | /// 851 | /// 可读的有效字节数 852 | /// 853 | /// 可读的字节数 854 | public int ReadableBytes 855 | { 856 | get 857 | { 858 | return writeIndex - readIndex; 859 | } 860 | } 861 | 862 | /// 863 | /// 可写的剩余空间数 864 | /// 865 | /// 可写的字节数 866 | public int WritableBytes 867 | { 868 | get 869 | { 870 | return capacity - writeIndex; 871 | } 872 | } 873 | 874 | /// 875 | /// 获取缓存区容量大小 876 | /// 877 | /// 缓存区容量 878 | public int Capacity 879 | { 880 | get 881 | { 882 | return this.capacity; 883 | } 884 | } 885 | 886 | public byte[] RawBuffer 887 | { 888 | get 889 | { 890 | return buf; 891 | } 892 | } 893 | 894 | /// 895 | /// 获取可读的字节数组 896 | /// 897 | /// 字节数据 898 | public byte[] ToArray() 899 | { 900 | byte[] bytes = new byte[writeIndex - readIndex]; 901 | Array.Copy(buf, readIndex, bytes, 0, bytes.Length); 902 | return bytes; 903 | } 904 | 905 | /// 906 | /// 简单的数据类型 907 | /// 908 | public enum DataType 909 | { 910 | //byte类型 911 | BYTE = 1, 912 | //short类型 913 | SHORT = 2, 914 | //int类型 915 | INT = 3, 916 | //long类型 917 | LONG = 4 918 | } 919 | 920 | /// 921 | /// 写入一个数据 922 | /// 923 | /// 待写入的数据 924 | /// 待写入的数据类型 925 | private void WriteValue(int value, DataType type) 926 | { 927 | switch (type) 928 | { 929 | case DataType.BYTE: 930 | this.WriteByte(value); 931 | break; 932 | case DataType.SHORT: 933 | this.WriteShort((short)value); 934 | break; 935 | case DataType.LONG: 936 | this.WriteLong((long)value); 937 | break; 938 | default: 939 | this.WriteInt(value); 940 | break; 941 | } 942 | } 943 | 944 | /// 945 | /// 读取一个值,值类型根据type决定,int或short或byte 946 | /// 947 | /// 值类型 948 | /// int数据 949 | private int ReadValue(DataType type) 950 | { 951 | switch (type) 952 | { 953 | case DataType.BYTE: 954 | return (int)ReadByte(); 955 | case DataType.SHORT: 956 | return (int)ReadShort(); 957 | case DataType.INT: 958 | return (int)ReadInt(); 959 | case DataType.LONG: 960 | return (int)ReadLong(); 961 | default: 962 | return -1; 963 | } 964 | } 965 | 966 | /// 967 | /// 写入可变长的UTF-8字符串 968 | /// 以长度类型(byte:1, short:2, int:3) + 长度(根据长度类型写入到字节缓冲区) + 字节数组表示一个字符串 969 | /// 970 | /// 971 | //public void WriteUTF8VarString(string content) 972 | //{ 973 | // byte[] bytes = System.Text.Encoding.UTF8.GetBytes(content); 974 | // ValueType lenType; 975 | // if (bytes.Length <= byte.MaxValue) 976 | // { 977 | // lenType = ValueType.BYTE; 978 | // } 979 | // else if (bytes.Length <= short.MaxValue) 980 | // { 981 | // lenType = ValueType.SHORT; 982 | // } 983 | // else 984 | // { 985 | // lenType = ValueType.INT; 986 | // } 987 | // WriteByte((int)lenType); 988 | // if (lenType == ValueType.BYTE) 989 | // { 990 | // WriteByte(bytes.Length); 991 | // } 992 | // else if (lenType == ValueType.SHORT) 993 | // { 994 | // WriteShort((short)bytes.Length); 995 | // } 996 | // else 997 | // { 998 | // WriteInt(bytes.Length); 999 | // } 1000 | // WriteBytes(bytes); 1001 | //} 1002 | 1003 | /// 1004 | /// 读取可变长的UTF-8字符串 1005 | /// 以长度类型(byte:1, short:2, int:3) + 长度(根据长度类型从字节缓冲区读取) + 字节数组表示一个字符串 1006 | /// 1007 | /// 1008 | //public string ReadUTF8VarString() 1009 | //{ 1010 | // int lenTypeValue = ReadByte(); 1011 | // int len = 0; 1012 | // if (lenTypeValue == (int)ValueType.BYTE) 1013 | // { 1014 | // len = ReadByte(); 1015 | // } 1016 | // else if (lenTypeValue == (int)ValueType.SHORT) 1017 | // { 1018 | // len = ReadShort(); 1019 | // } 1020 | // else if (lenTypeValue == (int)ValueType.INT) 1021 | // { 1022 | // len = ReadInt(); 1023 | // } 1024 | // if (len > 0) 1025 | // { 1026 | // byte[] bytes = new byte[len]; 1027 | // ReadBytes(bytes, 0, len); 1028 | // return System.Text.Encoding.UTF8.GetString(bytes); 1029 | // } 1030 | // return ""; 1031 | //} 1032 | 1033 | /// 1034 | /// 写入一个UTF-8字符串,UTF-8字符串无高低字节序问题 1035 | /// 写入缓冲区的结构为字符串字节长度(类型由lenType指定) + 字符串字节数组 1036 | /// 1037 | /// 待写入的字符串 1038 | /// 写入的字符串长度类型 1039 | public void WriteUTF8String(string content, DataType lenType) 1040 | { 1041 | byte[] bytes = System.Text.Encoding.UTF8.GetBytes(content); 1042 | int max; 1043 | if (lenType == DataType.BYTE) 1044 | { 1045 | WriteByte(bytes.Length); 1046 | max = byte.MaxValue; 1047 | } 1048 | else if (lenType == DataType.SHORT) 1049 | { 1050 | WriteShort((short)bytes.Length); 1051 | max = short.MaxValue; 1052 | } 1053 | else 1054 | { 1055 | WriteInt(bytes.Length); 1056 | max = int.MaxValue; 1057 | } 1058 | if (bytes.Length > max) 1059 | { 1060 | WriteBytes(bytes, 0, max); 1061 | } 1062 | else 1063 | { 1064 | WriteBytes(bytes, 0, bytes.Length); 1065 | } 1066 | } 1067 | 1068 | /// 1069 | /// 写入以short表示的字符串字节长度和字符串字节数据 1070 | /// 1071 | /// 1072 | public void WriteUTF(string content) 1073 | { 1074 | this.WriteUTF8String(content, DataType.SHORT); 1075 | } 1076 | 1077 | /// 1078 | /// 读取一个UTF-8字符串,UTF-8字符串无高低字节序问题 1079 | /// 1080 | /// 需读取的字符串长度 1081 | /// 字符串 1082 | public string ReadUTF8String(int len) 1083 | { 1084 | byte[] bytes = new byte[len]; 1085 | this.ReadBytes(bytes, 0, len); 1086 | return System.Text.Encoding.UTF8.GetString(bytes); 1087 | } 1088 | 1089 | /// 1090 | /// 读取一个UTF-8字符串,UTF-8字符串无高低字节序问题 1091 | /// 1092 | /// 字符串长度类型 1093 | /// 字符串 1094 | public string ReadUTF8String(DataType lenType) 1095 | { 1096 | int len = ReadValue(lenType); 1097 | return ReadUTF8String(len); 1098 | } 1099 | 1100 | /// 1101 | /// 读取short类型的字符串字节长度,然后根据此长度读取对应数量的字节数据后转为字符串 1102 | /// 1103 | /// UTF-8字符串 1104 | public string ReadUTF() 1105 | { 1106 | return this.ReadUTF8String(DataType.SHORT); 1107 | } 1108 | 1109 | /// 1110 | /// 复制一个对象,具有与原对象相同的数据,不改变原对象的数据,不包括已读数据 1111 | /// 1112 | /// 1113 | public ByteBuffer Copy() 1114 | { 1115 | if (buf == null) 1116 | { 1117 | return new ByteBuffer(16); 1118 | } 1119 | if (readIndex < writeIndex) 1120 | { 1121 | byte[] newbytes = new byte[writeIndex - readIndex]; 1122 | Array.Copy(buf, readIndex, newbytes, 0, newbytes.Length); 1123 | ByteBuffer buffer = new ByteBuffer(newbytes.Length); 1124 | buffer.WriteBytes(newbytes); 1125 | buffer.isPool = this.isPool; 1126 | return buffer; 1127 | } 1128 | return new ByteBuffer(16); 1129 | } 1130 | 1131 | /// 1132 | /// 深度复制,具有与原对象相同的数据,不改变原对象的数据,包括已读数据 1133 | /// 1134 | /// 1135 | public object Clone() 1136 | { 1137 | if (buf == null) 1138 | { 1139 | return new ByteBuffer(16); 1140 | } 1141 | ByteBuffer newBuf = new ByteBuffer(buf) 1142 | { 1143 | capacity = this.capacity, 1144 | readIndex = this.readIndex, 1145 | writeIndex = this.writeIndex, 1146 | markReadIndex = this.markReadIndex, 1147 | markWirteIndex = this.markWirteIndex, 1148 | isPool = this.isPool 1149 | }; 1150 | return newBuf; 1151 | } 1152 | 1153 | /// 1154 | /// 遍历所有的字节数据 1155 | /// 1156 | /// 1157 | public void ForEach(Action action) 1158 | { 1159 | for (int i = 0; i < this.ReadableBytes; i++) 1160 | { 1161 | action.Invoke(this.buf[i]); 1162 | } 1163 | } 1164 | 1165 | /// 1166 | /// 清空此对象,但保留字节缓存数组(空数组) 1167 | /// 1168 | public void Clear() 1169 | { 1170 | readIndex = 0; 1171 | writeIndex = 0; 1172 | markReadIndex = 0; 1173 | markWirteIndex = 0; 1174 | capacity = buf.Length; 1175 | } 1176 | 1177 | /// 1178 | /// 释放对象,清除字节缓存数组,如果此对象为可池化,那么调用此方法将会把此对象推入到池中等待下次调用 1179 | /// 1180 | public void Dispose() 1181 | { 1182 | if (isPool) { 1183 | lock (pool) { 1184 | if (pool.Count < poolMaxCount) { 1185 | this.Clear(); 1186 | pool.Add(this); 1187 | return; 1188 | } 1189 | } 1190 | } 1191 | 1192 | readIndex = 0; 1193 | writeIndex = 0; 1194 | markReadIndex = 0; 1195 | markWirteIndex = 0; 1196 | capacity = 0; 1197 | buf = null; 1198 | } 1199 | } 1200 | } -------------------------------------------------------------------------------- /KCP/KCP.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | 4 | namespace KcpProject 5 | { 6 | 7 | public class KCP 8 | { 9 | public const int IKCP_RTO_NDL = 30; // no delay min rto 10 | public const int IKCP_RTO_MIN = 100; // normal min rto 11 | public const int IKCP_RTO_DEF = 200; 12 | public const int IKCP_RTO_MAX = 60000; 13 | public const int IKCP_CMD_PUSH = 81; // cmd: push data 14 | public const int IKCP_CMD_ACK = 82; // cmd: ack 15 | public const int IKCP_CMD_WASK = 83; // cmd: window probe (ask) 16 | public const int IKCP_CMD_WINS = 84; // cmd: window size (tell) 17 | public const int IKCP_ASK_SEND = 1; // need to send IKCP_CMD_WASK 18 | public const int IKCP_ASK_TELL = 2; // need to send IKCP_CMD_WINS 19 | public const int IKCP_WND_SND = 32; 20 | public const int IKCP_WND_RCV = 32; 21 | public const int IKCP_MTU_DEF = 1400; 22 | public const int IKCP_ACK_FAST = 3; 23 | public const int IKCP_INTERVAL = 100; 24 | public const int IKCP_OVERHEAD = 24; 25 | public const int IKCP_DEADLINK = 20; 26 | public const int IKCP_THRESH_INIT = 2; 27 | public const int IKCP_THRESH_MIN = 2; 28 | public const int IKCP_PROBE_INIT = 7000; // 7 secs to probe window size 29 | public const int IKCP_PROBE_LIMIT = 120000; // up to 120 secs to probe window 30 | public const int IKCP_SN_OFFSET = 12; 31 | 32 | 33 | // encode 8 bits unsigned int 34 | public static int ikcp_encode8u(byte[] p, int offset, byte c) 35 | { 36 | p[0 + offset] = c; 37 | return 1; 38 | } 39 | 40 | // decode 8 bits unsigned int 41 | public static int ikcp_decode8u(byte[] p, int offset, ref byte c) 42 | { 43 | c = p[0 + offset]; 44 | return 1; 45 | } 46 | 47 | /* encode 16 bits unsigned int (lsb) */ 48 | public static int ikcp_encode16u(byte[] p, int offset, UInt16 w) 49 | { 50 | p[0 + offset] = (byte)(w >> 0); 51 | p[1 + offset] = (byte)(w >> 8); 52 | return 2; 53 | } 54 | 55 | /* decode 16 bits unsigned int (lsb) */ 56 | public static int ikcp_decode16u(byte[] p, int offset, ref UInt16 c) 57 | { 58 | UInt16 result = 0; 59 | result |= (UInt16)p[0 + offset]; 60 | result |= (UInt16)(p[1 + offset] << 8); 61 | c = result; 62 | return 2; 63 | } 64 | 65 | /* encode 32 bits unsigned int (lsb) */ 66 | public static int ikcp_encode32u(byte[] p, int offset, UInt32 l) 67 | { 68 | p[0 + offset] = (byte)(l >> 0); 69 | p[1 + offset] = (byte)(l >> 8); 70 | p[2 + offset] = (byte)(l >> 16); 71 | p[3 + offset] = (byte)(l >> 24); 72 | return 4; 73 | } 74 | 75 | /* decode 32 bits unsigned int (lsb) */ 76 | public static int ikcp_decode32u(byte[] p, int offset, ref UInt32 c) 77 | { 78 | UInt32 result = 0; 79 | result |= (UInt32)p[0 + offset]; 80 | result |= (UInt32)(p[1 + offset] << 8); 81 | result |= (UInt32)(p[2 + offset] << 16); 82 | result |= (UInt32)(p[3 + offset] << 24); 83 | c = result; 84 | return 4; 85 | } 86 | 87 | static UInt32 _imin_(UInt32 a, UInt32 b) 88 | { 89 | return a <= b ? a : b; 90 | } 91 | 92 | private static DateTime refTime = DateTime.Now; 93 | 94 | private static UInt32 currentMS() 95 | { 96 | var ts = DateTime.Now.Subtract(refTime); 97 | return (UInt32)ts.TotalMilliseconds; 98 | } 99 | 100 | static UInt32 _imax_(UInt32 a, UInt32 b) 101 | { 102 | return a >= b ? a : b; 103 | } 104 | 105 | static UInt32 _ibound_(UInt32 lower, UInt32 middle, UInt32 upper) 106 | { 107 | return _imin_(_imax_(lower, middle), upper); 108 | } 109 | 110 | static Int32 _itimediff(UInt32 later, UInt32 earlier) 111 | { 112 | return ((Int32)(later - earlier)); 113 | } 114 | 115 | // KCP Segment Definition 116 | internal class Segment 117 | { 118 | internal UInt32 conv = 0; 119 | internal UInt32 cmd = 0; 120 | internal UInt32 frg = 0; 121 | internal UInt32 wnd = 0; 122 | internal UInt32 ts = 0; 123 | internal UInt32 sn = 0; 124 | internal UInt32 una = 0; 125 | internal UInt32 rto = 0; 126 | internal UInt32 xmit = 0; 127 | internal UInt32 resendts = 0; 128 | internal UInt32 fastack = 0; 129 | internal UInt32 acked = 0; 130 | internal ByteBuffer data; 131 | 132 | private static Stack msSegmentPool = new Stack(32); 133 | 134 | public static Segment Get(int size) 135 | { 136 | lock (msSegmentPool) 137 | { 138 | if (msSegmentPool.Count > 0) 139 | { 140 | var seg = msSegmentPool.Pop(); 141 | seg.data = ByteBuffer.Allocate(size, true); 142 | return seg; 143 | } 144 | } 145 | return new Segment(size); 146 | } 147 | 148 | public static void Put(Segment seg) 149 | { 150 | seg.reset(); 151 | lock (msSegmentPool) { 152 | msSegmentPool.Push(seg); 153 | } 154 | } 155 | 156 | private Segment(int size) 157 | { 158 | data = ByteBuffer.Allocate(size, true); 159 | } 160 | 161 | // encode a segment into buffer 162 | internal int encode(byte[] ptr, int offset) 163 | { 164 | 165 | var offset_ = offset; 166 | 167 | offset += ikcp_encode32u(ptr, offset, conv); 168 | offset += ikcp_encode8u(ptr, offset, (byte)cmd); 169 | offset += ikcp_encode8u(ptr, offset, (byte)frg); 170 | offset += ikcp_encode16u(ptr, offset, (UInt16)wnd); 171 | offset += ikcp_encode32u(ptr, offset, ts); 172 | offset += ikcp_encode32u(ptr, offset, sn); 173 | offset += ikcp_encode32u(ptr, offset, una); 174 | offset += ikcp_encode32u(ptr, offset, (UInt32)data.ReadableBytes); 175 | 176 | return offset - offset_; 177 | } 178 | 179 | internal void reset() 180 | { 181 | conv = 0; 182 | cmd = 0; 183 | frg = 0; 184 | wnd = 0; 185 | ts = 0; 186 | sn = 0; 187 | una = 0; 188 | rto = 0; 189 | xmit = 0; 190 | resendts = 0; 191 | fastack = 0; 192 | acked = 0; 193 | 194 | data.Clear(); 195 | data.Dispose(); 196 | data = null; 197 | } 198 | } 199 | 200 | internal struct ackItem 201 | { 202 | internal UInt32 sn; 203 | internal UInt32 ts; 204 | } 205 | 206 | // kcp members. 207 | UInt32 conv; UInt32 mtu; UInt32 mss; UInt32 state; 208 | UInt32 snd_una; UInt32 snd_nxt; UInt32 rcv_nxt; 209 | UInt32 ts_recent; UInt32 ts_lastack; UInt32 ssthresh; 210 | Int32 rx_rttval; Int32 rx_srtt; 211 | UInt32 rx_rto; UInt32 rx_minrto; 212 | UInt32 snd_wnd; UInt32 rcv_wnd; UInt32 rmt_wnd; UInt32 cwnd; UInt32 probe; 213 | UInt32 interval; UInt32 ts_flush; 214 | UInt32 nodelay; UInt32 updated; 215 | UInt32 ts_probe; UInt32 probe_wait; 216 | UInt32 dead_link; UInt32 incr; 217 | 218 | Int32 fastresend; 219 | Int32 nocwnd; Int32 stream; 220 | 221 | List snd_queue = new List(16); 222 | List rcv_queue = new List(16); 223 | List snd_buf = new List(16); 224 | List rcv_buf = new List(16); 225 | 226 | List acklist = new List(16); 227 | 228 | byte[] buffer; 229 | Int32 reserved; 230 | Action output; // buffer, size 231 | 232 | // send windowd & recv window 233 | public UInt32 SndWnd { get { return snd_wnd; } } 234 | public UInt32 RcvWnd { get { return rcv_wnd; } } 235 | public UInt32 RmtWnd { get { return rmt_wnd; } } 236 | public UInt32 Mss { get { return mss; } } 237 | 238 | // get how many packet is waiting to be sent 239 | public int WaitSnd { get { return snd_buf.Count + snd_queue.Count; } } 240 | 241 | // internal time. 242 | public UInt32 CurrentMS { get { return currentMS(); } } 243 | 244 | // log 245 | Action writelog = null; 246 | 247 | public const Int32 IKCP_LOG_OUTPUT = 1; 248 | public const Int32 IKCP_LOG_INPUT = 2; 249 | public const Int32 IKCP_LOG_SEND = 4; 250 | public const Int32 IKCP_LOG_RECV = 8; 251 | public const Int32 IKCP_LOG_IN_DATA = 16; 252 | public const Int32 IKCP_LOG_IN_ACK = 32; 253 | public const Int32 IKCP_LOG_IN_PROBE = 64; 254 | public const Int32 IKCP_LOG_IN_WINS = 128; 255 | public const Int32 IKCP_LOG_OUT_DATA = 256; 256 | public const Int32 IKCP_LOG_OUT_ACK = 512; 257 | public const Int32 IKCP_LOG_OUT_PROBE = 1024; 258 | public const Int32 IKCP_LOG_OUT_WINS = 2048; 259 | public Int32 logmask; 260 | 261 | // create a new kcp control object, 'conv' must equal in two endpoint 262 | // from the same connection. 263 | public KCP(UInt32 conv_, Action output_) 264 | { 265 | conv = conv_; 266 | snd_wnd = IKCP_WND_SND; 267 | rcv_wnd = IKCP_WND_RCV; 268 | rmt_wnd = IKCP_WND_RCV; 269 | mtu = IKCP_MTU_DEF; 270 | mss = mtu - IKCP_OVERHEAD; 271 | rx_rto = IKCP_RTO_DEF; 272 | rx_minrto = IKCP_RTO_MIN; 273 | interval = IKCP_INTERVAL; 274 | ts_flush = IKCP_INTERVAL; 275 | ssthresh = IKCP_THRESH_INIT; 276 | dead_link = IKCP_DEADLINK; 277 | buffer = new byte[mtu]; 278 | output = output_; 279 | } 280 | 281 | // check the size of next message in the recv queue 282 | public int PeekSize() 283 | { 284 | 285 | if (0 == rcv_queue.Count) return -1; 286 | 287 | var seq = rcv_queue[0]; 288 | 289 | if (0 == seq.frg) return seq.data.ReadableBytes; 290 | 291 | if (rcv_queue.Count < seq.frg + 1) return -1; 292 | 293 | int length = 0; 294 | 295 | foreach (var item in rcv_queue) 296 | { 297 | length += item.data.ReadableBytes; 298 | if (0 == item.frg) 299 | break; 300 | } 301 | 302 | return length; 303 | } 304 | 305 | 306 | public int Recv(byte[] buffer) 307 | { 308 | return Recv(buffer, 0, buffer.Length); 309 | } 310 | 311 | // Receive data from kcp state machine 312 | // 313 | // Return number of bytes read. 314 | // 315 | // Return -1 when there is no readable data. 316 | // 317 | // Return -2 if len(buffer) is smaller than kcp.PeekSize(). 318 | public int Recv(byte[] buffer, int index, int length) 319 | { 320 | var peekSize = PeekSize(); 321 | if (peekSize < 0) 322 | return -1; 323 | 324 | if (peekSize > length) 325 | return -2; 326 | 327 | var fast_recover = false; 328 | if (rcv_queue.Count >= rcv_wnd) 329 | fast_recover = true; 330 | 331 | // merge fragment. 332 | var count = 0; 333 | var n = index; 334 | foreach (var seg in rcv_queue) 335 | { 336 | // copy fragment data into buffer. 337 | Buffer.BlockCopy(seg.data.RawBuffer, seg.data.ReaderIndex, buffer, n, seg.data.ReadableBytes); 338 | n += seg.data.ReadableBytes; 339 | 340 | count++; 341 | var fragment = seg.frg; 342 | 343 | if (ikcp_canlog(IKCP_LOG_RECV)) 344 | { 345 | ikcp_log($"recv sn={seg.sn.ToString()}"); 346 | } 347 | 348 | Segment.Put(seg); 349 | if (0 == fragment) break; 350 | } 351 | 352 | if (count > 0) 353 | { 354 | rcv_queue.RemoveRange(0, count); 355 | } 356 | 357 | // move available data from rcv_buf -> rcv_queue 358 | count = 0; 359 | foreach (var seg in rcv_buf) 360 | { 361 | if (seg.sn == rcv_nxt && rcv_queue.Count + count < rcv_wnd) 362 | { 363 | rcv_queue.Add(seg); 364 | rcv_nxt++; 365 | count++; 366 | } 367 | else 368 | { 369 | break; 370 | } 371 | } 372 | 373 | if (count > 0) 374 | { 375 | rcv_buf.RemoveRange(0, count); 376 | } 377 | 378 | 379 | // fast recover 380 | if (rcv_queue.Count < rcv_wnd && fast_recover) 381 | { 382 | // ready to send back IKCP_CMD_WINS in ikcp_flush 383 | // tell remote my window size 384 | probe |= IKCP_ASK_TELL; 385 | } 386 | 387 | return n - index; 388 | } 389 | 390 | public int Send(byte[] buffer) 391 | { 392 | return Send(buffer, 0, buffer.Length); 393 | } 394 | 395 | // user/upper level send, returns below zero for error 396 | public int Send(byte[] buffer, int index, int length) 397 | { 398 | if (0 == length) return -1; 399 | 400 | if (stream != 0) 401 | { 402 | var n = snd_queue.Count; 403 | if (n > 0) 404 | { 405 | var seg = snd_queue[n - 1]; 406 | if (seg.data.ReadableBytes < mss) 407 | { 408 | var capacity = (int)(mss - seg.data.ReadableBytes); 409 | var writen = Math.Min(capacity, length); 410 | seg.data.WriteBytes(buffer, index, writen); 411 | index += writen; 412 | length -= writen; 413 | } 414 | } 415 | } 416 | 417 | if (length == 0) 418 | return 0; 419 | 420 | var count = 0; 421 | if (length <= mss) 422 | count = 1; 423 | else 424 | count = (int)(((length) + mss - 1) / mss); 425 | 426 | if (count > 255) return -2; 427 | 428 | if (count == 0) count = 1; 429 | 430 | for (var i = 0; i < count; i++) 431 | { 432 | var size = Math.Min(length, (int)mss); 433 | 434 | var seg = Segment.Get(size); 435 | seg.data.WriteBytes(buffer, index, size); 436 | index += size; 437 | length -= size; 438 | 439 | seg.frg = (stream == 0 ? (byte)(count - i - 1) : (byte)0); 440 | snd_queue.Add(seg); 441 | } 442 | 443 | return 0; 444 | } 445 | 446 | // update ack. 447 | void update_ack(Int32 rtt) 448 | { 449 | // https://tools.ietf.org/html/rfc6298 450 | if (0 == rx_srtt) 451 | { 452 | rx_srtt = rtt; 453 | rx_rttval = rtt >> 1; 454 | } 455 | else 456 | { 457 | Int32 delta = rtt - rx_srtt; 458 | rx_srtt += (delta >> 3); 459 | if (0 > delta) delta = -delta; 460 | 461 | if (rtt < rx_srtt - rx_rttval) 462 | { 463 | // if the new RTT sample is below the bottom of the range of 464 | // what an RTT measurement is expected to be. 465 | // give an 8x reduced weight versus its normal weighting 466 | rx_rttval += ((delta - rx_rttval) >> 5); 467 | } 468 | else 469 | { 470 | rx_rttval += ((delta - rx_rttval) >> 2); 471 | } 472 | } 473 | 474 | uint rto = (uint)(rx_srtt) + _imax_(interval, (uint)(rx_rttval) << 2); 475 | rx_rto = _ibound_(rx_minrto, rto, IKCP_RTO_MAX); 476 | } 477 | 478 | void shrink_buf() 479 | { 480 | if (snd_buf.Count > 0) 481 | snd_una = snd_buf[0].sn; 482 | else 483 | snd_una = snd_nxt; 484 | } 485 | 486 | void parse_ack(UInt32 sn) 487 | { 488 | 489 | if (_itimediff(sn, snd_una) < 0 || _itimediff(sn, snd_nxt) >= 0) return; 490 | 491 | foreach (var seg in snd_buf) 492 | { 493 | if (sn == seg.sn) 494 | { 495 | // mark and free space, but leave the segment here, 496 | // and wait until `una` to delete this, then we don't 497 | // have to shift the segments behind forward, 498 | // which is an expensive operation for large window 499 | seg.acked = 1; 500 | break; 501 | } 502 | if (_itimediff(sn, seg.sn) < 0) 503 | break; 504 | } 505 | } 506 | 507 | void parse_fastack(UInt32 sn, UInt32 ts) 508 | { 509 | if (_itimediff(sn, snd_una) < 0 || _itimediff(sn, snd_nxt) >= 0) 510 | return; 511 | 512 | foreach (var seg in snd_buf) 513 | { 514 | if (_itimediff(sn, seg.sn) < 0) 515 | break; 516 | else if (sn != seg.sn && _itimediff(seg.ts, ts) <= 0) 517 | seg.fastack++; 518 | } 519 | } 520 | 521 | int parse_una(UInt32 una) 522 | { 523 | var count = 0; 524 | foreach (var seg in snd_buf) 525 | { 526 | if (_itimediff(una, seg.sn) > 0) { 527 | count++; 528 | Segment.Put(seg); 529 | } 530 | else 531 | break; 532 | } 533 | 534 | if (count > 0) 535 | snd_buf.RemoveRange(0, count); 536 | return count; 537 | } 538 | 539 | void ack_push(UInt32 sn, UInt32 ts) 540 | { 541 | acklist.Add(new ackItem { sn = sn, ts = ts }); 542 | } 543 | 544 | bool parse_data(Segment newseg) 545 | { 546 | var sn = newseg.sn; 547 | if (_itimediff(sn, rcv_nxt + rcv_wnd) >= 0 || _itimediff(sn, rcv_nxt) < 0) 548 | return true; 549 | 550 | var n = rcv_buf.Count - 1; 551 | var insert_idx = 0; 552 | var repeat = false; 553 | for (var i = n; i >= 0; i--) 554 | { 555 | var seg = rcv_buf[i]; 556 | if (seg.sn == sn) 557 | { 558 | repeat = true; 559 | break; 560 | } 561 | 562 | if (_itimediff(sn, seg.sn) > 0) 563 | { 564 | insert_idx = i + 1; 565 | break; 566 | } 567 | } 568 | 569 | if (!repeat) 570 | { 571 | if (insert_idx == n + 1) 572 | rcv_buf.Add(newseg); 573 | else 574 | rcv_buf.Insert(insert_idx, newseg); 575 | } 576 | 577 | // move available data from rcv_buf -> rcv_queue 578 | var count = 0; 579 | foreach (var seg in rcv_buf) 580 | { 581 | if (seg.sn == rcv_nxt && rcv_queue.Count + count < rcv_wnd) 582 | { 583 | rcv_nxt++; 584 | count++; 585 | } 586 | else 587 | { 588 | break; 589 | } 590 | } 591 | 592 | if (count > 0) 593 | { 594 | for (var i = 0; i < count; i++) 595 | rcv_queue.Add(rcv_buf[i]); 596 | rcv_buf.RemoveRange(0, count); 597 | } 598 | return repeat; 599 | } 600 | 601 | // Input when you received a low level packet (eg. UDP packet), call it 602 | // regular indicates a regular packet has received(not from FEC) 603 | // 604 | // 'ackNoDelay' will trigger immediate ACK, but surely it will not be efficient in bandwidth 605 | public int Input(byte[] data, int index, int size, bool regular, bool ackNoDelay) 606 | { 607 | var s_una = snd_una; 608 | if (size < IKCP_OVERHEAD) return -1; 609 | 610 | Int32 offset = index; 611 | UInt32 latest = 0; 612 | int flag = 0; 613 | UInt64 inSegs = 0; 614 | bool windowSlides = false; 615 | 616 | if (ikcp_canlog(IKCP_LOG_INPUT)) 617 | { 618 | ikcp_log($"[RI] {size.ToString()} bytes"); 619 | } 620 | 621 | 622 | while (true) 623 | { 624 | UInt32 ts = 0; 625 | UInt32 sn = 0; 626 | UInt32 length = 0; 627 | UInt32 una = 0; 628 | UInt32 conv_ = 0; 629 | UInt32 current = currentMS(); 630 | 631 | UInt16 wnd = 0; 632 | byte cmd = 0; 633 | byte frg = 0; 634 | 635 | if (size - (offset - index) < IKCP_OVERHEAD) break; 636 | 637 | offset += ikcp_decode32u(data, offset, ref conv_); 638 | 639 | if (conv != conv_) return -1; 640 | 641 | offset += ikcp_decode8u(data, offset, ref cmd); 642 | offset += ikcp_decode8u(data, offset, ref frg); 643 | offset += ikcp_decode16u(data, offset, ref wnd); 644 | offset += ikcp_decode32u(data, offset, ref ts); 645 | offset += ikcp_decode32u(data, offset, ref sn); 646 | offset += ikcp_decode32u(data, offset, ref una); 647 | offset += ikcp_decode32u(data, offset, ref length); 648 | 649 | if (size - (offset - index) < length) return -2; 650 | 651 | switch (cmd) 652 | { 653 | case IKCP_CMD_PUSH: 654 | case IKCP_CMD_ACK: 655 | case IKCP_CMD_WASK: 656 | case IKCP_CMD_WINS: 657 | break; 658 | default: 659 | return -3; 660 | } 661 | 662 | // only trust window updates from regular packets. i.e: latest update 663 | if (regular) 664 | { 665 | rmt_wnd = wnd; 666 | } 667 | 668 | if (parse_una(una) > 0) { 669 | windowSlides = true; 670 | } 671 | 672 | shrink_buf(); 673 | 674 | if (IKCP_CMD_ACK == cmd) 675 | { 676 | parse_ack(sn); 677 | parse_fastack(sn, ts); 678 | flag |= 1; 679 | latest = ts; 680 | 681 | if (ikcp_canlog(IKCP_LOG_IN_ACK)) 682 | { 683 | ikcp_log($" input ack: sn={sn.ToString()} ts={ts.ToString()} rtt={_itimediff(current, ts).ToString()} rto={rx_rto.ToString()}"); 684 | } 685 | } 686 | else if (IKCP_CMD_PUSH == cmd) 687 | { 688 | if (ikcp_canlog(IKCP_LOG_IN_DATA)) 689 | { 690 | ikcp_log($" input psh: sn={sn.ToString()} ts={ts.ToString()}"); 691 | } 692 | 693 | var repeat = true; 694 | if (_itimediff(sn, rcv_nxt + rcv_wnd) < 0) 695 | { 696 | ack_push(sn, ts); 697 | if (_itimediff(sn, rcv_nxt) >= 0) 698 | { 699 | var seg = Segment.Get((int)length); 700 | seg.conv = conv_; 701 | seg.cmd = (UInt32)cmd; 702 | seg.frg = (UInt32)frg; 703 | seg.wnd = (UInt32)wnd; 704 | seg.ts = ts; 705 | seg.sn = sn; 706 | seg.una = una; 707 | seg.data.WriteBytes(data, offset, (int)length); 708 | repeat = parse_data(seg); 709 | } 710 | } 711 | } 712 | else if (IKCP_CMD_WASK == cmd) 713 | { 714 | // ready to send back IKCP_CMD_WINS in Ikcp_flush 715 | // tell remote my window size 716 | probe |= IKCP_ASK_TELL; 717 | 718 | if (ikcp_canlog(IKCP_LOG_IN_PROBE)) 719 | { 720 | ikcp_log(" input probe"); 721 | } 722 | } 723 | else if (IKCP_CMD_WINS == cmd) 724 | { 725 | // do nothing 726 | if (ikcp_canlog(IKCP_LOG_IN_WINS)) 727 | { 728 | ikcp_log($" input wins: {wnd.ToString()}"); 729 | } 730 | } 731 | else 732 | { 733 | return -3; 734 | } 735 | 736 | inSegs++; 737 | offset += (int)length; 738 | } 739 | 740 | // update rtt with the latest ts 741 | // ignore the FEC packet 742 | if (flag != 0 && regular) 743 | { 744 | var current = currentMS(); 745 | if (_itimediff(current, latest) >= 0) 746 | { 747 | update_ack(_itimediff(current, latest)); 748 | } 749 | } 750 | 751 | // cwnd update when packet arrived 752 | if (nocwnd == 0) 753 | { 754 | if (_itimediff(snd_una, s_una) > 0) 755 | { 756 | if (cwnd < rmt_wnd) 757 | { 758 | var _mss = mss; 759 | if (cwnd < ssthresh) 760 | { 761 | cwnd++; 762 | incr += _mss; 763 | } 764 | else 765 | { 766 | if (incr < _mss) 767 | { 768 | incr = _mss; 769 | } 770 | incr += (_mss * _mss) / incr + (_mss) / 16; 771 | if ((cwnd + 1) * _mss <= incr) 772 | { 773 | if (_mss > 0) 774 | cwnd = (incr + _mss - 1) / _mss; 775 | else 776 | cwnd = incr + _mss - 1; 777 | } 778 | } 779 | if (cwnd > rmt_wnd) 780 | { 781 | cwnd = rmt_wnd; 782 | incr = rmt_wnd * _mss; 783 | } 784 | } 785 | } 786 | } 787 | 788 | if (windowSlides) // if window has slided, flush 789 | { 790 | Flush(false); 791 | } 792 | else if (ackNoDelay && acklist.Count > 0) // // ack immediately 793 | { 794 | Flush(true); 795 | } 796 | 797 | return 0; 798 | } 799 | 800 | UInt16 wnd_unused() 801 | { 802 | if (rcv_queue.Count < rcv_wnd) 803 | return (UInt16)(rcv_wnd - rcv_queue.Count); 804 | return 0; 805 | } 806 | 807 | int makeSpace(int space, int writeIndex) 808 | { 809 | if (writeIndex + space > mtu) 810 | { 811 | if (ikcp_canlog(IKCP_LOG_OUTPUT)) 812 | { 813 | ikcp_log($"[RO] {writeIndex.ToString()} bytes"); 814 | } 815 | output(buffer, writeIndex); 816 | writeIndex = reserved; 817 | } 818 | return writeIndex; 819 | } 820 | 821 | void flushBuffer(int writeIndex) 822 | { 823 | if (writeIndex > reserved) 824 | { 825 | if (ikcp_canlog(IKCP_LOG_OUTPUT)) 826 | { 827 | ikcp_log($"[RO] {writeIndex.ToString()} bytes"); 828 | } 829 | output(buffer, writeIndex); 830 | } 831 | } 832 | 833 | // flush pending data 834 | public UInt32 Flush(bool ackOnly) 835 | { 836 | var seg = Segment.Get(32); 837 | seg.conv = conv; 838 | seg.cmd = IKCP_CMD_ACK; 839 | seg.wnd = (UInt32)wnd_unused(); 840 | seg.una = rcv_nxt; 841 | 842 | var writeIndex = reserved; 843 | 844 | // flush acknowledges 845 | for (var i = 0; i < acklist.Count; i++) 846 | { 847 | writeIndex = makeSpace(KCP.IKCP_OVERHEAD, writeIndex); 848 | var ack = acklist[i]; 849 | if ( _itimediff(ack.sn, rcv_nxt) >=0 || acklist.Count - 1 == i) 850 | { 851 | seg.sn = ack.sn; 852 | seg.ts = ack.ts; 853 | writeIndex += seg.encode(buffer, writeIndex); 854 | 855 | if (ikcp_canlog(IKCP_LOG_OUT_ACK)) 856 | { 857 | ikcp_log($"output ack: sn={seg.sn.ToString()}"); 858 | } 859 | } 860 | } 861 | acklist.Clear(); 862 | 863 | // flash remain ack segments 864 | if (ackOnly) 865 | { 866 | flushBuffer(writeIndex); 867 | Segment.Put(seg); 868 | return interval; 869 | } 870 | 871 | uint current = 0; 872 | // probe window size (if remote window size equals zero) 873 | if (0 == rmt_wnd) 874 | { 875 | current = currentMS(); 876 | if (0 == probe_wait) 877 | { 878 | probe_wait = IKCP_PROBE_INIT; 879 | ts_probe = current + probe_wait; 880 | } 881 | else 882 | { 883 | if (_itimediff(current, ts_probe) >= 0) 884 | { 885 | if (probe_wait < IKCP_PROBE_INIT) 886 | probe_wait = IKCP_PROBE_INIT; 887 | probe_wait += probe_wait / 2; 888 | if (probe_wait > IKCP_PROBE_LIMIT) 889 | probe_wait = IKCP_PROBE_LIMIT; 890 | ts_probe = current + probe_wait; 891 | probe |= IKCP_ASK_SEND; 892 | } 893 | } 894 | } 895 | else 896 | { 897 | ts_probe = 0; 898 | probe_wait = 0; 899 | } 900 | 901 | // flush window probing commands 902 | if ((probe & IKCP_ASK_SEND) != 0) 903 | { 904 | seg.cmd = IKCP_CMD_WASK; 905 | writeIndex = makeSpace(IKCP_OVERHEAD, writeIndex); 906 | writeIndex += seg.encode(buffer, writeIndex); 907 | } 908 | 909 | if ((probe & IKCP_ASK_TELL) != 0) 910 | { 911 | seg.cmd = IKCP_CMD_WINS; 912 | writeIndex = makeSpace(IKCP_OVERHEAD, writeIndex); 913 | writeIndex += seg.encode(buffer, writeIndex); 914 | } 915 | 916 | probe = 0; 917 | 918 | // calculate window size 919 | var cwnd_ = _imin_(snd_wnd, rmt_wnd); 920 | if (0 == nocwnd) 921 | cwnd_ = _imin_(cwnd, cwnd_); 922 | 923 | // sliding window, controlled by snd_nxt && sna_una+cwnd 924 | var newSegsCount = 0; 925 | for (var k = 0; k < snd_queue.Count; k++) 926 | { 927 | if (_itimediff(snd_nxt, snd_una + cwnd_) >= 0) 928 | break; 929 | 930 | var newseg = snd_queue[k]; 931 | newseg.conv = conv; 932 | newseg.cmd = IKCP_CMD_PUSH; 933 | newseg.sn = snd_nxt; 934 | snd_buf.Add(newseg); 935 | snd_nxt++; 936 | newSegsCount++; 937 | } 938 | 939 | if (newSegsCount > 0) 940 | { 941 | snd_queue.RemoveRange(0, newSegsCount); 942 | } 943 | 944 | // calculate resent 945 | var resent = (UInt32)fastresend; 946 | if (fastresend <= 0) resent = 0xffffffff; 947 | 948 | // check for retransmissions 949 | current = currentMS(); 950 | UInt64 change = 0; UInt64 lostSegs = 0; UInt64 fastRetransSegs = 0; UInt64 earlyRetransSegs = 0; 951 | var minrto = (Int32)interval; 952 | 953 | for (var k = 0; k < snd_buf.Count; k++) 954 | { 955 | var segment = snd_buf[k]; 956 | var needsend = false; 957 | if (segment.acked == 1) 958 | continue; 959 | if (segment.xmit == 0) // initial transmit 960 | { 961 | needsend = true; 962 | segment.rto = rx_rto; 963 | segment.resendts = current + segment.rto; 964 | } 965 | else if (segment.fastack >= resent) // fast retransmit 966 | { 967 | needsend = true; 968 | segment.fastack = 0; 969 | segment.rto = rx_rto; 970 | segment.resendts = current + segment.rto; 971 | change++; 972 | fastRetransSegs++; 973 | } 974 | else if (segment.fastack > 0 && newSegsCount == 0) // early retransmit 975 | { 976 | needsend = true; 977 | segment.fastack = 0; 978 | segment.rto = rx_rto; 979 | segment.resendts = current + segment.rto; 980 | change++; 981 | earlyRetransSegs++; 982 | } 983 | else if (_itimediff(current, segment.resendts) >= 0) // RTO 984 | { 985 | needsend = true; 986 | if (nodelay == 0) 987 | segment.rto += rx_rto; 988 | else 989 | segment.rto += rx_rto / 2; 990 | segment.fastack = 0; 991 | segment.resendts = current + segment.rto; 992 | lostSegs++; 993 | } 994 | 995 | if (needsend) 996 | { 997 | current = CurrentMS; 998 | segment.xmit++; 999 | segment.ts = current; 1000 | segment.wnd = seg.wnd; 1001 | segment.una = seg.una; 1002 | 1003 | var need = IKCP_OVERHEAD + segment.data.ReadableBytes; 1004 | writeIndex = makeSpace(need, writeIndex); 1005 | writeIndex += segment.encode(buffer, writeIndex); 1006 | Buffer.BlockCopy(segment.data.RawBuffer, segment.data.ReaderIndex, buffer, writeIndex, segment.data.ReadableBytes); 1007 | writeIndex += segment.data.ReadableBytes; 1008 | 1009 | if (segment.xmit >= dead_link) 1010 | { 1011 | state = 0xFFFFFFFF; 1012 | } 1013 | 1014 | if (ikcp_canlog(IKCP_LOG_OUT_DATA)) 1015 | { 1016 | ikcp_log($"output psh: sn={segment.sn.ToString()} ts={segment.ts.ToString()} resendts={segment.resendts.ToString()} rto={segment.rto.ToString()} fastack={segment.fastack.ToString()}, xmit={segment.xmit.ToString()}"); 1017 | } 1018 | } 1019 | 1020 | // get the nearest rto 1021 | var _rto = _itimediff(segment.resendts, current); 1022 | if (_rto > 0 && _rto < minrto) 1023 | { 1024 | minrto = _rto; 1025 | } 1026 | } 1027 | 1028 | // flash remain segments 1029 | flushBuffer(writeIndex); 1030 | 1031 | // cwnd update 1032 | if (nocwnd == 0) 1033 | { 1034 | // update ssthresh 1035 | // rate halving, https://tools.ietf.org/html/rfc6937 1036 | if (change > 0) 1037 | { 1038 | var inflght = snd_nxt - snd_una; 1039 | ssthresh = inflght / 2; 1040 | if (ssthresh < IKCP_THRESH_MIN) 1041 | ssthresh = IKCP_THRESH_MIN; 1042 | cwnd = ssthresh + resent; 1043 | incr = cwnd * mss; 1044 | } 1045 | 1046 | // congestion control, https://tools.ietf.org/html/rfc5681 1047 | if (lostSegs > 0) 1048 | { 1049 | ssthresh = cwnd / 2; 1050 | if (ssthresh < IKCP_THRESH_MIN) 1051 | ssthresh = IKCP_THRESH_MIN; 1052 | cwnd = 1; 1053 | incr = mss; 1054 | } 1055 | 1056 | if (cwnd < 1) 1057 | { 1058 | cwnd = 1; 1059 | incr = mss; 1060 | } 1061 | } 1062 | 1063 | Segment.Put(seg); 1064 | return (UInt32)minrto; 1065 | } 1066 | 1067 | // update state (call it repeatedly, every 10ms-100ms), or you can ask 1068 | // ikcp_check when to call it again (without ikcp_input/_send calling). 1069 | // 'current' - current timestamp in millisec. 1070 | public void Update() 1071 | { 1072 | var current = currentMS(); 1073 | 1074 | if (0 == updated) 1075 | { 1076 | updated = 1; 1077 | ts_flush = current; 1078 | } 1079 | 1080 | var slap = _itimediff(current, ts_flush); 1081 | 1082 | if (slap >= 10000 || slap < -10000) 1083 | { 1084 | ts_flush = current; 1085 | slap = 0; 1086 | } 1087 | 1088 | if (slap >= 0) 1089 | { 1090 | ts_flush += interval; 1091 | if (_itimediff(current, ts_flush) >= 0) 1092 | ts_flush = current + interval; 1093 | Flush(false); 1094 | } 1095 | } 1096 | 1097 | // Determine when should you invoke ikcp_update: 1098 | // returns when you should invoke ikcp_update in millisec, if there 1099 | // is no ikcp_input/_send calling. you can call ikcp_update in that 1100 | // time, instead of call update repeatly. 1101 | // Important to reduce unnacessary ikcp_update invoking. use it to 1102 | // schedule ikcp_update (eg. implementing an epoll-like mechanism, 1103 | // or optimize ikcp_update when handling massive kcp connections) 1104 | public UInt32 Check() 1105 | { 1106 | var current = currentMS(); 1107 | 1108 | var ts_flush_ = ts_flush; 1109 | var tm_flush_ = 0x7fffffff; 1110 | var tm_packet = 0x7fffffff; 1111 | var minimal = 0; 1112 | 1113 | if (updated == 0) 1114 | return current; 1115 | 1116 | if (_itimediff(current, ts_flush_) >= 10000 || _itimediff(current, ts_flush_) < -10000) 1117 | ts_flush_ = current; 1118 | 1119 | if (_itimediff(current, ts_flush_) >= 0) 1120 | return current; 1121 | 1122 | tm_flush_ = (int)_itimediff(ts_flush_, current); 1123 | 1124 | foreach (var seg in snd_buf) 1125 | { 1126 | var diff = _itimediff(seg.resendts, current); 1127 | if (diff <= 0) 1128 | return current; 1129 | if (diff < tm_packet) 1130 | tm_packet = (int)diff; 1131 | } 1132 | 1133 | minimal = (int)tm_packet; 1134 | if (tm_packet >= tm_flush_) 1135 | minimal = (int)tm_flush_; 1136 | if (minimal >= interval) 1137 | minimal = (int)interval; 1138 | 1139 | return current + (UInt32)minimal; 1140 | } 1141 | 1142 | // change MTU size, default is 1400 1143 | public int SetMtu(Int32 mtu_) 1144 | { 1145 | if (mtu_ < 50 || mtu_ < (Int32)IKCP_OVERHEAD) 1146 | return -1; 1147 | if (reserved >= (int)(mtu - IKCP_OVERHEAD) || reserved < 0) 1148 | return -1; 1149 | 1150 | var buffer_ = new byte[mtu_]; 1151 | if (null == buffer_) 1152 | return -2; 1153 | 1154 | mtu = (UInt32)mtu_; 1155 | mss = mtu - IKCP_OVERHEAD - (UInt32)reserved; 1156 | buffer = buffer_; 1157 | return 0; 1158 | } 1159 | 1160 | // fastest: ikcp_nodelay(kcp, 1, 20, 2, 1) 1161 | // nodelay: 0:disable(default), 1:enable 1162 | // interval: internal update timer interval in millisec, default is 100ms 1163 | // resend: 0:disable fast resend(default), 1:enable fast resend 1164 | // nc: 0:normal congestion control(default), 1:disable congestion control 1165 | public int NoDelay(int nodelay_, int interval_, int resend_, int nc_) 1166 | { 1167 | 1168 | if (nodelay_ >= 0) 1169 | { 1170 | nodelay = (UInt32)nodelay_; 1171 | if (nodelay_ != 0) 1172 | rx_minrto = IKCP_RTO_NDL; 1173 | else 1174 | rx_minrto = IKCP_RTO_MIN; 1175 | } 1176 | 1177 | if (interval_ >= 0) 1178 | { 1179 | if (interval_ > 5000) 1180 | interval_ = 5000; 1181 | else if (interval_ < 10) 1182 | interval_ = 10; 1183 | interval = (UInt32)interval_; 1184 | } 1185 | 1186 | if (resend_ >= 0) 1187 | fastresend = resend_; 1188 | 1189 | if (nc_ >= 0) 1190 | nocwnd = nc_; 1191 | 1192 | return 0; 1193 | } 1194 | 1195 | // set maximum window size: sndwnd=32, rcvwnd=32 by default 1196 | public int WndSize(int sndwnd, int rcvwnd) 1197 | { 1198 | if (sndwnd > 0) 1199 | snd_wnd = (UInt32)sndwnd; 1200 | 1201 | if (rcvwnd > 0) 1202 | rcv_wnd = (UInt32)rcvwnd; 1203 | return 0; 1204 | } 1205 | 1206 | public bool ReserveBytes(int reservedSize) 1207 | { 1208 | if (reservedSize >= (mtu - IKCP_OVERHEAD) || reservedSize < 0) 1209 | return false; 1210 | 1211 | reserved = reservedSize; 1212 | mss = mtu - IKCP_OVERHEAD - (uint)(reservedSize); 1213 | return true; 1214 | } 1215 | 1216 | public void SetStreamMode(bool enabled) 1217 | { 1218 | stream = enabled ? 1 : 0; 1219 | } 1220 | 1221 | bool ikcp_canlog(int mask) 1222 | { 1223 | if ((mask & logmask) == 0 || writelog == null) return false; 1224 | return true; 1225 | } 1226 | 1227 | public void SetLogger(Action logger) 1228 | { 1229 | writelog = logger; 1230 | } 1231 | 1232 | public void SetLogMask(int mask) 1233 | { 1234 | logmask = mask; 1235 | } 1236 | 1237 | void ikcp_log(string logStr) 1238 | { 1239 | writelog?.Invoke(logStr); 1240 | } 1241 | } 1242 | } -------------------------------------------------------------------------------- /KCP/UDPSession.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Net; 5 | using System.Net.Sockets; 6 | using System.Text; 7 | 8 | namespace KcpProject 9 | { 10 | class UDPSession 11 | { 12 | private Socket mSocket = null; 13 | private KCP mKCP = null; 14 | 15 | private ByteBuffer mRecvBuffer = ByteBuffer.Allocate(1024 * 32); 16 | private UInt32 mNextUpdateTime = 0; 17 | 18 | public bool IsConnected { get { return mSocket != null && mSocket.Connected; } } 19 | public bool WriteDelay { get; set; } 20 | public bool AckNoDelay { get; set; } 21 | 22 | public IPEndPoint RemoteAddress { get; private set; } 23 | public IPEndPoint LocalAddress { get; private set; } 24 | 25 | private DateTime startDt = DateTime.Now; 26 | const int logmask = KCP.IKCP_LOG_IN_ACK | KCP.IKCP_LOG_OUT_ACK | KCP.IKCP_LOG_IN_DATA | KCP.IKCP_LOG_OUT_DATA; 27 | 28 | public void Connect(string host, int port) 29 | { 30 | IPHostEntry hostEntry = Dns.GetHostEntry(host); 31 | if (hostEntry.AddressList.Length == 0) 32 | { 33 | throw new Exception("Unable to resolve host: " + host); 34 | } 35 | var endpoint = hostEntry.AddressList[0]; 36 | mSocket = new Socket(endpoint.AddressFamily, SocketType.Dgram, ProtocolType.Udp); 37 | mSocket.Connect(endpoint, port); 38 | RemoteAddress = (IPEndPoint)mSocket.RemoteEndPoint; 39 | LocalAddress = (IPEndPoint)mSocket.LocalEndPoint; 40 | mKCP = new KCP((uint)(new Random().Next(1, Int32.MaxValue)), rawSend); 41 | // normal: 0, 40, 2, 1 42 | // fast: 0, 30, 2, 1 43 | // fast2: 1, 20, 2, 1 44 | // fast3: 1, 10, 2, 1 45 | mKCP.NoDelay(0, 30, 2, 1); 46 | mKCP.SetStreamMode(true); 47 | 48 | // Log 49 | //mKCP.SetLogger(Log); 50 | //mKCP.SetLogMask(logmask); 51 | 52 | mRecvBuffer.Clear(); 53 | } 54 | 55 | public void Close() 56 | { 57 | if (mSocket != null) { 58 | mSocket.Close(); 59 | mSocket = null; 60 | mRecvBuffer.Clear(); 61 | } 62 | } 63 | 64 | private void rawSend(byte[] data, int length) 65 | { 66 | if (mSocket != null) { 67 | mSocket.Send(data, length, SocketFlags.None); 68 | } 69 | } 70 | 71 | public int Send(byte[] data, int index, int length) 72 | { 73 | if (mSocket == null) 74 | return -1; 75 | 76 | var waitsnd = mKCP.WaitSnd; 77 | if (waitsnd < mKCP.SndWnd && waitsnd < mKCP.RmtWnd) { 78 | 79 | var sendBytes = 0; 80 | do { 81 | var n = Math.Min((int)mKCP.Mss, length - sendBytes); 82 | mKCP.Send(data, index + sendBytes, n); 83 | sendBytes += n; 84 | } while (sendBytes < length); 85 | 86 | waitsnd = mKCP.WaitSnd; 87 | if (waitsnd >= mKCP.SndWnd || waitsnd >= mKCP.RmtWnd || !WriteDelay) { 88 | mKCP.Flush(false); 89 | } 90 | 91 | return length; 92 | } 93 | 94 | return 0; 95 | } 96 | 97 | public int Recv(byte[] data, int index, int length) 98 | { 99 | // 上次剩下的部分 100 | if (mRecvBuffer.ReadableBytes > 0) { 101 | var recvBytes = Math.Min(mRecvBuffer.ReadableBytes, length); 102 | Buffer.BlockCopy(mRecvBuffer.RawBuffer, mRecvBuffer.ReaderIndex, data, index, recvBytes); 103 | mRecvBuffer.ReaderIndex += recvBytes; 104 | // 读完重置读写指针 105 | if (mRecvBuffer.ReaderIndex == mRecvBuffer.WriterIndex) { 106 | mRecvBuffer.Clear(); 107 | } 108 | return recvBytes; 109 | } 110 | 111 | if (mSocket == null) 112 | return -1; 113 | 114 | if (!mSocket.Poll(0, SelectMode.SelectRead)) { 115 | return 0; 116 | } 117 | 118 | var rn = 0; 119 | try { 120 | rn = mSocket.Receive(mRecvBuffer.RawBuffer, mRecvBuffer.WriterIndex, mRecvBuffer.WritableBytes, SocketFlags.None); 121 | } catch(Exception ex) { 122 | Console.WriteLine(ex); 123 | rn = -1; 124 | } 125 | 126 | if (rn <= 0) { 127 | return rn; 128 | } 129 | mRecvBuffer.WriterIndex += rn; 130 | 131 | var inputN = mKCP.Input(mRecvBuffer.RawBuffer, mRecvBuffer.ReaderIndex, mRecvBuffer.ReadableBytes, true, AckNoDelay); 132 | if (inputN < 0) { 133 | mRecvBuffer.Clear(); 134 | return inputN; 135 | } 136 | mRecvBuffer.Clear(); 137 | 138 | // 读完所有完整的消息 139 | for (;;) { 140 | var size = mKCP.PeekSize(); 141 | if (size < 0) break; 142 | 143 | mRecvBuffer.EnsureWritableBytes(size); 144 | 145 | var n = mKCP.Recv(mRecvBuffer.RawBuffer, mRecvBuffer.WriterIndex, size); 146 | if (n > 0) mRecvBuffer.WriterIndex += n; 147 | } 148 | 149 | // 有数据待接收 150 | if (mRecvBuffer.ReadableBytes > 0) { 151 | return Recv(data, index, length); 152 | } 153 | 154 | return 0; 155 | } 156 | 157 | public void Update() 158 | { 159 | if (mSocket == null) 160 | return; 161 | 162 | if (0 == mNextUpdateTime || mKCP.CurrentMS >= mNextUpdateTime) 163 | { 164 | mKCP.Update(); 165 | mNextUpdateTime = mKCP.Check(); 166 | } 167 | } 168 | 169 | public void Log(string str) 170 | { 171 | DateTime now = DateTime.Now; 172 | int t = (int)(now - startDt).TotalMilliseconds; 173 | Console.WriteLine($"[{t.ToString().PadLeft(10, ' ')}] {str}"); 174 | } 175 | } 176 | } 177 | -------------------------------------------------------------------------------- /KcpProject.csproj: -------------------------------------------------------------------------------- 1 |  2 | 3 | 4 | 5 | Debug 6 | AnyCPU 7 | {8C02F001-6134-4BC1-AD24-B6F3B458C929} 8 | Exe 9 | Properties 10 | KcpProject 11 | KcpProject 12 | v3.5 13 | 512 14 | 15 | 16 | AnyCPU 17 | true 18 | full 19 | false 20 | bin\Debug\ 21 | DEBUG;TRACE 22 | prompt 23 | 4 24 | 25 | 26 | AnyCPU 27 | pdbonly 28 | true 29 | bin\Release\ 30 | TRACE 31 | prompt 32 | 4 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 | 53 | 54 | 61 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 limpo1989 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 | -------------------------------------------------------------------------------- /Properties/AssemblyInfo.cs: -------------------------------------------------------------------------------- 1 | using System.Reflection; 2 | using System.Runtime.CompilerServices; 3 | using System.Runtime.InteropServices; 4 | 5 | // General Information about an assembly is controlled through the following 6 | // set of attributes. Change these attribute values to modify the information 7 | // associated with an assembly. 8 | [assembly: AssemblyTitle("KcpProject")] 9 | [assembly: AssemblyDescription("")] 10 | [assembly: AssemblyConfiguration("")] 11 | [assembly: AssemblyCompany("Microsoft")] 12 | [assembly: AssemblyProduct("KcpProject")] 13 | [assembly: AssemblyCopyright("Copyright © Microsoft 2016")] 14 | [assembly: AssemblyTrademark("")] 15 | [assembly: AssemblyCulture("")] 16 | 17 | // Setting ComVisible to false makes the types in this assembly not visible 18 | // to COM components. If you need to access a type in this assembly from 19 | // COM, set the ComVisible attribute to true on that type. 20 | [assembly: ComVisible(false)] 21 | 22 | // The following GUID is for the ID of the typelib if this project is exposed to COM 23 | [assembly: Guid("5ae62a35-f172-411a-b95b-502af9dfd222")] 24 | 25 | // Version information for an assembly consists of the following four values: 26 | // 27 | // Major Version 28 | // Minor Version 29 | // Build Number 30 | // Revision 31 | // 32 | // You can specify all the values or you can default the Build and Revision Numbers 33 | // by using the '*' as shown below: 34 | // [assembly: AssemblyVersion("1.0.*")] 35 | [assembly: AssemblyVersion("1.0.0.0")] 36 | [assembly: AssemblyFileVersion("1.0.0.0")] 37 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # kcp-csharp 2 | KCP - A Fast and Reliable ARQ Protocol 3 | 4 | ## TODO 5 | - [ ] FEC(Forward Error Correction) Based on Reed-Solomon Codes 6 | - [ ] Packet level encryption 7 | 8 | ## Links 9 | 1. skywind3000 [KCP](https://github.com/skywind3000/kcp) 10 | 2. xtaci [kcp-go](https://github.com/xtaci/kcp-go) 11 | -------------------------------------------------------------------------------- /Sample/Main.cs: -------------------------------------------------------------------------------- 1 | using System; 2 | using System.Collections.Generic; 3 | using System.Linq; 4 | using System.Text; 5 | using System.Threading; 6 | 7 | namespace KcpProject.Sample 8 | { 9 | class Program 10 | { 11 | static void Main(string[] args) 12 | { 13 | var connection = new UDPSession(); 14 | connection.AckNoDelay = true; 15 | connection.WriteDelay = false; 16 | 17 | connection.Connect("127.0.0.1", 4444); 18 | 19 | var stopSend = false; 20 | var buffer = new byte[1500]; 21 | var counter = 0; 22 | var sendBytes = 0; 23 | var recvBytes = 0; 24 | 25 | while (true) { 26 | connection.Update(); 27 | 28 | if (!stopSend) { 29 | //firstSend = false; 30 | // Console.WriteLine("Write Message..."); 31 | //var text = Encoding.UTF8.GetBytes(string.Format("Hello KCP: {0}", ++counter)); 32 | var sent = connection.Send(buffer, 0, buffer.Length); 33 | if (sent < 0) { 34 | Console.WriteLine("Write message failed."); 35 | break; 36 | } 37 | 38 | if (sent > 0) 39 | { 40 | counter++; 41 | sendBytes += buffer.Length; 42 | if (counter >= 500) 43 | stopSend = true; 44 | } 45 | } 46 | 47 | var n = connection.Recv(buffer, 0, buffer.Length); 48 | if (n == 0) 49 | { 50 | Thread.Sleep(10); 51 | continue; 52 | } 53 | else if (n < 0) 54 | { 55 | Console.WriteLine("Receive Message failed."); 56 | break; 57 | } 58 | else { 59 | recvBytes += n; 60 | Console.WriteLine($"{recvBytes} / {sendBytes}"); 61 | } 62 | 63 | //var resp = Encoding.UTF8.GetString(buffer, 0, n); 64 | //Console.WriteLine("Received Message: " + resp); 65 | } 66 | } 67 | } 68 | } 69 | --------------------------------------------------------------------------------