一、功能简述
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);
}