AS3 TCP Socket 拆包

本文介绍了在Adobe Flash的ActionScript 3 (AS3)中如何处理TCP Socket接收数据时的拆包问题。针对不同操作系统下网络模型的不同表现,文中提供了一段具体的代码示例来说明如何读取和解析接收到的数据包。

AS3 TCP Socket 拆包

  • 作者:柳大·Poechant(钟超)
  • 邮箱:zhongchao.ustc#gmail.com(# -> @)
  • 博客:Blog.youkuaiyun.com/Poechant
  • 日期:June 2nd, 2012

在 AS3 中的 Socket 只有 TCP 一种方式,而 TCP 就必然面临拆包的问题。对于 Windows、Linux、Mac等操作系统的不同网络模型,Flash 程序在不拆包下的运行糟糕程度不一样,我的亲身体会是 Windows 的网络模型会积攒更大的缓冲区数据让 Flash TCP Socket 每次收到的数据量都比 Mac 上的多,所以情况就更糟糕。这时候拆包的效果就十分明显。

下面实例中的 _socket 和 lastReservedLength 都是在函数外部定义的,其定义不影响程序的理解。各段含义我写在了程序的注释中。

部分代码如下:


private function onResponse(e:ProgressEvent):void
{
    // Data available from socket
    while (_socket.bytesAvailable > 0)
    {
        var tmpLength:int = 0;
        
        // Last package received is incomplete, and package length maintains last reserved length
        if (lastReservedLength > 0)
        {
            // CASE 1: Package is still incomplete after receiving data from socket at least once
            if (lastReservedLength > _socket.bytesAvailable + 4) break;
                
                // CASE 2: The last part of a package
            else tmpLength = lastReservedLength;
        }
            // Last package received is complete, and package length should be read from socket 
        else
        {
            var lengthBytes:ByteArray = new ByteArray();
            lengthBytes.endian = Endian.LITTLE_ENDIAN;
            
            _socket.readBytes(lengthBytes, 0, 4); // TODO Crash!
            //Error: Error #2030: End of file was encountered.
            //at flash.net::Socket/readBytes()
            
            tmpLength = lengthBytes.readInt();
            
            // CASE 3: The first part of a package
            if (_socket.bytesAvailable < tmpLength - 4)
            {
                lastReservedLength = tmpLength;
                break;
            }
        }
        
        // CASE 4: A complete package received once
        
        // Generate ByteArray of a complete package
        var resultBytes:ByteArray = new ByteArray();
        resultBytes.writeInt(tmpLength);
        _socket.readBytes(resultBytes, 4, tmpLength - 4);
        lastReservedLength = 0;
        
        // parse the ByteArray
        resultBytes.position = 0;
        var resProto:Object = CppLibUtil.parseMediaProto(resultBytes);
        
        // Dispatch the ByteArray
        if (resProto != null) {
            
            var uri:String;
            
            try {
                uri = resProto.uri;
            } catch (e:Event) {
                trace(e);
            }
                
            dispatchEvent(new InternalEvent(uri, resProto));
        }
    }
}

-

转载请注明来自柳大的优快云博客:Blog.youkuaiyun.com/Poechant

-

#include "finstcp.h" #include <QTimer> #include <QThread> FinsTCP::FinsTCP(QObject *parent) : QObject{parent} { } FinsTCP::~FinsTCP() { if(tcpIsConnected) { emit stopSignal(); } closeNetWorkPort(); } void FinsTCP::running() { if(!connectPLC()) return; QTimer timer; connect(&timer, &QTimer::timeout, [this]() { if(tcpIsConnected) { sendCommandRead(QByteArray("DM"), 0, 0, 5); } else if(!tcpIsConnected) { emit stopSignal(); } }); timer.start(1000); QEventLoop loop; connect(this, &FinsTCP::stopSignal, &loop, &QEventLoop::quit); loop.exec(); } bool FinsTCP::connectPLC() { if(tcpSocket) { tcpSocket->disconnect(); if(tcpSocket->isOpen()) { tcpSocket->close(); } delete tcpSocket; tcpSocket = nullptr; } isHandShake = false; tcpIsConnected = false; tcpSocket = new QTcpSocket(this); //tcpSocket连接上就修改标志并发送握手报文 connect(tcpSocket, &QTcpSocket::connected, this, &FinsTCP::onConnected); //tcpSocket断开就修改标志发出警告 connect(tcpSocket, &QTcpSocket::disconnected, this, &FinsTCP::onDisConnected); //接收到plc返回报文就处理数据 connect(tcpSocket, &QTcpSocket::readyRead, this, &FinsTCP::handleReadyRead); tcpSocket->connectToHost(tcpHost, tcpPort); if(tcpSocket->waitForConnected(500)) //同步等待连接0.5秒 { emit sendMessageToWindow("网口连接成功"); return true; } else { emit sendMessageToWindow("网口连接失败"); return false; } } void FinsTCP::closeNetWorkPort() { if(tcpSocket && tcpSocket->isOpen()) { tcpSocket->close(); emit sendMessageToWindow("网口关闭成功"); delete tcpSocket; tcpSocket = nullptr; } } QByteArray FinsTCP::buildHeaderFINS() { QByteArray headFINS; headFINS.append(QByteArray::fromHex("46494E53")); //FINS //握手报文头部 if(!isHandShake) { headFINS.append(QByteArray::fromHex("0000000C")); //后面报文长度 headFINS.append(QByteArray::fromHex("00000000")); //命令 headFINS.append(QByteArray::fromHex("00000000")); //错误码 } else { headFINS.append(QByteArray::fromHex("0000001A")); //后面报文长度 headFINS.append(QByteArray::fromHex("00000002")); //命令 headFINS.append(QByteArray::fromHex("00000000")); //错误码 } return headFINS; } void FinsTCP::sendHandShake() { handShakeCmd = buildHeaderFINS(); //报文最后的客户机节点地址可变 //将数字转为十六进制字符串,补齐位数,再用 QByteArray::fromHex 转换 QString PCipToString = QString("%1").arg(PCip, 8, 16, QLatin1Char(&#39;0&#39;)); handShakeCmd.append(QByteArray::fromHex(PCipToString.toLatin1())); tcpSocket->write(handShakeCmd); handShakeCmd.clear(); } void FinsTCP::sendCommandRead(QByteArray localAddr, quint16 byteAddr, quint8 bitAddr, quint16 count) { QMap<QByteArray, QByteArray> mapping = { {"DM", "82"}, {"CIO", "B0"}, {"HR", "09"}, {"TIM", "01"} }; QByteArray localAddrCode = mapping.value(localAddr); cmd = buildHeaderFINS(); cmd.append(QByteArray::fromHex("80000200")); QString PLCipToString = QString("%1").arg(PLCip, 2, 16, QLatin1Char(&#39;0&#39;)); cmd.append(QByteArray::fromHex(PLCipToString.toLatin1())); cmd.append(QByteArray::fromHex("0000")); QString PCipToString = QString("%1").arg(PCip, 2, 16, QLatin1Char(&#39;0&#39;)); cmd.append(QByteArray::fromHex(PCipToString.toLatin1())); cmd.append(QByteArray::fromHex("00FF0101")); cmd.append(QByteArray::fromHex(localAddrCode)); // 读取位置 - 小端序 QByteArray addressBytes; addressBytes.append(static_cast<char>((byteAddr >> 8) & 0xFF)); // 高字节 addressBytes.append(static_cast<char>(byteAddr & 0xFF)); // 低字节 addressBytes.append(static_cast<char>(bitAddr)); // 位地址 cmd.append(addressBytes); // 读取数量 - 小端字节序 QByteArray countBytes; countBytes.append(static_cast<char>((count >> 8) & 0xFF)); // 高字节 countBytes.append(static_cast<char>(count & 0xFF)); // 低字节 cmd.append(countBytes); tcpSocket->write(cmd); cmd.clear(); } void FinsTCP::handleReadyRead() { if(!isHandShake) { handleHandShake(); } else { handleNormalData(); } } void FinsTCP::onConnected() { tcpIsConnected = true; sendHandShake(); } void FinsTCP::onDisConnected() { tcpIsConnected = false; emit sendMessageToWindow("网口已断开,请重启PLC!"); } void FinsTCP::handleHandShake() { // const int HANDSHAKE_SIZE = 24; buffer = tcpSocket->readAll(); qDebug() << buffer; // if(buffer.size() != HANDSHAKE_SIZE) // { // emit sendMessageToWindow("握手响应报文长度有误,请检查你发送的握手报文!"); // return; // } isHandShake = true; buffer.clear(); } void FinsTCP::handleNormalData() { buffer = tcpSocket->readAll(); qDebug() << buffer.toHex(); buffer.clear(); } 处理握手报文那里没有写完,看看其他地方有什么隐藏的问题,不要动我的整体代码,告诉我问题在哪并解决
最新发布
11-13
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值