Erlang与Flash的Socket通讯-03

分享ErlangFlashSocketTCP by 达达 at 2010-08-19

今天和阿灿为项目做一个多人在线行走的Demo,在Flash端遇到数据“粘包”的问题,下面记录一下原理和处理办法。

什么是“粘包”呢?粘包是指数据发送端希望接受端收到的数据包因为网络的“流”特性,使得连续的数据包的字节连贯在一起,导致接收端无法以数据包为单位处理数据的情况。

打个比方吧,A和B两个人通过一根水管互相传递水,他们通过每次接到的水的重量来判断对方表达的意思。如果A连续乘了不同重量的几杯水倒入水管中传递给B,B那一头打开水龙头接到的只能是连贯的水流,而不是一杯杯的水,数据在网络上传递也是同样道理,数据是以流的方式传递的,通讯双方需要通过一定的协议来分割数据流。

防止粘包的典型处理方式有两种,一种是通过判断结束符来分割数据包,这种方式就得确保分隔符不会出现在数据包的主体中,有一定的局限性;另一种是通过在数据包的开始部分加上固定字节长度的一个头部,用以表示数据包的长度。

Erlang天生就具备了解析数据包长度的功能,通过设置socket的{packet, 2}属性,Erlang会在接收到数据时会自动按数据的头两个字节表示的短整型值来分割数据包,所以在Erlang端没有粘包问题。但是在Flash端如果惯性思维的以为数据包会分次到达并分别触发SocketData事件那就会出问题了,由于网络的不可靠性,一个数据包有可能在触发N次SocketData事件后才完整接收完毕,也可能一次SocketData事件接收到的数据包含连续多个数据包,甚至尾部有可能包含最后一个数据包的起始部分但又不完整。

之前的实验有证明了Flash端通过writeUTF函数可以跟Erlang的{packet, 2}模式对应,但是这种方式只能传递字符串,当需要传递二进制数据的时候就只能靠自己拆包和解包了。下面是与Erlang的{packet, 2}模式对应的Flash客户端接收数据的代码:

private static var packet_len : int;
private static var packet_buf : ByteArray;

private static function OnSocketData (event:ProgressEvent) : void
{
    while (socket.bytesAvailable > 0)
    {
        if (packet_len == -1)
        {
            if (socket.bytesAvailable < 2)
                break;

            packet_len = socket.readShort();

            packet_buf = new ByteArray();
        }

        socket.readBytes(packet_buf, 0, packet_len - packet_buf.length);

        if (packet_buf.length == packet_len)
        {
            ParsePacket(packet_buf);

            packet_len = -1;
        }
    }
}

数据会被暂存到packet_buf中,直到packet_buf中数据达到预期的数据包大小,才将packet_buf传递给ParsePacket进行解析,否则,如果socket还有可读数据就尝试读取数据包剩余的数据,如果没有则等待下次SocketData事件。