Home > Article > Backend Development > Detailed explanation of examples of socket transmission protobuf byte stream
Copyright Statement: This article is an original article, please declare when reprinting
The previous article mainly talks about the serialization and parsing of protobuf byte stream, serializing protobuf objects into words Although it can be transmitted directly after throttling, it is actually impossible to transmit only the protobuf byte stream in the project, because there are several very common problems in the TCP communication of the socket, namely sticky packets and missing packets. The so-called sticky packets simply mean that the socket will merge multiple smaller packets and send them together. Because TCP is connection-oriented, in order to send multiple packets to the receiving end more efficiently, the sending end uses an optimization method (Nagle algorithm) to merge multiple data with small intervals and small data volume into A large data block is then packetized. Missing packets means that after the buffer area is full, soket will send incomplete packets to the receiving end (according to my understanding, sticky packets and missing packets are actually a problem). In this way, the data received by the receiving end at one time may be multiple packets. In order to solve this problem, the length of the packet needs to be sent before sending the data. Therefore, the structure of the packet should be message length + message content.
This article, let’s talk about data splicing, here comes the useful information
First, splice the data packet
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 used in the code The tool is provided in Java, but not in C#. The source code is excerpted. However, the author did not add a method to obtain all bytecodes in the tool, so he added a GetBytes() method
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,则返回值为8100 /// </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 else603 {604 capacity = 0;605 buf = null;606 }607 }608 609 /// <summary>610 /// 设置/获取读指针位置611 /// </summary>612 public int ReaderIndex613 {614 get615 {616 return readIndex;617 }618 set619 {620 if (value < 0) return;621 readIndex = value;622 }623 }624 625 /// <summary>626 /// 设置/获取写指针位置627 /// </summary>628 public int WriterIndex629 {630 get631 {632 return writeIndex;633 }634 set635 {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 LengthType706 {707 //byte类型708 BYTE,709 //short类型710 SHORT,711 //int类型712 INT713 }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或byte738 /// </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 else774 {775 WriteInt(bytes.Length);776 max = int.MaxValue;777 }778 if (bytes.Length > max)779 {780 WriteBytes(bytes, 0, max);781 }782 else783 {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 }
Of course, although there is no ByteBuffer in c#, there are ways to splice byte arrays, such as
Send( [] bytes = [data.Length + [] length = BitConverter.GetBytes( Array.Copy(length, , bytes, , Array.Copy(data, , bytes, mSocket.Send(bytes); }
After the byte array is spliced, you can use the send method of the socket to send it, but this article will continue to finish the processing of receiving data.
The order of receiving data is to receive the message length first. , and then receive the message of the specified length according to the message length
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 }
The GetBytesRecive method is used to receive the message and solve the problem of sticky packets and missing packets. The code is as follows
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 else22 iBytesBody = mSocket.Receive(receiveBytes, length, 0);23 receiveBytes.CopyTo(recvBytes, recvBytes.Length - length);24 //减去已接收的长度25 length -= iBytesBody;26 }27 return recvBytes;28 }
At this point, the simple sending and receiving of messages is basically done. However, in actual projects, we will definitely not have only one message. If it is a long-link project, we need to receive and send messages all the time. ,what to do?
As we all know, the display on Unity's UI can only be executed in the main thread. However, if we keep receiving and sending messages in the main thread, the experience will be extremely poor, so we must open another thread to be responsible for the messages. Receiving and sending, the next article is to use multi-threading to complete socket communication
The above is the detailed content of Detailed explanation of examples of socket transmission protobuf byte stream. For more information, please follow other related articles on the PHP Chinese website!