版權聲明:本文為原創文章,轉載請聲明http://www.cnblogs.com/unityExplorer/p/6977935.html
上一篇主要說的是protobuf字節流的序列化和解析,將protobuf對象序列化為字節流后雖然可以直接傳遞,但是實際在項目中卻不可能真的只是傳遞protobuf字節流,因為socket的tcp通訊中會出現幾個很常見的問題,就是粘包和少包。所謂粘包,簡單點說就是socket會將多個較小的包合並到一起發送。因為tcp是面向連接的,發送端為了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將多次間隔較小且數據量小的數據,合並成一個大的數據塊,然后進行封包。少包則是指緩存區滿后,soket將不完整的包發送到接收端(按我的理解,粘包和少包其實是一個問題)。這樣接收端一次接收到的數據就有可能是多個包,為了解決這個問題,在發送數據之前,需要將包的長度也發送出去。於是,包的結構就應該是 消息長度+消息內容。
這一篇,就來說說數據的拼接,干貨來了
首先的拼接數據包
1 /// <summary> 2 /// 構建消息數據包 3 /// </summary> 4 /// <param name="protobufModel"></param> 5 byte[] BuildPackage(IExtensible protobufModel) 6 { 7 if (protobufModel != null) 8 { 9 byte[] b = ProtobufSerilizer.Serialize(protobufModel); 10 11 ByteBuffer buf = ByteBuffer.Allocate(b.Length + 4); 12 buf.WriteInt(b.Length); 13 buf.WriteBytes(b); 14 return buf.GetBytes(); 15 } 16 return null; 17 }
代碼中使用的ByteBuffer工具java中有提供,但是c#中是沒有的,源碼摘至https://www.oschina.net/code/snippet_42170_37516,不過作者並未在工具中添加獲取所有字節碼的方法,所以自己添加了一個GetBytes()方法
1 using System; 2 using System.Collections.Generic; 3 4 /// <summary> 5 /// 字節緩沖處理類,本類僅處理大字節序 6 /// 警告,本類非線程安全 7 /// </summary> 8 public class ByteBuffer 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<ByteBuffer> pool = new List<ByteBuffer>(); 25 private static int poolMaxCount = 200; 26 //此對象是否池化 27 private bool isPool = false; 28 29 /// <summary> 30 /// 構造方法 31 /// </summary> 32 /// <param name="capacity">初始容量</param> 33 private ByteBuffer(int capacity) 34 { 35 buf = new byte[capacity]; 36 this.capacity = capacity; 37 } 38 39 /// <summary> 40 /// 構造方法 41 /// </summary> 42 /// <param name="bytes">初始字節數組</param> 43 private ByteBuffer(byte[] bytes) 44 { 45 buf = bytes; 46 this.capacity = bytes.Length; 47 this.readIndex = 0; 48 this.writeIndex = bytes.Length + 1; 49 } 50 51 /// <summary> 52 /// 構建一個capacity長度的字節緩存區ByteBuffer對象 53 /// </summary> 54 /// <param name="capacity">初始容量</param> 55 /// <returns>ByteBuffer對象</returns> 56 public static ByteBuffer Allocate(int capacity) 57 { 58 return new ByteBuffer(capacity); 59 } 60 61 /// <summary> 62 /// 構建一個以bytes為字節緩存區的ByteBuffer對象,一般不推薦使用 63 /// </summary> 64 /// <param name="bytes">初始字節數組</param> 65 /// <returns>ByteBuffer對象</returns> 66 public static ByteBuffer Allocate(byte[] bytes) 67 { 68 return new ByteBuffer(bytes); 69 } 70 71 /// <summary> 72 /// 獲取一個池化的ByteBuffer對象,池化的對象必須在調用Dispose后才會推入池中,否則此方法等同於Allocate(int capacity)方法,此方法為線程安全的 73 /// </summary> 74 /// <param name="capacity">ByteBuffer對象的初始容量大小,如果緩存池中沒有對象,則對象的容量大小為此值,否則為池中對象的實際容量值</param> 75 /// <returns></returns> 76 public static ByteBuffer GetFromPool(int capacity) 77 { 78 lock (pool) 79 { 80 ByteBuffer bbuf; 81 if (pool.Count == 0) 82 { 83 bbuf = Allocate(capacity); 84 bbuf.isPool = true; 85 return bbuf; 86 } 87 int lastIndex = pool.Count - 1; 88 bbuf = pool[lastIndex]; 89 pool.RemoveAt(lastIndex); 90 if (!bbuf.isPool) 91 { 92 bbuf.isPool = true; 93 } 94 return bbuf; 95 } 96 } 97 98 /// <summary> 99 /// 根據length長度,確定大於此leng的最近的2次方數,如length=7,則返回值為8 100 /// </summary> 101 /// <param name="length">參考容量</param> 102 /// <returns>比參考容量大的最接近的2次方數</returns> 103 private int FixLength(int length) 104 { 105 int n = 2; 106 int b = 2; 107 while (b < length) 108 { 109 b = 2 << n; 110 n++; 111 } 112 return b; 113 } 114 115 /// <summary> 116 /// 翻轉字節數組,如果本地字節序列為低字節序列,則進行翻轉以轉換為高字節序列 117 /// </summary> 118 /// <param name="bytes">待轉為高字節序的字節數組</param> 119 /// <returns>高字節序列的字節數組</returns> 120 private byte[] flip(byte[] bytes) 121 { 122 if (BitConverter.IsLittleEndian) 123 { 124 Array.Reverse(bytes); 125 } 126 return bytes; 127 } 128 129 /// <summary> 130 /// 確定內部字節緩存數組的大小 131 /// </summary> 132 /// <param name="currLen">當前容量</param> 133 /// <param name="futureLen">將來的容量</param> 134 /// <returns>將來的容量</returns> 135 private int FixSizeAndReset(int currLen, int futureLen) 136 { 137 if (futureLen > currLen) 138 { 139 //以原大小的2次方數的兩倍確定內部字節緩存區大小 140 int size = FixLength(currLen) * 2; 141 if (futureLen > size) 142 { 143 //以將來的大小的2次方的兩倍確定內部字節緩存區大小 144 size = FixLength(futureLen) * 2; 145 } 146 byte[] newbuf = new byte[size]; 147 Array.Copy(buf, 0, newbuf, 0, currLen); 148 buf = newbuf; 149 capacity = newbuf.Length; 150 } 151 return futureLen; 152 } 153 154 /// <summary> 155 /// 將bytes字節數組從startIndex開始的length字節寫入到此緩存區 156 /// </summary> 157 /// <param name="bytes">待寫入的字節數據</param> 158 /// <param name="startIndex">寫入的開始位置</param> 159 /// <param name="length">寫入的長度</param> 160 public void WriteBytes(byte[] bytes, int startIndex, int length) 161 { 162 int offset = length - startIndex; 163 if (offset <= 0) return; 164 int total = offset + writeIndex; 165 int len = buf.Length; 166 FixSizeAndReset(len, total); 167 for (int i = writeIndex, j = startIndex; i < total; i++, j++) 168 { 169 buf[i] = bytes[j]; 170 } 171 writeIndex = total; 172 } 173 174 /// <summary> 175 /// 將字節數組中從0到length的元素寫入緩存區 176 /// </summary> 177 /// <param name="bytes">待寫入的字節數據</param> 178 /// <param name="length">寫入的長度</param> 179 public void WriteBytes(byte[] bytes, int length) 180 { 181 WriteBytes(bytes, 0, length); 182 } 183 184 /// <summary> 185 /// 將字節數組全部寫入緩存區 186 /// </summary> 187 /// <param name="bytes">待寫入的字節數據</param> 188 public void WriteBytes(byte[] bytes) 189 { 190 WriteBytes(bytes, bytes.Length); 191 } 192 193 /// <summary> 194 /// 將一個ByteBuffer的有效字節區寫入此緩存區中 195 /// </summary> 196 /// <param name="buffer">待寫入的字節緩存區</param> 197 public void Write(ByteBuffer buffer) 198 { 199 if (buffer == null) return; 200 if (buffer.ReadableBytes() <= 0) return; 201 WriteBytes(buffer.ToArray()); 202 } 203 204 /// <summary> 205 /// 寫入一個int16數據 206 /// </summary> 207 /// <param name="value">short數據</param> 208 public void WriteShort(short value) 209 { 210 WriteBytes(flip(BitConverter.GetBytes(value))); 211 } 212 213 /// <summary> 214 /// 寫入一個ushort數據 215 /// </summary> 216 /// <param name="value">ushort數據</param> 217 public void WriteUshort(ushort value) 218 { 219 WriteBytes(flip(BitConverter.GetBytes(value))); 220 } 221 222 /// <summary> 223 /// 寫入一個int32數據 224 /// </summary> 225 /// <param name="value">int數據</param> 226 public void WriteInt(int value) 227 { 228 //byte[] array = new byte[4]; 229 //for (int i = 3; i >= 0; i--) 230 //{ 231 // array[i] = (byte)(value & 0xff); 232 // value = value >> 8; 233 //} 234 //Array.Reverse(array); 235 //Write(array); 236 WriteBytes(flip(BitConverter.GetBytes(value))); 237 } 238 239 /// <summary> 240 /// 寫入一個uint32數據 241 /// </summary> 242 /// <param name="value">uint數據</param> 243 public void WriteUint(uint value) 244 { 245 WriteBytes(flip(BitConverter.GetBytes(value))); 246 } 247 248 /// <summary> 249 /// 寫入一個int64數據 250 /// </summary> 251 /// <param name="value">long數據</param> 252 public void WriteLong(long value) 253 { 254 WriteBytes(flip(BitConverter.GetBytes(value))); 255 } 256 257 /// <summary> 258 /// 寫入一個uint64數據 259 /// </summary> 260 /// <param name="value">ulong數據</param> 261 public void WriteUlong(ulong value) 262 { 263 WriteBytes(flip(BitConverter.GetBytes(value))); 264 } 265 266 /// <summary> 267 /// 寫入一個float數據 268 /// </summary> 269 /// <param name="value">float數據</param> 270 public void WriteFloat(float value) 271 { 272 WriteBytes(flip(BitConverter.GetBytes(value))); 273 } 274 275 /// <summary> 276 /// 寫入一個byte數據 277 /// </summary> 278 /// <param name="value">byte數據</param> 279 public void WriteByte(byte value) 280 { 281 int afterLen = writeIndex + 1; 282 int len = buf.Length; 283 FixSizeAndReset(len, afterLen); 284 buf[writeIndex] = value; 285 writeIndex = afterLen; 286 } 287 288 /// <summary> 289 /// 寫入一個byte數據 290 /// </summary> 291 /// <param name="value">byte數據</param> 292 public void WriteByte(int value) 293 { 294 byte b = (byte)value; 295 WriteByte(b); 296 } 297 298 /// <summary> 299 /// 寫入一個double類型數據 300 /// </summary> 301 /// <param name="value">double數據</param> 302 public void WriteDouble(double value) 303 { 304 WriteBytes(flip(BitConverter.GetBytes(value))); 305 } 306 307 /// <summary> 308 /// 寫入一個字符 309 /// </summary> 310 /// <param name="value"></param> 311 public void WriteChar(char value) 312 { 313 WriteBytes(flip(BitConverter.GetBytes(value))); 314 } 315 316 /// <summary> 317 /// 寫入一個布爾型數據 318 /// </summary> 319 /// <param name="value"></param> 320 public void WriteBoolean(bool value) 321 { 322 WriteBytes(flip(BitConverter.GetBytes(value))); 323 } 324 325 /// <summary> 326 /// 讀取一個字節 327 /// </summary> 328 /// <returns>字節數據</returns> 329 public byte ReadByte() 330 { 331 byte b = buf[readIndex]; 332 readIndex++; 333 return b; 334 } 335 336 /// <summary> 337 /// 讀取一個字節並轉為int類型的數據 338 /// </summary> 339 /// <returns>int數據</returns> 340 public int ReadByteToInt() 341 { 342 byte b = ReadByte(); 343 return (int)b; 344 } 345 346 /// <summary> 347 /// 獲取從index索引處開始len長度的字節 348 /// </summary> 349 /// <param name="index"></param> 350 /// <param name="len"></param> 351 /// <returns></returns> 352 private byte[] Get(int index, int len) 353 { 354 byte[] bytes = new byte[len]; 355 Array.Copy(buf, index, bytes, 0, len); 356 return flip(bytes); 357 } 358 359 /// <summary> 360 /// 從讀取索引位置開始讀取len長度的字節數組 361 /// </summary> 362 /// <param name="len">待讀取的字節長度</param> 363 /// <returns>字節數組</returns> 364 private byte[] Read(int len) 365 { 366 byte[] bytes = Get(readIndex, len); 367 readIndex += len; 368 return bytes; 369 } 370 371 /// <summary> 372 /// 讀取一個uint16數據 373 /// </summary> 374 /// <returns>ushort數據</returns> 375 public ushort ReadUshort() 376 { 377 return BitConverter.ToUInt16(Read(2), 0); 378 } 379 380 /// <summary> 381 /// 讀取一個int16數據 382 /// </summary> 383 /// <returns>short數據</returns> 384 public short ReadShort() 385 { 386 return BitConverter.ToInt16(Read(2), 0); 387 } 388 389 /// <summary> 390 /// 讀取一個uint32數據 391 /// </summary> 392 /// <returns>uint數據</returns> 393 public uint ReadUint() 394 { 395 return BitConverter.ToUInt32(Read(4), 0); 396 } 397 398 /// <summary> 399 /// 讀取一個int32數據 400 /// </summary> 401 /// <returns>int數據</returns> 402 public int ReadInt() 403 { 404 return BitConverter.ToInt32(Read(4), 0); 405 } 406 407 /// <summary> 408 /// 讀取一個uint64數據 409 /// </summary> 410 /// <returns>ulong數據</returns> 411 public ulong ReadUlong() 412 { 413 return BitConverter.ToUInt64(Read(8), 0); 414 } 415 416 /// <summary> 417 /// 讀取一個long數據 418 /// </summary> 419 /// <returns>long數據</returns> 420 public long ReadLong() 421 { 422 return BitConverter.ToInt64(Read(8), 0); 423 } 424 425 /// <summary> 426 /// 讀取一個float數據 427 /// </summary> 428 /// <returns>float數據</returns> 429 public float ReadFloat() 430 { 431 return BitConverter.ToSingle(Read(4), 0); 432 } 433 434 /// <summary> 435 /// 讀取一個double數據 436 /// </summary> 437 /// <returns>double數據</returns> 438 public double ReadDouble() 439 { 440 return BitConverter.ToDouble(Read(8), 0); 441 } 442 443 /// <summary> 444 /// 讀取一個字符 445 /// </summary> 446 /// <returns></returns> 447 public char ReadChar() 448 { 449 return BitConverter.ToChar(Read(2), 0); 450 } 451 452 /// <summary> 453 /// 讀取布爾型數據 454 /// </summary> 455 /// <returns></returns> 456 public bool ReadBoolean() 457 { 458 return BitConverter.ToBoolean(Read(1), 0); 459 } 460 461 /// <summary> 462 /// 從讀取索引位置開始讀取len長度的字節到disbytes目標字節數組中 463 /// </summary> 464 /// <param name="disbytes">讀取的字節將存入此字節數組</param> 465 /// <param name="disstart">目標字節數組的寫入索引</param> 466 /// <param name="len">讀取的長度</param> 467 public void ReadBytes(byte[] disbytes, int disstart, int len) 468 { 469 int size = disstart + len; 470 for (int i = disstart; i < size; i++) 471 { 472 disbytes[i] = this.ReadByte(); 473 } 474 } 475 476 /// <summary> 477 /// 獲取一個字節 478 /// </summary> 479 /// <param name="index"></param> 480 /// <returns></returns> 481 public byte GetByte(int index) 482 { 483 return buf[index]; 484 } 485 486 /// <summary> 487 /// 獲取全部字節 488 /// </summary> 489 /// <returns></returns> 490 public byte[] GetBytes() 491 { 492 return buf; 493 } 494 495 /// <summary> 496 /// 獲取一個雙精度浮點數據,不改變數據內容 497 /// </summary> 498 /// <param name="index">字節索引</param> 499 /// <returns></returns> 500 public double GetDouble(int index) 501 { 502 return BitConverter.ToDouble(Get(0, 8), 0); 503 } 504 505 /// <summary> 506 /// 獲取一個浮點數據,不改變數據內容 507 /// </summary> 508 /// <param name="index">字節索引</param> 509 /// <returns></returns> 510 public float GetFloat(int index) 511 { 512 return BitConverter.ToSingle(Get(0, 4), 0); 513 } 514 515 /// <summary> 516 /// 獲取一個長整形數據,不改變數據內容 517 /// </summary> 518 /// <param name="index">字節索引</param> 519 /// <returns></returns> 520 public long GetLong(int index) 521 { 522 return BitConverter.ToInt64(Get(0, 8), 0); 523 } 524 525 /// <summary> 526 /// 獲取一個整形數據,不改變數據內容 527 /// </summary> 528 /// <param name="index">字節索引</param> 529 /// <returns></returns> 530 public int GetInt(int index) 531 { 532 return BitConverter.ToInt32(Get(0, 4), 0); 533 } 534 535 /// <summary> 536 /// 獲取一個短整形數據,不改變數據內容 537 /// </summary> 538 /// <param name="index">字節索引</param> 539 /// <returns></returns> 540 public int GetShort(int index) 541 { 542 return BitConverter.ToInt16(Get(0, 2), 0); 543 } 544 545 546 /// <summary> 547 /// 清除已讀字節並重建緩存區 548 /// </summary> 549 public void DiscardReadBytes() 550 { 551 if (readIndex <= 0) return; 552 int len = buf.Length - readIndex; 553 byte[] newbuf = new byte[len]; 554 Array.Copy(buf, readIndex, newbuf, 0, len); 555 buf = newbuf; 556 writeIndex -= readIndex; 557 markReadIndex -= readIndex; 558 if (markReadIndex < 0) 559 { 560 markReadIndex = readIndex; 561 } 562 markWirteIndex -= readIndex; 563 if (markWirteIndex < 0 || markWirteIndex < readIndex || markWirteIndex < markReadIndex) 564 { 565 markWirteIndex = writeIndex; 566 } 567 readIndex = 0; 568 } 569 570 /// <summary> 571 /// 清空此對象,但保留字節緩存數組(空數組) 572 /// </summary> 573 public void Clear() 574 { 575 buf = new byte[buf.Length]; 576 readIndex = 0; 577 writeIndex = 0; 578 markReadIndex = 0; 579 markWirteIndex = 0; 580 capacity = buf.Length; 581 } 582 583 /// <summary> 584 /// 釋放對象,清除字節緩存數組,如果此對象為可池化,那么調用此方法將會把此對象推入到池中等待下次調用 585 /// </summary> 586 public void Dispose() 587 { 588 readIndex = 0; 589 writeIndex = 0; 590 markReadIndex = 0; 591 markWirteIndex = 0; 592 if (isPool) 593 { 594 lock (pool) 595 { 596 if (pool.Count < poolMaxCount) 597 { 598 pool.Add(this); 599 } 600 } 601 } 602 else 603 { 604 capacity = 0; 605 buf = null; 606 } 607 } 608 609 /// <summary> 610 /// 設置/獲取讀指針位置 611 /// </summary> 612 public int ReaderIndex 613 { 614 get 615 { 616 return readIndex; 617 } 618 set 619 { 620 if (value < 0) return; 621 readIndex = value; 622 } 623 } 624 625 /// <summary> 626 /// 設置/獲取寫指針位置 627 /// </summary> 628 public int WriterIndex 629 { 630 get 631 { 632 return writeIndex; 633 } 634 set 635 { 636 if (value < 0) return; 637 writeIndex = value; 638 } 639 } 640 641 /// <summary> 642 /// 標記讀取的索引位置 643 /// </summary> 644 public void MarkReaderIndex() 645 { 646 markReadIndex = readIndex; 647 } 648 649 /// <summary> 650 /// 標記寫入的索引位置 651 /// </summary> 652 public void MarkWriterIndex() 653 { 654 markWirteIndex = writeIndex; 655 } 656 657 /// <summary> 658 /// 將讀取的索引位置重置為標記的讀取索引位置 659 /// </summary> 660 public void ResetReaderIndex() 661 { 662 readIndex = markReadIndex; 663 } 664 665 /// <summary> 666 /// 將寫入的索引位置重置為標記的寫入索引位置 667 /// </summary> 668 public void ResetWriterIndex() 669 { 670 writeIndex = markWirteIndex; 671 } 672 673 /// <summary> 674 /// 可讀的有效字節數 675 /// </summary> 676 /// <returns>可讀的字節數</returns> 677 public int ReadableBytes() 678 { 679 return writeIndex - readIndex; 680 } 681 682 /// <summary> 683 /// 獲取可讀的字節數組 684 /// </summary> 685 /// <returns>字節數據</returns> 686 public byte[] ToArray() 687 { 688 byte[] bytes = new byte[writeIndex]; 689 Array.Copy(buf, 0, bytes, 0, bytes.Length); 690 return bytes; 691 } 692 693 /// <summary> 694 /// 獲取緩存區容量大小 695 /// </summary> 696 /// <returns>緩存區容量</returns> 697 public int GetCapacity() 698 { 699 return this.capacity; 700 } 701 702 /// <summary> 703 /// 簡單的數據類型 704 /// </summary> 705 public enum LengthType 706 { 707 //byte類型 708 BYTE, 709 //short類型 710 SHORT, 711 //int類型 712 INT 713 } 714 715 /// <summary> 716 /// 寫入一個數據 717 /// </summary> 718 /// <param name="value">待寫入的數據</param> 719 /// <param name="type">待寫入的數據類型</param> 720 public void WriteValue(int value, LengthType type) 721 { 722 switch (type) 723 { 724 case LengthType.BYTE: 725 this.WriteByte(value); 726 break; 727 case LengthType.SHORT: 728 this.WriteShort((short)value); 729 break; 730 default: 731 this.WriteInt(value); 732 break; 733 } 734 } 735 736 /// <summary> 737 /// 讀取一個值,值類型根據type決定,int或short或byte 738 /// </summary> 739 /// <param name="type">值類型</param> 740 /// <returns>int數據</returns> 741 public int ReadValue(LengthType type) 742 { 743 switch (type) 744 { 745 case LengthType.BYTE: 746 return ReadByteToInt(); 747 case LengthType.SHORT: 748 return (int)ReadShort(); 749 default: 750 return ReadInt(); 751 } 752 } 753 754 /// <summary> 755 /// 寫入一個字符串 756 /// </summary> 757 /// <param name="content">待寫入的字符串</param> 758 /// <param name="lenType">寫入的字符串長度類型</param> 759 public void WriteUTF8String(string content, LengthType lenType) 760 { 761 byte[] bytes = System.Text.UTF8Encoding.UTF8.GetBytes(content); 762 int max; 763 if (lenType == LengthType.BYTE) 764 { 765 WriteByte(bytes.Length); 766 max = byte.MaxValue; 767 } 768 else if (lenType == LengthType.SHORT) 769 { 770 WriteShort((short)bytes.Length); 771 max = short.MaxValue; 772 } 773 else 774 { 775 WriteInt(bytes.Length); 776 max = int.MaxValue; 777 } 778 if (bytes.Length > max) 779 { 780 WriteBytes(bytes, 0, max); 781 } 782 else 783 { 784 WriteBytes(bytes, 0, bytes.Length); 785 } 786 } 787 788 /// <summary> 789 /// 讀取一個字符串 790 /// </summary> 791 /// <param name="len">需讀取的字符串長度</param> 792 /// <returns>字符串</returns> 793 public string ReadUTF8String(int len) 794 { 795 byte[] bytes = new byte[len]; 796 this.ReadBytes(bytes, 0, len); 797 return System.Text.UTF8Encoding.UTF8.GetString(bytes); 798 } 799 800 /// <summary> 801 /// 讀取一個字符串 802 /// </summary> 803 /// <param name="lenType">字符串長度類型</param> 804 /// <returns>字符串</returns> 805 public string ReadUTF8String(LengthType lenType) 806 { 807 int len = ReadValue(lenType); 808 return ReadUTF8String(len); 809 } 810 811 /// <summary> 812 /// 復制一個對象,具有與原對象相同的數據,不改變原對象的數據 813 /// </summary> 814 /// <returns></returns> 815 public ByteBuffer Copy() 816 { 817 return Copy(0); 818 } 819 820 public ByteBuffer Copy(int startIndex) 821 { 822 if (buf == null) 823 { 824 return new ByteBuffer(16); 825 } 826 byte[] target = new byte[buf.Length - startIndex]; 827 Array.Copy(buf, startIndex, target, 0, target.Length); 828 ByteBuffer buffer = new ByteBuffer(target.Length); 829 buffer.WriteBytes(target); 830 return buffer; 831 } 832 }
當然,c#中雖然沒有ByteBuffer,但也有拼接字節數組的方法,比如
1 void Send(byte[] data) 2 { 3 byte[] bytes = new byte[data.Length + 4]; 4 byte[] length = BitConverter.GetBytes(4); 5 //因為不同系統間通信一律采用網絡字節序,而網絡字節序為大端序 6 //但是c#中使用的是小端序,所以此處需要將端序轉換下,關於端序的定義,大家可以自己上網查查,此處就不多說了 7 if (BitConverter.IsLittleEndian) 8 Array.Reverse(length); 9 Array.Copy(length, 0, bytes, 0, 4); 10 Array.Copy(data, 0, bytes, 4, data.Length); 11 mSocket.Send(bytes);
12 }
字節數組拼接好后,就可以使用socket的send方法發送了,不過這一篇先繼續講完接收數據的處理
接收數據的順序是先接收消息長度,然后根據消息長度接收指定長度的消息
1 void ReceiveMessage() 2 { 3 //上文說過,一個完整的消息是 消息長度+消息內容 4 //所以先創建一個長度4的字節數組,用於接收消息長度 5 byte[] recvBytesHead = GetBytesReceive(4); 6 //將消息長度字節組轉為int數值 7 int bodyLength = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(recvBytesHead, 0)); 8 //根據消息長度接收指定長度的字節組,這個字節組就是完整的消息內容 9 byte[] recvBytesBody = GetBytesReceive(bodyLength); 10 //最后反序列化消息的內容 11 Test message = ProtobufSerilizer.DeSerialize<Test>(messageBody); 12 }
GetBytesRecive方法用於接收消息,並解決粘包、少包的問題,代碼如下
1 /// <summary> 2 /// 接收數據並處理 3 /// </summary> 4 /// <param name="length"></param> 5 /// <returns></returns> 6 byte[] GetBytesReceive(int length) 7 { 8 //創建指定長度的字節組 9 byte[] recvBytes = new byte[length]; 10 //設置每次接收包的最大長度為1024個字節 11 int packageMaxLength = 1024; 12 //使用循環來保證接收的數據是完整的,如果剩余長度大於0,證明接收未完成 13 while (length > 0) 14 { 15 //創建字節組,用於存放需要接收的字節流 16 byte[] receiveBytes = new byte[length < packageMaxLength ? length : packageMaxLength]; 17 int iBytesBody = 0; 18 //根據剩余需接收的長度來設置接收數據的長度 19 if (length >= receiveBytes.Length) 20 iBytesBody = mSocket.Receive(receiveBytes, receiveBytes.Length, 0); 21 else 22 iBytesBody = mSocket.Receive(receiveBytes, length, 0); 23 receiveBytes.CopyTo(recvBytes, recvBytes.Length - length); 24 //減去已接收的長度 25 length -= iBytesBody; 26 } 27 return recvBytes; 28 }
到這里,消息的簡單發送和接收就基本搞定了,但是,實際項目中,我們的消息數量肯定不會只有一條,如果是長鏈接的項目,更是需要一直接收和發送消息,該怎么辦?
眾所周知,unity的UI上的顯示只能在主線程中執行,可是如果我們在主線程一直接收和發送消息,那體驗將會極差,所以我們必須另外開啟線程來負責消息的接收和發送,下一篇就是使用多線程來完成socket通訊
