QT case combat 1 - Escriba un software de herramienta OCR desde cero (9) Use QTcpServer para construir un servidor Tcp

1. Resumen de funciones

        QT6.4 agregó oficialmente QHttpServer. Antes de eso, QTcpServer podía usarse para comunicaciones personalizadas. Aquí hay una demostración que quiere implementar una función de reconocimiento ocr simple para externo a través de Tcp.

        Con respecto a QTcpServer, como muchos otros lenguajes, admite métodos de bloqueo y no bloqueo. Aquí se usa el modo sin bloqueo. Personalmente, creo que la encapsulación de QT es un modelo muy claro y fácil de usar de multiplexación de E/S. Comparado con el paquete nio en Java, considera más aspectos, pero es también más abstracto, no particularmente fácil de entender.

        El proceso general de usar QTcpServer aquí es el siguiente:

        1. Cree un QTcpServer e implemente newConnectionSlot para esperar una nueva conexión.

        2. Cada vez que llega una nueva conexión, se obtendrá un nuevo socket correspondiente y se vinculará la señal readyRead del socket (aquí está la realización de la multiplexación de E/S, que no requiere que el programador bloquee los subprocesos para esperar los mensajes , pero QT para sondear, con el mensaje llame a nuestra función de tragamonedas).

        3. Siempre que se obtenga un socket, asigne un nombre al socket, guárdelo en una clase e inserte esta clase en una colección para su posterior identificación/gestión/destrucción.

        4. Después de que el socket recibe el mensaje, analiza e inicia un nuevo subproceso para la identificación de OCR y devuelve el resultado una vez finalizado.

        5. Después de desconectar el enchufe, varios objetos se destruyen y eliminan del mapa.

2. Código de referencia

1. Inicie QTcpServer

        El puerto 18080 está vinculado aquí y newConnectionSlot está conectado.

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, nueva ranura de conexión

       Las listas aquí son listas de QMap<int, _socketanddata*>, la clave es el descriptor de socket y _socketanddata es el puntero a la clase personalizada.

    _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);
    }

        El _socketanddata aquí es una clase personalizada, que contiene (1) el objeto puntero del socket (2) el objeto del subproceso Ocr (3) m_requestInfo es el mensaje enviado por el cliente (4) los encabezados son la información del encabezado http analizado, Esto se debe a que la publicación http se realiza mediante cartero sin utilizar la conexión del cliente tcp, por lo que habrá encabezados http. (5) _outText es el resultado del reconocimiento ocr. (6) m_thread_state es la identidad del estado del hilo.

    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;
    };

        enchufe desconectado

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

3, enviar mensaje

        Esta es la forma de recibir mensajes, porque algunos códigos, como el análisis simple de los encabezados http, son muy imprecisos, por lo que no tienen significado de referencia. Solo hay algunos puntos a los que prestar atención.

        Una es que si el mensaje es demasiado largo, este método se llamará varias veces y se debe evaluar la integridad de los datos. Además, personalmente creo que, en general, el protocolo tcp se usa activamente para la comunicación. ¿Cuál es el propósito? ? Vale la pena considerar la necesidad y la posibilidad de sustitución de transferir grandes cantidades de datos. Mi propósito aquí es principalmente comprender el mecanismo relacionado de QT.

        La segunda es que necesitamos mantener múltiples sockets e hilos en este escenario, pero no hay comunicación entre hilos en este escenario.

        La tercera es que aquí no se utilizan punteros inteligentes, por lo que debe prestar atención a la liberación de memoria y al manejo de excepciones.

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. Identifique el mensaje de respuesta completo

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);
}

Supongo que te gusta

Origin blog.csdn.net/bashendixie5/article/details/127190462
Recomendado
Clasificación