客户端发送消息的时候,也是异步发送,所以和服务器直接发送不一样,需要先将bytes数据都包装在ByteArray里。

ByteArray内添加新的构造函数

1
2
3
4
5
6
7
8
public ByteArray(byte[] defaultBytes)
{
Bytes = defaultBytes;
Capacity = defaultBytes.Length;
m_InitSize = defaultBytes.Length;
ReadIdx = 0;
WriteIdx = defaultBytes.Length;
}

Capacity = defaultBytes.Length; WriteIdx = defaultBytes.Length;表示构造的ByteArray并不是空的,是一个可读不可写的状态,因为此时它的Remain(Capacity - WriteIdx)等于0

在客户端NetManager中声明一个Queue,把需要发送的ByteArray都放在这个Queue里,然后将Queue中的ByteArray一个一个发送出去。这个发送的过程是异步的。所以每次对于这个Queue的操作都要加锁。并且这个异步的发送还是递归的,会把Queue的消息都发送完。

1
2
3
4
5
6
7
8
9
10
private Queue<ByteArray> m_WriteQueue;
/// <summary>
/// 初始化链接状态
/// </summary>
void InitState()
{
//...
m_WriteQueue = new Queue<ByteArray>();
//...
}

SendMessage

NetManager中添加SendMessage方法,基本上和服务器差不多,但是多了添加到队列的操作和回调方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
public void SendMessage(MsgBase msg)
{
if(m_Socket == null && !m_Socket.Connected)
{
return;
}
if(m_IsConnecting)
{
Debug.LogError("正在连接服务器,无法发送消息");
return;
}
if(m_IsClosing)
{
Debug.LogError("正在关闭连接中,无法发送消息");
return;
}
try
{
byte[] nameBytes = MsgBase.EncodeName(msg);
byte[] bodyBytes = MsgBase.Encode(msg);
int len = nameBytes.Length + bodyBytes.Length;
byte[] headBytes = BitConverter.GetBytes(len);
byte[] sendBytes = new byte[len + headBytes.Length];
Array.Copy(headBytes,0,sendBytes, 0, headBytes.Length);
Array.Copy(nameBytes,0,sendBytes,headBytes.Length, nameBytes.Length);
Array.Copy(bodyBytes,0,sendBytes,headBytes.Length + nameBytes.Length, bodyBytes.Length);
ByteArray byteArray = new ByteArray(sendBytes);
int count = 0;
lock(m_WriteQueue)
{
m_WriteQueue.Enqueue(byteArray);
count = m_WriteQueue.Count;
}
if(count == 1)
{
m_Socket.BeginSend(sendBytes, 0, sendBytes.Length,SocketFlags.None,SendCallback,m_Socket);
}
}
catch (Exception ex)
{
Debug.LogError("SendMessage Error:" + ex);
CloseConnection();
}
}

修改CloseConnection

之前我们标注过CloseConnection还没有写完,需要判断消息的发送队列,在这里补完这部分的逻辑。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
/// <summary>
/// 关闭与远程服务器的链接
/// </summary>
/// <param name="normal">是否正常关闭</param>
public void CloseConnection(bool normal = true)
{
if(m_Socket == null || m_IsConnecting) return;

//根据是否正常关闭调整消息发送队列
if(m_IsConnecting) { return; }
if (m_WriteQueue.Count > 0)
{
m_IsClosing = true;
}
else
{
ReallyClose(normal);
}
}
void ReallyClose(bool normal = true)
{
SecretKey = "";
m_Socket.Close();
FirstEvent(NetEvent.Close, normal.ToString());
if (m_HeartThread != null && m_HeartThread.IsAlive)
{
m_HeartThread.Abort();
m_HeartThread = null;
}
if (m_MsgThread != null && m_MsgThread.IsAlive)
{
m_MsgThread.Abort();
m_MsgThread = null;
}
Debug.Log("CloseSocket");
}

NetManager中,有多处都会调用CloseConnection方法,接收数据异常了会调用,服务器没有消息了也会调用……,每次调用时,都会先判断当先的消息发送队列m_WriteQueue是否还有消息,如果还有,把m_IsClosing设为true,让SendMessage的回调来彻底关闭Socket。所以我们分离出来了ReallyClose方法来彻底关闭

SendCallback回调

NetManager中添加SendCallback回调方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
/// <summary>
/// 发送结束回调
/// </summary>
/// <param name="ar"></param>
void SendCallback(IAsyncResult ar)
{
try
{
Socket socket = (Socket)ar.AsyncState;
if (socket == null || !socket.Connected) return;
//获取上一次发送的byte数量
int count = socket.EndSend(ar);
//判断是否发送完整
ByteArray ba;
lock(m_WriteQueue)
{
ba = m_WriteQueue.Peek();
}
ba.ReadIdx += count;
if(ba.Length == 0)//代表上次完整发送
{
lock(m_WriteQueue)
{
m_WriteQueue.Dequeue();
if(m_WriteQueue.Count > 0)//判断发送队列里还有没有消息
{
ba = m_WriteQueue.Peek();
}
else
{
ba = null;
}
}
}
if(ba != null)//说明上次发送不完整 或 发送队列里还有其他消息
{
//如果是不完整的消息,这里发送完整,如果是队列里的其他消息,这里发送出去
socket.BeginSend(ba.Bytes, ba.ReadIdx, ba.Length, SocketFlags.None, SendCallback, socket);
}
else if (m_IsClosing)//确保关闭链接前,先把消息发送出去。
{
ReallyClose();
}
}
catch (SocketException ex)
{
Debug.LogError("SendCallback Error: " + ex.ToString());
CloseConnection();
}
}

在回调方法中,使用socket.EndSend(ar);来获取上一次发送的byte数量,来判断是否完整发送了消息。在回调中发现没有消息可以发了并且m_IsClosing已经为true,说明此时需要关闭链接,调用ReallyClose方法。