您的位置:首页 > 移动开发 > Unity3D

Unity3D 使用异步socket通讯

2015-12-14 16:02 489 查看
C# 中Socket的异步方法,都是以Begin开始表示开启异步调用,以End开始表示结束异步调用线程。根据官方文档的介绍,Begin方法会在后台开启线程操作,完成后回调注册函数。分为三个部分,建立连接,接收数据,发送数据,全部使用异步调用。

首先,异步建立连接。

public static void SocketConnect(string server, int port, Action onConnected)
{
IPAddress  ipAddress  = Dns.GetHostEntry(server).AddressList[0];
IPEndPoint ipEndPoint = new IPEndPoint(ipAddress, port);

socket                = new Socket(ipEndPoint.AddressFamily, SocketType.Stream, ProtocolType.Tcp);
socket.NoDelay        = true;

ADebug.Log("[Socket start connecting]");

socket.BeginConnect
(
ipEndPoint,
(IAsyncResult ar) =>
{
ADebug.Log("[Socket connected]");

socket.EndSend(ar);
onConnected();
StartReceived();
},
null // no need
);
}


解析地址是标准做法。BeginConnect方法的第二个参数,是一个匿名函数,在连接建立完成后回调。socket.EndSend(ar); 是一个必须的操作,结束后台线程的挂起。这里,完成连接后,回调了一个用户函数,接着就要启动接受数据的函数。

然后,异步接受数据。

先定义一些接受数据的结构。

private static int        bufferSize    = 1024;
private static byte[]     buffer        = new byte[bufferSize];
private static List<byte> receiveBuffer = new List<byte>(bufferSize);


buffer 是用来异步接受数据,每次存放的数据结构。数据是一个包一个包回发的,如果数据量大,一次是接受不完的,每次接受一点。这样,每次接受一些都放在receiveBuffer,等到数据完成后一次性处理。

private static void StartReceived()
{
socket.BeginReceive
(
buffer,
0,
bufferSize,
SocketFlags.None,
Received,
null
);
}


BeginReceive调用后,后台线程开始接受数据,每次接收到数据,都会调用回调函数Received,而接受的数据就会放在buffer里面。

private static void Received(IAsyncResult ar)
{
int read = socket.EndReceive(ar);

if (read > 0)
{
byte[] bytes = new byte[read];
Buffer.BlockCopy(buffer, 0 , bytes, 0, read);
receiveBuffer.AddRange(bytes);
}

if (receiveBuffer.Count > 4)
{
byte[] lenBytes = receiveBuffer.GetRange(0, 4).ToArray();
int    len      = IPAddress.HostToNetworkOrder(BitConverter.ToInt32(lenBytes, 0));

// one protocol data received
if (receiveBuffer.Count - 4 >= len)
{
byte[] dataBytes = receiveBuffer.GetRange(4, len).ToArray();
}
else
{
// protocol data not complete
}
}

// continue to receive listen
StartReceived();
}


int read = socket.EndReceive(ar); 是标准做法。结束后台线程挂起,并且获得真实读取的数据字节数。如果read > 0 说明有数据读取,否则表示没有数据可读了。这里的处理逻辑是,如果有可以读取的数据,就把数据从buffer中拷贝到receiveBuffer中,由于receiveBuffer是动态增长的可以存放任意长度的数据。

接下来,我们判断前4个字节,这里有网络数据大端转c#小端的过程,一般是网络数据长度。如果拿不到长度,说明数据没有发完,继续接收,如果拿到了完整的数据流。那么我们就把整个数据流方式receiveBuffer里面,这样receiveBuffer就有了整个数据缓冲字节,可以交给逻辑代码处理。

StartReceived(); 是一个递归调用,继续开始监听接收数据,如此循环,来不断的监听网络数据。

注意:如果socket连接的服务器没有响应的话,BeginReceive的回调函数式不会响应的,会挂起直到有数据回来。

最后,就是发送数据。

public static void SocketSend(Action<NetStream> OnSetStream, Action OnComplete)
{
ADebug.Assert(OnSetStream != null, "Please set send OnSetStream");
ADebug.Assert(OnComplete  != null, "Please set send OnComplete");

NetStream stream = new NetStream();

stream.WriteInt32(0); // temp length

OnSetStream(stream);

stream.Seek(0);
// real length
stream.WriteInt32(stream.GetLength() - 4);

byte[] buffer = stream.GetBuffer();

socket.BeginSend
(
buffer,
0,
buffer.Length,
SocketFlags.None,
(IAsyncResult ar) =>
{
socket.EndSend(ar);
stream.Close();
OnComplete();
},
null // no need
);
}


这里的设计是

生成一个可以写内存流结构,写入一个0长度。

然后交给OnStream回调函数,让用户去向流里面写数据。

然后,把流定位到开始那个0的长度上,写入真实的数据长度。

最后,用BeginSend发送这个流。

在发送回调函数里面, socket.EndSend(ar) 会返回发送的真实数据长度。但,貌似这个数据一定会和发送buffer的长度一致。我们回调OnComplete通知用户发送完成。

另外,提供一下 NetStream 的封装,是针对网络数据流的读写的,包括了大小端的转换。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: