unity探索者之socket传输protobuf字节流(二)
版权声明:本文为原创文章,转载请声明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字节流(二)的更多相关文章
- unity探索者之socket传输protobuf字节流(一)
版权声明:本文为原创文章,转载请声明http://www.cnblogs.com/unityExplorer/p/6974229.html 近期在做一个棋牌项目,需要用到socket传输protobu ...
- unity探索者之socket传输protobuf字节流(四)
版权声明:本文为原创文章,转载请声明http://www.cnblogs.com/unityExplorer/p/7027659.html 上篇已经把socket的传输说的差不多了,这篇主要是说说断线 ...
- unity探索者之socket传输protobuf字节流(三)
版权声明:本文为原创文章,转载请声明http://www.cnblogs.com/unityExplorer/p/6986474.html 上一篇讲到了数据的处理,这一篇主要讲使用多线程收发消息 // ...
- C#使用ProtocolBuffer(ProtoBuf)进行Unity中的Socket通信
首先来说一下本文中例子所要实现的功能: 基于ProtoBuf序列化对象 使用Socket实现时时通信 数据包的编码和解码 下面来看具体的步骤: 一.Unity中使用ProtoBuf 导入DLL到Uni ...
- [C#技术参考]Socket传输结构数据
最近在做一个机器人项目,要实时的接收机器人传回的坐标信息,并在客户端显示当前的地图和机器人的位置.当然坐标的回传是用的Socket,用的是C++的结构体表示的坐标信息.但是C#不能像C++那样很eas ...
- Java 学习笔记 网络编程 使用Socket传输文件 CS模式
Socket的简单认识 Socket是一种面向连接的通信协议,Socket应用程序是一种C/S(Client端/Server端)结构的应用程序 Socket是两台机器间通信的端点. Socket是连接 ...
- C++ socket 传输不同类型数据的四种方式
使用socket传输组织好的不同类型数据,有四种不同的方式(我知道的嘿嘿): a. 结构体 b. Json序列化 c. 类对象 d. protobuf 下面逐一整理一下,方便以后进行项目开发. 1. ...
- 【Unity Shaders】学习笔记——SurfaceShader(二)两个结构体和CG类型
[Unity Shaders]学习笔记——SurfaceShader(二)两个结构体和CG类型 转载请注明出处:http://www.cnblogs.com/-867259206/p/5596698. ...
- Java使用Socket传输文件遇到的问题(转)
1.写了一个socket传输文件的程序,发现传输过去文件有问题.找了一下午终于似乎找到了原因,记录下来警示一下: 接受文件的一端,向本地写文件之前使用Thread.sleep(time)休息一下就解决 ...
随机推荐
- 题解 洛谷 P4899 【[IOI2018] werewolf 狼人】
先考虑狼形,其只能走编号小于\(R\)的点.若将每条边赋边权为其两端点编号的较大值,然后按最小生成树的顺序构建\(Kruskal\)重构树. 那么从原图的一个点\(x\)在树上倍增,到达满足要求且深度 ...
- laravel5.5 安装
环境要求 PHP >= 7.0.0 PHP OpenSSL 扩展 PHP PDO 扩展 PHP Mbstring 扩展 PHP Tokenizer 扩展 PHP XML 扩展 通过 Larave ...
- Java中包装类Test类测试出错的解决方法(JUnit5)
import org.junit.jupiter.api.Test; public class TestJunit { public static void main(String[]args) { ...
- LQB201804第几个幸运数
我自己一开始想的差不多,但是好像想得是vector+sort.... 直接用set它不香吗? 还有就是寻找下一个数的时候,没有用upperbound,,, 我想的大概是遍历一遍(就是用for对这个函数 ...
- Vue数据产生变化需要页面渲染完之后执行某操作
1.数据产生变化或者页面需要vue数据渲染完之后加载的东西 Vue.nextTick(function () { alert(123); }); 2 调用vue方法 --------------Vue ...
- C++ 第二天 字符串
字符串 字符串是最常用的一种数据类型了,在python中声明字符串和声明其他类型的数据一样,都非常的简单.但是在c++中,对于字符串的操作,相对来说要稍微复杂一些. C++ 提供了以下两种类型的字符串 ...
- PHP fread() 函数
定义和用法 fread() 函数读取打开的文件. 函数会在到达指定长度或读到文件末尾(EOF)时(以先到者为准),停止运行. 该函数返回读取的字符串,如果失败则返回 FALSE. 语法 string ...
- 4.19 ABC F path pass i 容斥 树形dp
LINK:path pass i 原本想了一个点分治 yy了半天 发现重复的部分还是很难减掉 况且统计答案的时候有点ex. (点了别人的提交记录 发现dfs就过了 于是yy了一个容斥 发现可以直接减掉 ...
- Android JNI之数据类型
JNI中数据类型的意义在于桥接Java数据类型与C数据类型. 简单数据类型: Java Type Native Type Description boolean jboolean unsigned 8 ...
- jmeter中使用jdbc参数化
以读取mysql数据库为例 1.下载一个mysql驱动包,最好去mysql官网下载 下载网址:https://dev.mysql.com/downloads/connector/j/ Select O ...