MMORPG游戏核心技术-网络通信(发送篇)(二)

发表于2016-05-30
评论0 1.5k浏览

序言:

 上一篇文章介绍了自定网络消息的接收篇,需要了解的朋友可以通过下面地址了解:

   http://gad.qq.com/article/detail/7160380

   关于网络通信,这里需要说一点题外话,这也是最近跟同行讨论比较多的问题,关于同步的问题。

   1、关于网络出现卡顿的同步问题,当客户端网络出现卡顿,之后恢复正常同时接受大量服务器消息时候的处理。建议:将客户端的表现一个状态一个状态的追赶过去,别直接跳过状态,负责客户端会出现各种表现的异常,比如:穿越障碍,空中飞行等状态。当接收到的消息量巨大的时候,瞬间产生的命令太多。也可以对部分命令进行过滤。

   2、到底是以客户端为准,还是以服务器为准。建议:根据项目组的需要,进行一个合理的取舍,如果项目组需要更好的客户端表现,以客户端为准;如果项目组需要根精确的计算,以服务器为准。当然,就算以客户端为准,也需要对客户端进行合理效验,负责外挂就太疯狂了。

   3、最需要的还是进行大量测试。然后保证通信的稳定。一套稳定的架构是游戏的基础。


正文

这一章主要给大家介绍自定网络通信的发送篇,网络消息发送,就是将客户端需要发送的消息发送到服务器,然后等待服务器处理完之后返回处理结果。在整个发送篇中

   

函数介绍

    Socket类:Socket 类为网络通信提供了一套丰富的方法和属性。

    BeginSend方法:socket类提供的异步发送消息的方法。

    SendedEnd方法:socket类提供的异步发送消息完之后回调的方法。

    Stream类:Stream 是所有流的抽象基类。 流是字节序列的抽象,例如文件、输入/输出设备、进程中通信管道或 TCP/IP 套接字。

     

整套类架构

    

   

流程

    

    

NetRunTime类

    Start函数:实例化socket定义。定义NetAsynSend最大的发送消息的长度(4096);

1
2
3
4
5
6
7
8
9
10
11
12
public void Start()
{
    if (!GameSet.m_bUseWebSocket)
    {
        m_socketClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        m_nSend = new NetAsynSend(m_socketClient, 4096);
        //接收的可能多一些
        m_nRece = new NetAsynRecevice(m_socketClient);
        m_nSend.Start();
        m_nRece.Start();
    }
}

     SendMessage函数:外部接口调用,发送消息的时候统一掉这个函数。将需要发送的消息保存到m_listSend中。

1
2
3
4
5
6
7
8
public void SendMessage(NetMessage msg)
{
    if (!IsConnected())
    {
        return;
    }
    m_listSend.Add(msg);
}

     DisPatchServerSendMsg函数:将需要发送消息的m_listSend发送给NetAsynSend进行处理。

1
2
3
4
5
6
7
8
9
10
11
12
private void DisPatchServerSendMsg(float fTime, float fDTime)
{
    if (m_nSend.SendMessage(ref m_listSend))
    {
        m_listSend.Clear();
        m_lastSendMessage = 0;
    }
    else
    {
        Debug.Log("消息发送失败!");
    }
}


NetAsynSend类

      SendMessage函数:将NetRunTime中的消息列表缓存到NetAsynSend中,如果再发送状态,将等到发送完成之后再继续发送。

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
public bool SendMessage(ref BetterList listMsg)
{
    if (listMsg.Count == 0)
    {
        return false;
    }
    var e = listMsg.GetEnumerator();
    while (e.MoveNext())
    {
        m_listMsg.Add(e.Current);
    }
    if (!GameSet.m_bUseWebSocket)
    {
        if (m_bSending)
        {
            //下帧在发送
            return false;
        }
    }
    //有消息才需要发送
    if (m_listMsg.Count > 0)
    {
        SendToSever();
    }
    return true;
}

    SendToSever函数:将发送的状态设置为true,调用SocketOutputStream中将NetMessage消息转换了二进制,开始发送消息!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void SendToSever()
{
    m_bSending = true;
    if (NetRunTime.Inst.IsConnected())
    {
        //如果还没有建立连接试着建立连接
        int iMsgNums = m_outPutStream.MakeBuffer(m_listMsg);
        if (iMsgNums > 0)
        {
            if (!GameSet.m_bUseWebSocket)
            {
                if (m_socket != null)
                {                           m_socket.BeginSend(m_outPutStream.m_steam.GetBuffer(), 0, m_outPutStream.m_steam.GetOffset(), SocketFlags.None, new AsyncCallback(SendedEnd), null);
                }
            }
            else
            {
                m_webSocket.Send(m_outPutStream.m_steam.GetBuffer(), m_outPutStream.m_steam.GetOffset());
                m_outPutStream.m_steam.Reset();
            }
        }
    }
}


SocketOutputStream类

    MakeBuffer函数:将NetMessage消息转换到消息发送存储区m_stream中。这里需要注意的是如果消息的发送长度超过了4096。将对消息进行保留,放置到下一帧发送。

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
public int MakeBuffer(BetterList lstMsg)
{
    int iFreeBuffer = (int)m_m_BufferLen;
    int i = 0;
    for (; i < lstMsg.Count; i++)
    {
        //计算发送NetMessage消息的长度
        lstMsg[i].MakeMessage();
        //这一帧发送的消息长度已经达到最大,下一帧再发送
        if (iFreeBuffer - lstMsg[i].m_uMsgLenght <= 0)
        {
            //TODO:以后扩展为可以支持任意大小的缓冲
            //Debug.LogWarning("消息缓冲区不够了。消息" + m_listDelegates[i].m_uID.ToString() + "隔帧发送");
        }
        else
        {
            //获取流的偏移位置
            int nOffset = m_steam.GetOffset();
            //NetMessage消息里面对数据转换为Btye
            int nMsgLen = lstMsg[i].ToByte(ref m_steam);
            //判断消息的偏移数据是否与消息的实际长度一致
            int nWriteLen = m_steam.GetOffset() - nOffset;
            if (nWriteLen != nMsgLen)
            {
                Debug.LogError(string.Format("{0}消息时 消息长度为{1} 实际发送的消息长度却是{2}", (NetMessageDefine)lstMsg[i].m_uID, nMsgLen, nWriteLen));
            }
            iFreeBuffer -= nMsgLen;
        }
    }
    if (i < lstMsg.Count)
    {
        for (int j = 0; j < i ; j++)
        {
            lstMsg.RemoveAt(0);
        }
    }
    else
    {
        lstMsg.Clear();
    }
    return i;
}

       

总结

     到止,自定义消息的架构基本介绍完毕。整个架构用到了5个类:NetRunTime(消息运行类)、NetAsynReveice(异步消息接收类)、NetAsynSend(异步消息发送类)、SocketInputStream(异步接收消息存储区)、SocketOutputStream(异步发送消息存储区)。对应每个类的描述都有在文章中做了详细的描述。存在疑问的伙伴,欢迎留言进行讨论。

如社区发表内容存在侵权行为,您可以点击这里查看侵权投诉指引

0个评论