QT案例实战1 - 从零开始编写一个OCR工具软件 (9) 使用QTcpServer建立一个Tcp服务端

一、功能简述

        QT6.4正式增加了QHttpServer,在这之前是可以使用QTcpServer进行自定义通讯。这里是想要实现一个简单的通过Tcp为外部提供ocr识别功能的demo。

        关于QTcpServer,像其他很多语言一样,都是支持阻塞和非阻塞两种方式。这里使用的是非阻塞模式,个人感觉QT的封装是I/O多路复用的非常清晰易用的典范,相比Java中的nio包考虑的方方面面就更多,但是也更加抽象,不是特别易于理解。

        这里使用QTcpServer大致流程如下:

        1、创建QTcpServer,并实现newConnectionSlot等待新连接。

        2、每当一个新连接到来,就会得到对应的一个新的socket,绑定socket的readyRead信号(这里就是i/o多路复用的体现,不需要程序员进行阻塞线程等待消息,而是QT来轮询,有了消息调用我们的槽函数)。

        3、每当得到一个socket,就给socket命名,将socket保存到一个类里面,并将这个类插入到一个集合中,方便后续识别/管理/销毁。

        4、socket接收消息后,解析并启动新线程进行ocr识别,完成后返回结果。

        5、socket断开后销毁各种对象,从map中移除。

二、参考代码

1、启动QTcpServer

        这里绑定了18080端口,另外connect了newConnectionSlot。

m_tcpServer = new QTcpServer(this);
m_tcpServer->listen(QHostAddress::Any, 18080);
connect(m_tcpServer, SIGNAL(newConnection()), this, SLOT(newConnectionSlot()));
connect(m_tcpServer, SIGNAL(acceptError(QAbstractSocket::SocketError)), this, SLOT(errorStringSlot()));

2、newConnectionSlot

       这里的lists是一个QMap<int, _socketanddata*> lists,key是socket的描述符,_socketanddata是自定义类的指针。

    _socketanddata* item = new _socketanddata;
    item->m_tcpSocket = m_tcpServer->nextPendingConnection();
    connect(item->m_tcpSocket, SIGNAL(readyRead()), this, SLOT(sendMsg()));
    connect(item->m_tcpSocket, SIGNAL(disconnected()), this, SLOT(disconnected()));
    if(item->m_tcpSocket->socketDescriptor() > -1)
    {
        item->m_tcpSocket->setObjectName(QString::number(item->m_tcpSocket->socketDescriptor(), 10));
        lists.insert(item->m_tcpSocket->socketDescriptor(), item);
    }

        这里的_socketanddata是一个自定义类,里面包含了(1)socket的指针对象(2)Ocr线程的对象(3)m_requestInfo是客户端发送来的消息(4)headers是解析出来的http头信息,这里是因为使用postman进行的http post,而没有使用tcp客户端连接,所以会有http头。(5)_outText是ocr识别的结果。(6)m_thread_state 是线程状态的标识。

    class _socketanddata
    {
        public:
            ~_socketanddata()
            {
                headers.clear();
                m_requestInfo.clear();
                m_requestInfo.squeeze();
                qDebug()<< "m_thread_state:" << m_thread_state;
                if(m_thread_state == 1)
                {
                    m_thread->deleteLater();
                }
                m_tcpSocket->deleteLater();
                _outText.clear();
            };
            QTcpSocket* m_tcpSocket;
            QString m_requestInfo;
            QMap<QByteArray, QByteArray> headers;
            MyThreadForHttpOcr* m_thread;
            std::string _outText;
            int m_thread_state = 0;
    };

        socket断开的

void ocrhttpservice::disconnected()
{
    //获取socket名称
    QString s_name = this->sender()->objectName();
    delete lists.value(s_name.toInt());
}

3、sendMsg

        这是接收消息的方法,因为有一些自己简单解析http头等代码十分不严谨,所以不具备参考意义,只有几点需要注意。

        一是如果消息过长,则此方法会被多次调用,则需要判断数据完整性,另外个人认为,一般情况下主动使用tcp协议进行通讯,目的是什么?传输大量数据的必要性和可替代性是值得考量的。我这里的目的主要要是为了了解QT的相关机制。

        二是这个场景我们需要维护多个socket和线程,不过此场景没有线程间的通信。

        三是这里没有使用智能指针等,所以需要注意内存的释放和异常处理。

void ocrhttpservice::sendMsg()
{
    //获取socket名称
    QString s_name = this->sender()->objectName();

    //读取数据
    lists.value(s_name.toInt())->m_requestInfo += lists.value(s_name.toInt())->m_tcpSocket->readAll();

    //static QRegularExpression exp("[\r\n\r\n]");
    //解析请求信息,这里使用raw提交数据,首先使用\r\n\r\n分割,把header信息和body分割
    QStringList list = lists.value(s_name.toInt())->m_requestInfo.split("\r\n\r\n", Qt::SkipEmptyParts);
    if(list.size()>0)
    {
        //解析头部信息
        QByteArray httpHeaders = list[0].toUtf8();
        if(lists.value(s_name.toInt())->headers.empty())
        {
            // 丢弃第一行
            httpHeaders = httpHeaders.mid(httpHeaders.indexOf('\n') + 1).trimmed();
            foreach(QByteArray line, httpHeaders.split('\n')) {
                int colon = line.indexOf(':');
                QByteArray headerName = line.left(colon).trimmed();
                QByteArray headerValue = line.mid(colon + 1).trimmed();
                //qDebug()<<headerName + " : " <<headerValue;
                lists.value(s_name.toInt())->headers.insert(headerName, headerValue);
            }
        }

        //如果数据包比较大,一次没有接受完整,这个方法就会被调用多次。
        //所以下面的逻辑就有问题了,要么根据长度判断是否接收完整,或者直接不允许提交图片
        //判断Post或者是Get。
        if(list[0].indexOf("POST") == 0 && list.size()>1)
        {
            QString le = "Content-Length";
            int length = lists.value(s_name.toInt())->headers.value(le.toUtf8()).toInt();
            int body_length = list[1].toUtf8().length();
            //qDebug() << list[1];

            if(length == body_length)
            {
                //json解析
                QJsonDocument m_document;
                QJsonParseError error;
                m_document = QJsonDocument::fromJson(list[1].toUtf8(), &error);
                QJsonObject object;
                if (m_document.isObject()) {
                    object = m_document.object();
                }

                QString ocr_engine = object.value("ocr_engine").toString();
                QString http_type = object.value("http_type").toString();
                QString pic_url = object.value("pic_url").toString();
                QString pic_data = object.value("pic_data").toString();

                //参数异常检查
                if(QString::compare(http_type, "pull", Qt::CaseInsensitive)==0)
                {
                    if(pic_url.isEmpty())
                    {
                        QString respone("图片路径不能为空");
                        sendResponeText(s_name, respone);
                        return;
                    }
                }
                if(QString::compare(http_type, "push", Qt::CaseInsensitive)==0)
                {
                    if(pic_data.isEmpty())
                    {
                         QString respone("图片数据不能为空");
                        sendResponeText(s_name, respone);
                        return;
                    }
                }

                //开启线程,线程内保存图片并解析OCR,然后返回输出
                lists.value(s_name.toInt())->m_thread = new MyThreadForHttpOcr;
                lists.value(s_name.toInt())->m_thread->init(ocr_engine, http_type, pic_url, pic_data,
                                                            QString("%1\\screen%2.jpg").arg(qApp->applicationDirPath().replace("/", "\\")).arg(s_name).toStdString(),
                                                            s_name);

                //connect(lists.value(s_name.toInt())->m_thread, &QThread::finished, this, &QObject::deleteLater);
                connect(lists.value(s_name.toInt())->m_thread, &MyThreadForHttpOcr::getRecognitionText,this,&ocrhttpservice::getRecognitionText);
                connect(lists.value(s_name.toInt())->m_thread, &MyThreadForHttpOcr::recognitionFinish,this,&ocrhttpservice::recognitionFinish);
                lists.value(s_name.toInt())->m_thread->start();

                lists.value(s_name.toInt())->m_thread_state = 1;
                lists.value(s_name.toInt())->m_requestInfo.clear();
                list.clear();
                list.squeeze();
                httpHeaders.clear();
                httpHeaders.squeeze();
                lists.value(s_name.toInt())->headers.clear();
            }
        }
    }
}

4、识别完整返回消息

void ocrhttpservice::sendResponeText(QString socket_id, QString respone)
{
    qDebug()<<"参数长度:"<<respone.toUtf8().length();
    QString str = "HTTP/1.1 200 OK\r\n";
    str.append("Server:nginx\r\n");
    str.append("Content-Type:application/json;charset=UTF-8\r\n");
    str.append("Connection:keep-alive\r\n");
    str.append(QString("Content-Length:%1\r\n\r\n").arg(respone.toUtf8().length()));
    str.append(respone);
    lists.value(socket_id.toInt())->m_tcpSocket->write(str.toStdString().c_str());
    lists.value(socket_id.toInt())->_outText = "";
    lists.value(socket_id.toInt())->m_thread->deleteLater();
    lists.value(socket_id.toInt())->m_thread_state = 2;
}


void ocrhttpservice::getRecognitionText(QString socket_id, std::string outText)
{
    lists.value(socket_id.toInt())->_outText = outText;
}

/**
 * @brief TextRecognition::recognitionFinish
 * 识别完成
 */
void ocrhttpservice::recognitionFinish(QString socket_id)
{
    QString str(lists.value(socket_id.toInt())->_outText.c_str());
    sendResponeText(socket_id, str);
}

猜你喜欢

转载自blog.csdn.net/bashendixie5/article/details/127190462