tcp unity 图片_谈谈Unity游戏TCP连接和网络重连

本文详细介绍了Unity中使用TcpClient进行TCP连接的方法,包括建立连接、异步处理连接结果、处理连接超时、异步读取数据、发送消息的步骤。同时,讨论了如何处理数据的解包和封包,并强调了在出现异常和断线时,通过事件机制通知上层处理。此外,还提到了断线重连的策略,特别是在移动端,需要关注从后台切回前台时的网络状态检查。
摘要由CSDN通过智能技术生成

谈谈Unity游戏TCP连接和网络重连

Unity中通常使用TcpClient来进行Tcp连接,TcpClient支持异步读写,避免了我们需要另外开辟线程管理网络数据发送。

当异步读写经常会让人摸不着头脑,比较困惑。

1. 建立连接

///

/// 连接服务器

///

public void ConnectServer (string host, int port)

{

Log.Instance.infoFormat ("start connect server host:{0}, port:{1}", host, port);

lock (lockObj) {

// 关闭老的连接

if (null != client) {

Close ();

}

// 建立新的连接

client = new TcpClient ();

client.SendTimeout = 1000;

client.ReceiveTimeout = 1000;

client.NoDelay = true;

IsConnected = false;

connectingFlag = true;

try {

client.BeginConnect (host, port, new AsyncCallback (OnConnect), client);

// 这里是一个任务管理器,可以用来执行定时任务。连接时候添加一个超时检查的定时任务。

TimerManager timer = AppFacade.Instance.GetManager (ManagerName.Timer);

timer.AddTask (OnConnectTimeout, CONN_TIMEOUT);

} catch (Exception e) {

Log.Instance.error ("connect server error", e);

// 通知连接失败

NetworkManager.AddEvent (Protocal.ConnectFail, null);

}

}

}

2. 异步处理连接结果

///

/// 连接上服务器

///

void OnConnect (IAsyncResult asr)

{

lock (lockObj) {

TcpClient client = (TcpClient)asr.AsyncState;

bool validConn = (client == this.client);

connectingFlag = false;

try {

// 结束异步连接

client.EndConnect (asr);

// 非当前连接

if (!validConn) {

client.Close ();

}

if (client.Connected) {

Log.Instance.info ("connect server succ");

// 异步读socket数据

socketStream = client.GetStream ();

socketStream.BeginRead (byteBuffer, 0, MAX_READ, new AsyncCallback (OnRead), new SocketState (client, socketStream));

// 通知连接成功

IsConnected = true;

NetworkManager.AddEvent (Protocal.Connect, null);

} else {

// 通知连接失败

Log.Instance.info ("connect server failed");

NetworkManager.AddEvent (Protocal.ConnectFail, null);

}

} catch (SocketException e) {

Log.Instance.error ("connect error", e);

if (validConn) {

// 通知连接失败

NetworkManager.AddEvent (Protocal.ConnectFail, null);

} else {

client.Close ();

}

}

}

}

3. 处理连接超时

///

/// 连接超时

///

void OnConnectTimeout ()

{

lock (lockObj) {

if (connectingFlag) {

Log.Instance.error ("connect server timeout");

// 通知连接失败

NetworkManager.AddEvent (Protocal.ConnectFail, null);

}

}

}

4. 异步读取数据

///

/// 读取消息

///

void OnRead (IAsyncResult asr)

{

int bytesRead = 0; // 读取到的字节

bool validConn = false; // 是否是合法的连接

SocketState socketState = (SocketState)asr.AsyncState;

TcpClient client = socketState.client;

if (client == null || !client.Connected) {

return;

}

lock (lockObj) {

try {

validConn = (client == this.client);

NetworkStream socketStream = socketState.socketStream;

// 读取字节流到缓冲区

bytesRead = socketStream.EndRead (asr);

if (bytesRead < 1) {

if (!validConn) {

// 已经重新连接过了

socketStream.Close ();

client.Close ();

} else {

// 被动断开时

// 通知连接被断开

OnDisconnected (DisType.Disconnect, "bytesRead < 1");

}

return;

}

// 接受数据包,写入缓冲区

OnReceive (byteBuffer, bytesRead);

// 再次监听服务器发过来的新消息

Array.Clear (byteBuffer, 0, byteBuffer.Length); //清空数组

socketStream.BeginRead (byteBuffer, 0, MAX_READ, new AsyncCallback (OnRead), socketState);

} catch (Exception e) {

Log.Instance.errorFormat ("read data error, connect valid:{0}", e, validConn);

if (validConn) {

// 通知连接被断开

OnDisconnected (DisType.Exception, e);

} else {

socketStream.Close ();

client.Close ();

}

}

}

// 对消息进行解码

if (bytesRead > 0) {

OnDecodeMessage ();

}

}

对于数据的解包和封包,推荐MiscUtil这个库十分好用,大端小端模式都能很好处理。

5. 发送消息

///

/// 发送消息

///

public bool SendMessage (Request request)

{

try {

bool ret = WriteMessage (request.ToBytes ());

request.Clear ();

return ret;

} catch (Exception e) {

Log.Instance.errorFormat ("write message error, requestId:{0}", e, request.GetRequestId ());

}

return false;

}

///

/// 写数据

///

bool WriteMessage (byte[] message)

{

bool ret = true;

using (MemoryStream ms = new MemoryStream ()) {

ms.Position = 0;

EndianBinaryWriter writer = new EndianBinaryWriter (EndianBitConverter.Big, ms);

int msglen = message.Length;

writer.Write (msglen);

writer.Write (message);

writer.Flush ();

lock (lockObj) {

if (null != socketStream) {

byte[] bytes = ms.ToArray ();

socketStream.BeginWrite (bytes, 0, bytes.Length, new AsyncCallback (OnWrite), socketStream);

ret = true;

} else {

Log.Instance.warn ("write data, but socket not connected");

ret = false;

}

}

}

return ret;

}

///

/// 向链接写入数据流

///

void OnWrite (IAsyncResult r)

{

lock (lockObj) {

try {

NetworkStream socketStream = (NetworkStream)r.AsyncState;

socketStream.EndWrite (r);

} catch (Exception e) {

Log.Instance.error ("write data error", e);

if ((e is IOException) && socketStream == this.socketStream) {

// IO 异常并且还是当前连接

OnDisconnected (DisType.Exception, e);

}

}

}

}

6. 总结

为了防止并发,这里使用lock对于共享变量client、socketStream是使用都加了锁。

在出现异常,连接断开的时候都通过事件机制抛给上层使用者,由上层使用者决定如何

处理这个异常。

7. 断线重连处理

断线重连第一步监听TcpClient使用的过程中,对于异常发生之后触发重连逻辑。

但在移动端比较重要的一点还要做好从后台切回前台过程中及时检查网络连接状态

及时重连。

Android后台切回前台的事件流

onPause(切回后台之前) -> onResume -> focusChanged(false) -> focusChanged(true) (后面3个都是要在前台才能收到)

不切出游戏暂停游戏 focusChanged(false) -> focusChanged(true) // 如呼出键盘,或者下拉通知栏

IOS后台切回前台的事件流

IOS的消息顺序 resignActive(切回后台之前) -> enterBackground -> enterForeground -> becomeActive (后面3个都是要在前台才能收到)

不切出游戏暂停游戏 resignctive -> becomeActive

由上不难看出:

Android可以监听focusChanged(false) -> focusChanged(true) ,注意onPause要当做一次focusChanged(false)。记录两次事件的间隔,比如间隔时间过长直接重新建立连接,比较短的话立即做一次

网络检查。

IOS可以监听resignctive -> becomeActive

TcpClient做网络检查可以发送一个0字节的包,代码如下:

///

/// 检查socket状态

///

/// true, if socket was checked, false otherwise.

public bool CheckSocketState ()

{

Log.Instance.info ("check socket state start");

// socket流为空

if (client == null) {

return true;

}

// 不在连接状态

if (!client.Connected) {

Log.Instance.info ("check socket state end, socket is not connected");

return false;

}

// 判断连接状态

bool connectState = true;

Socket socket = client.Client;

bool blockingState = socket.Blocking;

try {

byte[] tmp = new byte[1];

socket.Blocking = false;

socket.Send (tmp, 0, 0);

connectState = true; // 若Send错误会跳去执行catch体,而不会执行其try体里其之后的代码

Log.Instance.info("check socket state succ");

} catch (SocketException e) {

Log.Instance.warnFormat ("check socket error, errorCode:{0}", e.NativeErrorCode);

// 10035 == WSAEWOULDBLOCK

if (e.NativeErrorCode.Equals (10035)) {

// Still Connected, but the Send would block

connectState = true;

} else {

// Disconnected

connectState = false;

}

} finally {

socket.Blocking = blockingState;

}

return connectState;

}

作者:Aodota

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值