版权声明:本文为原创文章,转载请声明http://www.cnblogs.com/unityExplorer/p/6977935.html

上一篇主要说的是protobuf字节流的序列化和解析,将protobuf对象序列化为字节流后虽然可以直接传递,但是实际在项目中却不可能真的只是传递protobuf字节流,因为socket的tcp通讯中会出现几个很常见的问题,就是粘包和少包。所谓粘包,简单点说就是socket会将多个较小的包合并到一起发送。因为tcp是面向连接的,发送端为了将多个发往接收端的包,更有效的发到对方,使用了优化方法(Nagle算法),将多次间隔较小且数据量小的数据,合并成一个大的数据块,然后进行封包。少包则是指缓存区满后,soket将不完整的包发送到接收端(按我的理解,粘包和少包其实是一个问题)。这样接收端一次接收到的数据就有可能是多个包,为了解决这个问题,在发送数据之前,需要将包的长度也发送出去。于是,包的结构就应该是 消息长度+消息内容。

这一篇,就来说说数据的拼接,干货来了

首先的拼接数据包

     /// <summary>
/// 构建消息数据包
/// </summary>
/// <param name="protobufModel"></param>
byte[] BuildPackage(IExtensible protobufModel)
{
if (protobufModel != null)
{
byte[] b = ProtobufSerilizer.Serialize(protobufModel); ByteBuffer buf = ByteBuffer.Allocate(b.Length + );
buf.WriteInt(b.Length);
buf.WriteBytes(b);
return buf.GetBytes();
}
return null;
}

代码中使用的ByteBuffer工具java中有提供,但是c#中是没有的,源码摘至https://www.oschina.net/code/snippet_42170_37516,不过作者并未在工具中添加获取所有字节码的方法,所以自己添加了一个GetBytes()方法

 using System;
using System.Collections.Generic; /// <summary>
/// 字节缓冲处理类,本类仅处理大字节序
/// 警告,本类非线程安全
/// </summary>
public class ByteBuffer
{
//字节缓存区
private byte[] buf;
//读取索引
private int readIndex = ;
//写入索引
private int writeIndex = ;
//读取索引标记
private int markReadIndex = ;
//写入索引标记
private int markWirteIndex = ;
//缓存区字节数组的长度
private int capacity; //对象池
private static List<ByteBuffer> pool = new List<ByteBuffer>();
private static int poolMaxCount = ;
//此对象是否池化
private bool isPool = false; /// <summary>
/// 构造方法
/// </summary>
/// <param name="capacity">初始容量</param>
private ByteBuffer(int capacity)
{
buf = new byte[capacity];
this.capacity = capacity;
} /// <summary>
/// 构造方法
/// </summary>
/// <param name="bytes">初始字节数组</param>
private ByteBuffer(byte[] bytes)
{
buf = bytes;
this.capacity = bytes.Length;
this.readIndex = ;
this.writeIndex = bytes.Length + ;
} /// <summary>
/// 构建一个capacity长度的字节缓存区ByteBuffer对象
/// </summary>
/// <param name="capacity">初始容量</param>
/// <returns>ByteBuffer对象</returns>
public static ByteBuffer Allocate(int capacity)
{
return new ByteBuffer(capacity);
} /// <summary>
/// 构建一个以bytes为字节缓存区的ByteBuffer对象,一般不推荐使用
/// </summary>
/// <param name="bytes">初始字节数组</param>
/// <returns>ByteBuffer对象</returns>
public static ByteBuffer Allocate(byte[] bytes)
{
return new ByteBuffer(bytes);
} /// <summary>
/// 获取一个池化的ByteBuffer对象,池化的对象必须在调用Dispose后才会推入池中,否则此方法等同于Allocate(int capacity)方法,此方法为线程安全的
/// </summary>
/// <param name="capacity">ByteBuffer对象的初始容量大小,如果缓存池中没有对象,则对象的容量大小为此值,否则为池中对象的实际容量值</param>
/// <returns></returns>
public static ByteBuffer GetFromPool(int capacity)
{
lock (pool)
{
ByteBuffer bbuf;
if (pool.Count == )
{
bbuf = Allocate(capacity);
bbuf.isPool = true;
return bbuf;
}
int lastIndex = pool.Count - ;
bbuf = pool[lastIndex];
pool.RemoveAt(lastIndex);
if (!bbuf.isPool)
{
bbuf.isPool = true;
}
return bbuf;
}
} /// <summary>
/// 根据length长度,确定大于此leng的最近的2次方数,如length=7,则返回值为8
/// </summary>
/// <param name="length">参考容量</param>
/// <returns>比参考容量大的最接近的2次方数</returns>
private int FixLength(int length)
{
int n = ;
int b = ;
while (b < length)
{
b = << n;
n++;
}
return b;
} /// <summary>
/// 翻转字节数组,如果本地字节序列为低字节序列,则进行翻转以转换为高字节序列
/// </summary>
/// <param name="bytes">待转为高字节序的字节数组</param>
/// <returns>高字节序列的字节数组</returns>
private byte[] flip(byte[] bytes)
{
if (BitConverter.IsLittleEndian)
{
Array.Reverse(bytes);
}
return bytes;
} /// <summary>
/// 确定内部字节缓存数组的大小
/// </summary>
/// <param name="currLen">当前容量</param>
/// <param name="futureLen">将来的容量</param>
/// <returns>将来的容量</returns>
private int FixSizeAndReset(int currLen, int futureLen)
{
if (futureLen > currLen)
{
//以原大小的2次方数的两倍确定内部字节缓存区大小
int size = FixLength(currLen) * ;
if (futureLen > size)
{
//以将来的大小的2次方的两倍确定内部字节缓存区大小
size = FixLength(futureLen) * ;
}
byte[] newbuf = new byte[size];
Array.Copy(buf, , newbuf, , currLen);
buf = newbuf;
capacity = newbuf.Length;
}
return futureLen;
} /// <summary>
/// 将bytes字节数组从startIndex开始的length字节写入到此缓存区
/// </summary>
/// <param name="bytes">待写入的字节数据</param>
/// <param name="startIndex">写入的开始位置</param>
/// <param name="length">写入的长度</param>
public void WriteBytes(byte[] bytes, int startIndex, int length)
{
int offset = length - startIndex;
if (offset <= ) return;
int total = offset + writeIndex;
int len = buf.Length;
FixSizeAndReset(len, total);
for (int i = writeIndex, j = startIndex; i < total; i++, j++)
{
buf[i] = bytes[j];
}
writeIndex = total;
} /// <summary>
/// 将字节数组中从0到length的元素写入缓存区
/// </summary>
/// <param name="bytes">待写入的字节数据</param>
/// <param name="length">写入的长度</param>
public void WriteBytes(byte[] bytes, int length)
{
WriteBytes(bytes, , length);
} /// <summary>
/// 将字节数组全部写入缓存区
/// </summary>
/// <param name="bytes">待写入的字节数据</param>
public void WriteBytes(byte[] bytes)
{
WriteBytes(bytes, bytes.Length);
} /// <summary>
/// 将一个ByteBuffer的有效字节区写入此缓存区中
/// </summary>
/// <param name="buffer">待写入的字节缓存区</param>
public void Write(ByteBuffer buffer)
{
if (buffer == null) return;
if (buffer.ReadableBytes() <= ) return;
WriteBytes(buffer.ToArray());
} /// <summary>
/// 写入一个int16数据
/// </summary>
/// <param name="value">short数据</param>
public void WriteShort(short value)
{
WriteBytes(flip(BitConverter.GetBytes(value)));
} /// <summary>
/// 写入一个ushort数据
/// </summary>
/// <param name="value">ushort数据</param>
public void WriteUshort(ushort value)
{
WriteBytes(flip(BitConverter.GetBytes(value)));
} /// <summary>
/// 写入一个int32数据
/// </summary>
/// <param name="value">int数据</param>
public void WriteInt(int value)
{
//byte[] array = new byte[4];
//for (int i = 3; i >= 0; i--)
//{
// array[i] = (byte)(value & 0xff);
// value = value >> 8;
//}
//Array.Reverse(array);
//Write(array);
WriteBytes(flip(BitConverter.GetBytes(value)));
} /// <summary>
/// 写入一个uint32数据
/// </summary>
/// <param name="value">uint数据</param>
public void WriteUint(uint value)
{
WriteBytes(flip(BitConverter.GetBytes(value)));
} /// <summary>
/// 写入一个int64数据
/// </summary>
/// <param name="value">long数据</param>
public void WriteLong(long value)
{
WriteBytes(flip(BitConverter.GetBytes(value)));
} /// <summary>
/// 写入一个uint64数据
/// </summary>
/// <param name="value">ulong数据</param>
public void WriteUlong(ulong value)
{
WriteBytes(flip(BitConverter.GetBytes(value)));
} /// <summary>
/// 写入一个float数据
/// </summary>
/// <param name="value">float数据</param>
public void WriteFloat(float value)
{
WriteBytes(flip(BitConverter.GetBytes(value)));
} /// <summary>
/// 写入一个byte数据
/// </summary>
/// <param name="value">byte数据</param>
public void WriteByte(byte value)
{
int afterLen = writeIndex + ;
int len = buf.Length;
FixSizeAndReset(len, afterLen);
buf[writeIndex] = value;
writeIndex = afterLen;
} /// <summary>
/// 写入一个byte数据
/// </summary>
/// <param name="value">byte数据</param>
public void WriteByte(int value)
{
byte b = (byte)value;
WriteByte(b);
} /// <summary>
/// 写入一个double类型数据
/// </summary>
/// <param name="value">double数据</param>
public void WriteDouble(double value)
{
WriteBytes(flip(BitConverter.GetBytes(value)));
} /// <summary>
/// 写入一个字符
/// </summary>
/// <param name="value"></param>
public void WriteChar(char value)
{
WriteBytes(flip(BitConverter.GetBytes(value)));
} /// <summary>
/// 写入一个布尔型数据
/// </summary>
/// <param name="value"></param>
public void WriteBoolean(bool value)
{
WriteBytes(flip(BitConverter.GetBytes(value)));
} /// <summary>
/// 读取一个字节
/// </summary>
/// <returns>字节数据</returns>
public byte ReadByte()
{
byte b = buf[readIndex];
readIndex++;
return b;
} /// <summary>
/// 读取一个字节并转为int类型的数据
/// </summary>
/// <returns>int数据</returns>
public int ReadByteToInt()
{
byte b = ReadByte();
return (int)b;
} /// <summary>
/// 获取从index索引处开始len长度的字节
/// </summary>
/// <param name="index"></param>
/// <param name="len"></param>
/// <returns></returns>
private byte[] Get(int index, int len)
{
byte[] bytes = new byte[len];
Array.Copy(buf, index, bytes, , len);
return flip(bytes);
} /// <summary>
/// 从读取索引位置开始读取len长度的字节数组
/// </summary>
/// <param name="len">待读取的字节长度</param>
/// <returns>字节数组</returns>
private byte[] Read(int len)
{
byte[] bytes = Get(readIndex, len);
readIndex += len;
return bytes;
} /// <summary>
/// 读取一个uint16数据
/// </summary>
/// <returns>ushort数据</returns>
public ushort ReadUshort()
{
return BitConverter.ToUInt16(Read(), );
} /// <summary>
/// 读取一个int16数据
/// </summary>
/// <returns>short数据</returns>
public short ReadShort()
{
return BitConverter.ToInt16(Read(), );
} /// <summary>
/// 读取一个uint32数据
/// </summary>
/// <returns>uint数据</returns>
public uint ReadUint()
{
return BitConverter.ToUInt32(Read(), );
} /// <summary>
/// 读取一个int32数据
/// </summary>
/// <returns>int数据</returns>
public int ReadInt()
{
return BitConverter.ToInt32(Read(), );
} /// <summary>
/// 读取一个uint64数据
/// </summary>
/// <returns>ulong数据</returns>
public ulong ReadUlong()
{
return BitConverter.ToUInt64(Read(), );
} /// <summary>
/// 读取一个long数据
/// </summary>
/// <returns>long数据</returns>
public long ReadLong()
{
return BitConverter.ToInt64(Read(), );
} /// <summary>
/// 读取一个float数据
/// </summary>
/// <returns>float数据</returns>
public float ReadFloat()
{
return BitConverter.ToSingle(Read(), );
} /// <summary>
/// 读取一个double数据
/// </summary>
/// <returns>double数据</returns>
public double ReadDouble()
{
return BitConverter.ToDouble(Read(), );
} /// <summary>
/// 读取一个字符
/// </summary>
/// <returns></returns>
public char ReadChar()
{
return BitConverter.ToChar(Read(), );
} /// <summary>
/// 读取布尔型数据
/// </summary>
/// <returns></returns>
public bool ReadBoolean()
{
return BitConverter.ToBoolean(Read(), );
} /// <summary>
/// 从读取索引位置开始读取len长度的字节到disbytes目标字节数组中
/// </summary>
/// <param name="disbytes">读取的字节将存入此字节数组</param>
/// <param name="disstart">目标字节数组的写入索引</param>
/// <param name="len">读取的长度</param>
public void ReadBytes(byte[] disbytes, int disstart, int len)
{
int size = disstart + len;
for (int i = disstart; i < size; i++)
{
disbytes[i] = this.ReadByte();
}
} /// <summary>
/// 获取一个字节
/// </summary>
/// <param name="index"></param>
/// <returns></returns>
public byte GetByte(int index)
{
return buf[index];
} /// <summary>
/// 获取全部字节
/// </summary>
/// <returns></returns>
public byte[] GetBytes()
{
return buf;
} /// <summary>
/// 获取一个双精度浮点数据,不改变数据内容
/// </summary>
/// <param name="index">字节索引</param>
/// <returns></returns>
public double GetDouble(int index)
{
return BitConverter.ToDouble(Get(, ), );
} /// <summary>
/// 获取一个浮点数据,不改变数据内容
/// </summary>
/// <param name="index">字节索引</param>
/// <returns></returns>
public float GetFloat(int index)
{
return BitConverter.ToSingle(Get(, ), );
} /// <summary>
/// 获取一个长整形数据,不改变数据内容
/// </summary>
/// <param name="index">字节索引</param>
/// <returns></returns>
public long GetLong(int index)
{
return BitConverter.ToInt64(Get(, ), );
} /// <summary>
/// 获取一个整形数据,不改变数据内容
/// </summary>
/// <param name="index">字节索引</param>
/// <returns></returns>
public int GetInt(int index)
{
return BitConverter.ToInt32(Get(, ), );
} /// <summary>
/// 获取一个短整形数据,不改变数据内容
/// </summary>
/// <param name="index">字节索引</param>
/// <returns></returns>
public int GetShort(int index)
{
return BitConverter.ToInt16(Get(, ), );
} /// <summary>
/// 清除已读字节并重建缓存区
/// </summary>
public void DiscardReadBytes()
{
if (readIndex <= ) return;
int len = buf.Length - readIndex;
byte[] newbuf = new byte[len];
Array.Copy(buf, readIndex, newbuf, , len);
buf = newbuf;
writeIndex -= readIndex;
markReadIndex -= readIndex;
if (markReadIndex < )
{
markReadIndex = readIndex;
}
markWirteIndex -= readIndex;
if (markWirteIndex < || markWirteIndex < readIndex || markWirteIndex < markReadIndex)
{
markWirteIndex = writeIndex;
}
readIndex = ;
} /// <summary>
/// 清空此对象,但保留字节缓存数组(空数组)
/// </summary>
public void Clear()
{
buf = new byte[buf.Length];
readIndex = ;
writeIndex = ;
markReadIndex = ;
markWirteIndex = ;
capacity = buf.Length;
} /// <summary>
/// 释放对象,清除字节缓存数组,如果此对象为可池化,那么调用此方法将会把此对象推入到池中等待下次调用
/// </summary>
public void Dispose()
{
readIndex = ;
writeIndex = ;
markReadIndex = ;
markWirteIndex = ;
if (isPool)
{
lock (pool)
{
if (pool.Count < poolMaxCount)
{
pool.Add(this);
}
}
}
else
{
capacity = ;
buf = null;
}
} /// <summary>
/// 设置/获取读指针位置
/// </summary>
public int ReaderIndex
{
get
{
return readIndex;
}
set
{
if (value < ) return;
readIndex = value;
}
} /// <summary>
/// 设置/获取写指针位置
/// </summary>
public int WriterIndex
{
get
{
return writeIndex;
}
set
{
if (value < ) return;
writeIndex = value;
}
} /// <summary>
/// 标记读取的索引位置
/// </summary>
public void MarkReaderIndex()
{
markReadIndex = readIndex;
} /// <summary>
/// 标记写入的索引位置
/// </summary>
public void MarkWriterIndex()
{
markWirteIndex = writeIndex;
} /// <summary>
/// 将读取的索引位置重置为标记的读取索引位置
/// </summary>
public void ResetReaderIndex()
{
readIndex = markReadIndex;
} /// <summary>
/// 将写入的索引位置重置为标记的写入索引位置
/// </summary>
public void ResetWriterIndex()
{
writeIndex = markWirteIndex;
} /// <summary>
/// 可读的有效字节数
/// </summary>
/// <returns>可读的字节数</returns>
public int ReadableBytes()
{
return writeIndex - readIndex;
} /// <summary>
/// 获取可读的字节数组
/// </summary>
/// <returns>字节数据</returns>
public byte[] ToArray()
{
byte[] bytes = new byte[writeIndex];
Array.Copy(buf, , bytes, , bytes.Length);
return bytes;
} /// <summary>
/// 获取缓存区容量大小
/// </summary>
/// <returns>缓存区容量</returns>
public int GetCapacity()
{
return this.capacity;
} /// <summary>
/// 简单的数据类型
/// </summary>
public enum LengthType
{
//byte类型
BYTE,
//short类型
SHORT,
//int类型
INT
} /// <summary>
/// 写入一个数据
/// </summary>
/// <param name="value">待写入的数据</param>
/// <param name="type">待写入的数据类型</param>
public void WriteValue(int value, LengthType type)
{
switch (type)
{
case LengthType.BYTE:
this.WriteByte(value);
break;
case LengthType.SHORT:
this.WriteShort((short)value);
break;
default:
this.WriteInt(value);
break;
}
} /// <summary>
/// 读取一个值,值类型根据type决定,int或short或byte
/// </summary>
/// <param name="type">值类型</param>
/// <returns>int数据</returns>
public int ReadValue(LengthType type)
{
switch (type)
{
case LengthType.BYTE:
return ReadByteToInt();
case LengthType.SHORT:
return (int)ReadShort();
default:
return ReadInt();
}
} /// <summary>
/// 写入一个字符串
/// </summary>
/// <param name="content">待写入的字符串</param>
/// <param name="lenType">写入的字符串长度类型</param>
public void WriteUTF8String(string content, LengthType lenType)
{
byte[] bytes = System.Text.UTF8Encoding.UTF8.GetBytes(content);
int max;
if (lenType == LengthType.BYTE)
{
WriteByte(bytes.Length);
max = byte.MaxValue;
}
else if (lenType == LengthType.SHORT)
{
WriteShort((short)bytes.Length);
max = short.MaxValue;
}
else
{
WriteInt(bytes.Length);
max = int.MaxValue;
}
if (bytes.Length > max)
{
WriteBytes(bytes, , max);
}
else
{
WriteBytes(bytes, , bytes.Length);
}
} /// <summary>
/// 读取一个字符串
/// </summary>
/// <param name="len">需读取的字符串长度</param>
/// <returns>字符串</returns>
public string ReadUTF8String(int len)
{
byte[] bytes = new byte[len];
this.ReadBytes(bytes, , len);
return System.Text.UTF8Encoding.UTF8.GetString(bytes);
} /// <summary>
/// 读取一个字符串
/// </summary>
/// <param name="lenType">字符串长度类型</param>
/// <returns>字符串</returns>
public string ReadUTF8String(LengthType lenType)
{
int len = ReadValue(lenType);
return ReadUTF8String(len);
} /// <summary>
/// 复制一个对象,具有与原对象相同的数据,不改变原对象的数据
/// </summary>
/// <returns></returns>
public ByteBuffer Copy()
{
return Copy();
} public ByteBuffer Copy(int startIndex)
{
if (buf == null)
{
return new ByteBuffer();
}
byte[] target = new byte[buf.Length - startIndex];
Array.Copy(buf, startIndex, target, , target.Length);
ByteBuffer buffer = new ByteBuffer(target.Length);
buffer.WriteBytes(target);
return buffer;
}
}

当然,c#中虽然没有ByteBuffer,但也有拼接字节数组的方法,比如

     void Send(byte[] data)
{
byte[] bytes = new byte[data.Length + ];
byte[] length = BitConverter.GetBytes();
//因为不同系统间通信一律采用网络字节序,而网络字节序为大端序
//但是c#中使用的是小端序,所以此处需要将端序转换下,关于端序的定义,大家可以自己上网查查,此处就不多说了
if (BitConverter.IsLittleEndian)
Array.Reverse(length);
Array.Copy(length, , bytes, , );
Array.Copy(data, , bytes, , data.Length);
mSocket.Send(bytes);
}

字节数组拼接好后,就可以使用socket的send方法发送了,不过这一篇先继续讲完接收数据的处理

接收数据的顺序是先接收消息长度,然后根据消息长度接收指定长度的消息

     void ReceiveMessage()
{
//上文说过,一个完整的消息是 消息长度+消息内容
//所以先创建一个长度4的字节数组,用于接收消息长度
byte[] recvBytesHead = GetBytesReceive();
//将消息长度字节组转为int数值
int bodyLength = IPAddress.NetworkToHostOrder(BitConverter.ToInt32(recvBytesHead, ));
//根据消息长度接收指定长度的字节组,这个字节组就是完整的消息内容
byte[] recvBytesBody = GetBytesReceive(bodyLength);
//最后反序列化消息的内容
Test message = ProtobufSerilizer.DeSerialize<Test>(messageBody);
}

GetBytesRecive方法用于接收消息,并解决粘包、少包的问题,代码如下

     /// <summary>
/// 接收数据并处理
/// </summary>
/// <param name="length"></param>
/// <returns></returns>
byte[] GetBytesReceive(int length)
{
//创建指定长度的字节组
byte[] recvBytes = new byte[length];
//设置每次接收包的最大长度为1024个字节
int packageMaxLength = ;
//使用循环来保证接收的数据是完整的,如果剩余长度大于0,证明接收未完成
while (length > )
{
//创建字节组,用于存放需要接收的字节流
byte[] receiveBytes = new byte[length < packageMaxLength ? length : packageMaxLength];
int iBytesBody = ;
//根据剩余需接收的长度来设置接收数据的长度
if (length >= receiveBytes.Length)
iBytesBody = mSocket.Receive(receiveBytes, receiveBytes.Length, );
else
iBytesBody = mSocket.Receive(receiveBytes, length, );
receiveBytes.CopyTo(recvBytes, recvBytes.Length - length);
//减去已接收的长度
length -= iBytesBody;
}
return recvBytes;
}

到这里,消息的简单发送和接收就基本搞定了,但是,实际项目中,我们的消息数量肯定不会只有一条,如果是长链接的项目,更是需要一直接收和发送消息,该怎么办?

众所周知,unity的UI上的显示只能在主线程中执行,可是如果我们在主线程一直接收和发送消息,那体验将会极差,所以我们必须另外开启线程来负责消息的接收和发送,下一篇就是使用多线程来完成socket通讯

unity探索者之socket传输protobuf字节流(二)的更多相关文章

  1. unity探索者之socket传输protobuf字节流(一)

    版权声明:本文为原创文章,转载请声明http://www.cnblogs.com/unityExplorer/p/6974229.html 近期在做一个棋牌项目,需要用到socket传输protobu ...

  2. unity探索者之socket传输protobuf字节流(四)

    版权声明:本文为原创文章,转载请声明http://www.cnblogs.com/unityExplorer/p/7027659.html 上篇已经把socket的传输说的差不多了,这篇主要是说说断线 ...

  3. unity探索者之socket传输protobuf字节流(三)

    版权声明:本文为原创文章,转载请声明http://www.cnblogs.com/unityExplorer/p/6986474.html 上一篇讲到了数据的处理,这一篇主要讲使用多线程收发消息 // ...

  4. C#使用ProtocolBuffer(ProtoBuf)进行Unity中的Socket通信

    首先来说一下本文中例子所要实现的功能: 基于ProtoBuf序列化对象 使用Socket实现时时通信 数据包的编码和解码 下面来看具体的步骤: 一.Unity中使用ProtoBuf 导入DLL到Uni ...

  5. [C#技术参考]Socket传输结构数据

    最近在做一个机器人项目,要实时的接收机器人传回的坐标信息,并在客户端显示当前的地图和机器人的位置.当然坐标的回传是用的Socket,用的是C++的结构体表示的坐标信息.但是C#不能像C++那样很eas ...

  6. Java 学习笔记 网络编程 使用Socket传输文件 CS模式

    Socket的简单认识 Socket是一种面向连接的通信协议,Socket应用程序是一种C/S(Client端/Server端)结构的应用程序 Socket是两台机器间通信的端点. Socket是连接 ...

  7. C++ socket 传输不同类型数据的四种方式

    使用socket传输组织好的不同类型数据,有四种不同的方式(我知道的嘿嘿): a. 结构体 b. Json序列化 c. 类对象 d. protobuf 下面逐一整理一下,方便以后进行项目开发. 1. ...

  8. 【Unity Shaders】学习笔记——SurfaceShader(二)两个结构体和CG类型

    [Unity Shaders]学习笔记——SurfaceShader(二)两个结构体和CG类型 转载请注明出处:http://www.cnblogs.com/-867259206/p/5596698. ...

  9. Java使用Socket传输文件遇到的问题(转)

    1.写了一个socket传输文件的程序,发现传输过去文件有问题.找了一下午终于似乎找到了原因,记录下来警示一下: 接受文件的一端,向本地写文件之前使用Thread.sleep(time)休息一下就解决 ...

随机推荐

  1. Java继承之面向对象

    面向对象与面向过程: 面向对象(OOP)与面向过程 二者都是一种思想,面向对象是相对于面向过程而言的. 面向过程,强调的是功能行为.面向对象,将功能封装进对象,强调具备了功能的对象. 面向对象更加强调 ...

  2. 面试题十八:在O(1)的时间内删除链表的节点

    方法一:将要删除的·节点的下一个节点的内容复制到该节点上,然后删除下一个节点注意特殊情况:链表只有一个节点时,则删除头节点,则把头节点设置为null, 如果删除的尾节点则需要顺序遍历链表,取得前序节点 ...

  3. apache 基本配置

    1.1 ServerRoot 配置 [ServerRoot "" 主要用于指定Apache的安装路径,此选项参数值在安装Apache时系统会自动把Apache的路径写入.Windo ...

  4. REST是什么?RESTFul又是什么?这二者的关系是怎样的?

    REST(一种软件架构风格) 全称:Representational State Transfer 含义:(表述性 状态 转移) 是一种针对网络应用的设计和开发方式,可以降低开发的复杂性,提高系统的可 ...

  5. centos7安装部署docker

    Kubernetes/K8s架构师实战集训营[中级班]:https://pan.baidu.com/s/1FWAz2V7BPsObixlZyW93sw 提取码:mvu0 Kubernetes/K8s架 ...

  6. Python元组索引、截取

    Python元组索引.截取: 索引下标: tuple_1 = ('a','b','c','d','e','f','g','h') print(tuple_1[0]) # a print(tuple_1 ...

  7. KNN算法基本原理与sklearn实现

    ''' KNN 近邻算法,有监督学习算法 用于分类和回归 思路: 1.在样本空间中查找 k 个最相似或者距离最近的样本 2.根据这 k 个最相似的样本对未知样本进行分类 步骤: 1.对数据进行预处理 ...

  8. PHP date_offset_get() 函数

    ------------恢复内容开始------------ 实例 返回奥斯陆(在欧洲挪威)冬天和夏天相对于 UTC 的以秒计的时区偏移量: <?php$winter=date_create(& ...

  9. Skill 脚本演示 ycMPPTap.skl

    https://www.cnblogs.com/yeungchie/ ycMPPTap.skl 主要用于创建自定的 Tap 类型(指定 掺杂类型 / Via 数量 / Active 宽度),并可以通过 ...

  10. Python性能分析与优化PDF高清完整版免费下载|百度云盘

    百度云盘|Python性能分析与优化PDF高清完整版免费下载 提取码:ubjt 内容简介 全面掌握Python代码性能分析和优化方法,消除性能瓶颈,迅速改善程序性能! 对于Python程序员来说,仅仅 ...