在网络游戏开发中,一些游戏需要使用长连接的方式进行网络通信,即使用Socket建立长连接。那么在Unity3d中,如何使用C#与服务端建立长连接呢?为什么 要说使用异步呢?我们知道,在Unity3d中,每个游戏画面的播放都是以帖的概念循环播放的。而且只能在UI线程中播放,在其它线程不可以操作UI有关的东西,这都是网络通信需要解决的问题。
使用Socket创建连接
众所周知,在游戏客户端启动之后,一定有一个时机是创建网络连接的,比如一般是选游戏大区这后,或用户点击进入游戏时,这都是由UI层触发点击和创建网络连接的。但是网络连接是一个IO阻塞操作,如果直接使用Socket的同步方法,会卡住当前UI线程,导致游戏画面出现卡顿现象。所以要么使用协程,要么便需要使用异步创建连接。为了方便创建socket连接,这里使用异步连接方式。如下面代码所示:
public void ConnectServer()
{
this.Close();
try
{
_socket = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
SocketAsyncEventArgs args = new SocketAsyncEventArgs();//创建连接参数对象
this.endPoint = new IPEndPoint(IPAddress.Parse(host), port);
args.RemoteEndPoint = this.endPoint;
args.Completed += OnConnectedCompleted;//添加连接创建成功监听
_socket.ConnectAsync(args); //异步创建连接
}
catch(Exception e)
{
Debug.Log("服务器连接异常:" + e);
}
}
private void OnConnectedCompleted(object sender,SocketAsyncEventArgs args)
{
try
{ ///连接创建成功监听处理
if (args.SocketError != SocketError.Success)
{
//通知上层连接失败
CompleteConnectServerHandler?.Invoke(ConnectedStatus.ConnectFailed);
}
else
{
Debug.Log("网络连接成功线程:" + Thread.CurrentThread.ManagedThreadId.ToString());
//通知上层连接创建成功
CompleteConnectServerHandler?.Invoke(ConnectedStatus.ConnectedSucess);
StartReceiveMessage(); //启动接收消息
}
}
catch(Exception e)
{
Debug.Log("开启接收数据异常" + e);
}
}
发送消息
一般来说,消息的发送都是UI层触发的,比如点击某个按钮,执行了某个操作,就会向服务器获取或保存数据。所以也需要使用协程或异步的方式发送消息,这里使用异步的方式。如下面代码所示:
public void SendToServer(byte[] data)
{
try
{
if (_socket == null || !_socket.Connected)
{
Debug.Log("socket未连接,发送消息失败");
return;
}
//创建发送参数
SocketAsyncEventArgs sendEventArgs = new SocketAsyncEventArgs();
sendEventArgs.RemoteEndPoint = endPoint;
//设置要发送的数据
sendEventArgs.SetBuffer(data, 0, data.Length);
//异步发送数据
_socket.SendAsync(sendEventArgs);
}
catch(Exception e)
{
Debug.Log("发送数据异常:" + e);
}
}
由于Socket制作是一种非常底层的操作,所以这里直接发送byte[],具体发送什么样的数据,由应用层决定。
接收数据
由于我们使用的是Socket自带的异步创建连接,发送数据,所以接收数据也是在socket的异步线程中。如下面代码所示:
private void StartReceiveMessage()
{
//启动接收消息
SocketAsyncEventArgs receiveArgs = new SocketAsyncEventArgs();
//设置接收消息的缓存大小,正式项目中可以放在配置 文件中
byte[] buffer = new byte[1024 * 512];
//设置接收缓存
receiveArgs.SetBuffer(buffer, 0, buffer.Length);
receiveArgs.RemoteEndPoint = this.endPoint;
receiveArgs.Completed += OnReceiveCompleted; //接收成功
_socket.ReceiveAsync(receiveArgs);//开始异步接收监听
}
public void OnReceiveCompleted(object sender,SocketAsyncEventArgs args)
{
try
{
Debug.Log("网络接收成功线程:" + Thread.CurrentThread.ManagedThreadId.ToString());
if (args.SocketError == SocketError.Success && args.BytesTransferred > 0)
{
//创建读取数据的缓存
byte[] bytes = new byte[args.BytesTransferred];
//将数据复制到缓存中
Buffer.BlockCopy(args.Buffer, 0, bytes, 0, bytes.Length);
if(ReceiveSocketDataHandler == null)
{
Debug.Log("没有处理接收消息的事件 ");
}
//接收数据成功,调上层处理接收数据的事件
ReceiveSocketDataHandler?.Invoke(bytes);
//再次启动接收数据监听,接收下次的数据。
StartReceiveMessage();
}
else
{
CompleteConnectServerHandler(ConnectedStatus.Disconnect);
}
}
catch(Exception e)
{
Debug.Log("接收数据异常:" + e);
}
}
接收数据的方法会在连接创建成功时调一次,它一直监听服务器的数据到来。当接收到服务器的数据时,我们会调用上层处理数据的事件 ,即ReceiveSocketDataHandler?.Invoke(bytes);
,首先要做的就是对接收的数据进行断包和粘包处理。这个在下篇文章中再说。
详细的代码,可以参考项目源码:http://www.xinyues.com/h-nd-195.html#_np=2_627
可以关注微信公众号,及时接收文章推送