使用QT,C++,编写的http服务器

5 篇文章 0 订阅
2 篇文章 0 订阅

最近在做骨料全自动自动装车项目,项目需要对车辆进行定位,对车厢尺寸进行测量,因此用到了激光雷达。具体的做法,在linux工控机上运行一个程序,使用qt,C++开发,对接激光雷达处理数据,得到车辆尺寸,然后将尺寸发送给软件系统。这里工控机和激光雷达组成一个独立的扫描系统。(激光雷达这个程序随后再共享吧)

原来测量系统和上位软件系统通讯,采用了tcp方式,通过发送自定义指令,测量,并返回数据。后来发现这种方式,需要不停的去维护tcp链接,断线重连,上位机维护比较复杂,后来想到给激光雷达系统弄成http方式的,上位使用get请求,就可以拿到测量结果,比较方便。因此用C++写了一个http服务。

由于前面对http协议进行过比较详细的解析,因此本次也比较简单,http协议解析见:使用java手写一个http服务,Http协议解析_javahttp服务-CSDN博客

这里具体的思路其实很简单:

step 1: 程序启动,读取配置文件,监听端口,等待客户端链接。主要代码如下:

/**
 * @brief HttpServer::start 开启服务器
 */
void HttpServer::start(){
    if(svr->isListening()){
        return;//已经开始了,就不要再监听了
    }
    int port=Config::getInstance()->value("server/port").toInt();//获取端口
    if(!svr->listen(QHostAddress::Any,port)){
        qDebug()<<"started http server on port"<<port<<"error";
    }
    else {
        qDebug()<<"listsen port"<<port<<"success";
    }

}

step 2: 当有客户端链接进来,在线程池(QThreadPool)启动一个request处理对象(QRunable),解析客户端发过来的请求数据,这里主要解析请求地址,主要代码如下:

/**
 * @brief QServer::onConnect 新的链接,客户端
 */
void HttpServer::onConnect(){

    if(svr->hasPendingConnections()){        //如果服务端有一个待处理的连接,就返回真,否则返回假
        QTcpSocket* socket = svr->nextPendingConnection(); //将下一个挂起的连接作为已连接的qtcsocket对象返回。
        if(!socket->isValid()){            //套接字是否有效
            qDebug()<<"invalid socket";
            return;
        }
        Request* r=new Request(socket);//创建一个request对象,处理请求数据
        connect(r,SIGNAL(onFinish(Request*)),this,SLOT(onRequestFinish(Request*)));//用于监听请求处理完毕消息
        QThreadPool::globalInstance()->start((QRunnable*)r);//交给线程池处理

    }
    else{
        qDebug()<<"client err";
    }
}

step 3:从配置文件读取网址根目录,加上当前地址,读取文件,作为请求返回的body。如果文件不存在,把请求返回code,设置为404,就是没有这个文件。如果有,请求返回code设置为200,就是正常。这一部分代码稍微长点。

/**
 * @brief Request::doResponse 该方法由主线程调用,因为客户端是在主线程产生的,不能跨线程调用
 */
void Request::doResponse(){
    this->response->doResponse();//response返回
}
/**
 * @brief RequestHandler::run 处理主函数,这是在子线程运行的,不耽误主线程同时处理多个请求
 */
void Request::run(){
    //step 1 接收数据
    //qDebug()<<"handle request data";
    map=new QMap<QString,QString>();//用来存储header的map集合

    QByteArray arr=client->readLine();//读取一行
    int c=5;
    while(arr.length()==0 && c-->0){//防止没有读取到数据
        QThread::msleep(20);//等待20毫秒,防止没有数据
        arr=client->readLine();//读取一行
    }

    if(arr.length()==0){//如果还是读取不到
        //没有任何数据
        response->setCode(200);
        response->setContentType("application/json");
        response->setBody(QString("{\"code\":9999,\"msg\":\"here is some thing worng\"}"));
        emit onFinish(this);//告诉主线程,处理完了
        return;
    }
    QString url(arr);//转换为字符串
    QStringList slist=url.split(" ");
    path=slist.at(1);//请求路径   
    //step 2解析header, 虽然并没有啥用
    while(!client->atEnd()){
        arr=client->readLine();//读取行
        if(arr.length()<3)continue;//跳过空行
        QString l(arr);//转换为字符串
        QStringList list=l.split(":");//按冒号分割
        if(list.size()<2)continue;//如果没有冒号,直接跳过
        QString k=list.at(0);//名称
        QString v=list.at(1);//值
        //qDebug()<<"header"<<k<<v;
        map->insert(k.trimmed(),v.trimmed());//添加到map
    }
    //qDebug()<<"path"<<path;
    if(path=="/")path=Config::getInstance()->value("server/default").toString();//默认访问文件
    qDebug()<<"path"<<path;
    QByteArray b=FileTool::getInstance()->readFile(path);//读取文件
    response->setContentType(FileTool::getInstance()->getFileExt(path));//设置返回内容
    response->setBody(b);//设置返回内容
    if(b==NULL || b.size()==0){
        response->setCode(404);//找不到就是404
    }
    else{
        response->setCode(200);//正常返回
    }

    // request finish
    emit onFinish(this);//请求结束
}

step 4:请求处理完成以后,emit一个信号,通知主线程,由主线程调用response返回。主要就是下面一行代码:

emit onFinish(this);//请求结束

step 5 :response 返回,主要就是根据,http协议格式,返回标准数据,详细的协议就不说了,代码如下:

/**
 * @brief Response::doResponse 请求结果返回
 */
void Response::doResponse(){

    QString d="HTTP/1.1 "+QString::number(code)+" OK\r\n";//返回第一行
    client->write(d.toLatin1());
    QString s="Content-Type: "+contentType+"\r\n";//返回类型行,类型不对的话,html不会正常显示
    client->write(s.toLatin1());
    client->write("Content-Language: zh-cn\r\n");
    if(body==NULL || body.length()==0){//没有body
        client->write("Content-Length: 0\r\n");//内容长度为0
        client->write("\r\n");//后面有个空行
    }
    else {   
        QString d1=QString("Content-Length: %0\r\n").arg(body.length());//内容长度行
        client->write(d1.toLatin1());
        client->write("\r\n");//内容和body之间有个空行
        client->write(body);
    }
    //response end   
    client->close();//close client
}

配置文件类Config使用QSetting,对QSetting进行了简单封装,代码如下:

#include "config.h"

Config::Config()
{
    set=new QSettings("config.ini", QSettings::IniFormat);
}

///
/// \brief Config::login 登录判断
/// \param name 用户名
/// \param pwd 密码
/// \return
///
bool Config::login(QString name, QString pwd){
    if(name==set->value("server/user").toString() && pwd==set->value("server/password").toString()){
        return true;
    }
    else return false;
}
///
/// \brief Config::getInstance 单例模式
/// \return
///
Config* Config::getInstance(){
    static Config* ins=NULL;
    if(ins==NULL)ins=new Config();
    return ins;
}
///
/// \brief Config::value 获取一个值
/// \param key
/// \return
///
QVariant Config::value(QString key)
{
    return set->value(key);
}
///
/// \brief Config::setValue 保存一个值
/// \param key 键
/// \param value 值,整数
///
void Config::setValue(QString key, int value){
    set->setValue(key,value);
    save();

}
///
/// \brief Config::setValue 保存一个值
/// \param key 键
/// \param value 值,字符串
///
void Config::setValue(QString key,QString value){
    set->setValue(key,value);
    save();
}
///
/// \brief Config::maxClients 返回最大运行连接数
/// \return
///
int Config::maxClients(){
    return set->value("server/maxClients").toInt();
}
///
/// \brief Config::save 保存配置
///
void Config::save(){
    set->sync();
}
///
/// \brief Config::sync 保存配置
///
void Config::sync(){
    set->sync();
}
///
/// \brief Config::groups 返回所有组
/// \return
///
QStringList Config::groups(){
    return set->childGroups();
}

配置文件格式:

[server]
port=8182
root=e:/temProject/svrtest/
default=index.html

写了一个简单的网页进行测试。服务端运行后截图如下:

 

html网页可以正常显示:

### 回答1: 在Qt中,可以使用QUdpSocket类来实现UDP客户端接收服务器端数据。首先创建一个QUdpSocket对象: QUdpSocket *udpSocket = new QUdpSocket(this); 然后使用bind()函数将该udpSocket绑定到一个本地端口: udpSocket->bind(localPort); 其中localPort是一个本地端口号,用于接收服务器端发送的数据。 接下来,可以使用readyRead()信号和QByteArray类来接收数据。readyRead()信号在有数据到达时触发,我们可以使用信号槽机制连接它: connect(udpSocket, SIGNAL(readyRead()), this, SLOT(processPendingDatagrams())); 接收数据的具体实现可以在processPendingDatagrams()槽函数中完成: void MyClass::processPendingDatagrams() { while (udpSocket->hasPendingDatagrams()) { QByteArray datagram; datagram.resize(udpSocket->pendingDatagramSize()); udpSocket->readDatagram(datagram.data(), datagram.size()); // 处理接收到的数据 } } 在该槽函数中,我们利用hasPendingDatagrams()函数判断是否有待处理的数据包,如果有,就使用readDatagram()函数读取数据包的内容。 最后,可以在处理接收到的数据的部分添加逻辑来完成对接收到的数据的处理。 以上就是在Qt中实现UDP客户端接收服务器端数据的方法。 ### 回答2: QT是一种开发工具,UDP是一种网络通信协议,客户端指的是对UDP服务器进行请求并接收数据的一方,服务器端是负责接收请求并提供数据的一方。 在QT中,开发客户端来接收UDP服务器端的数据可以按照以下步骤进行: 1. 创建一个QT项目,并在项目中添加UDP相关的头文件和库文件。 2. 使用QT提供的QUdpSocket类来创建一个UDP套接字对象。套接字对象是用来进行网络通信的对象。 3. 配置套接字对象的相关参数,如绑定端口号。 4. 使用套接字对象的bind()函数将套接字绑定到特定的主机和端口号上。 5. 使用套接字对象的readyRead()信号和对应的槽函数来接收服务器端的数据。 6. 在槽函数中调用套接字对象的readDatagram()函数来读取接收到的数据,并进行处理。 7. 根据具体需求,可以在接收到数据后对数据进行解析、展示或者其他操作。 8. 可以使用套接字对象的writeDatagram()函数向服务器端发送数据。 9. 在必要的情况下,可以在客户端与服务器端的通信过程中使用一些错误处理机制,如超时重发等。 通过以上步骤,就可以在QT中实现UDP客户端接收服务器端的数据。接收到的数据可以根据需求进行处理和展示,以满足具体的业务需求。 ### 回答3: Qt是一个跨平台的C++应用程序开发框架,支持多种网络通信协议,其中包括UDP协议。 在Qt中,可以通过QUdpSocket类来实现UDP客户端。首先,需要创建一个QUdpSocket对象,并通过bind函数绑定本地地址和端口号。然后,可以使用receiveDatagram函数来接收服务器端发送过来的数据。 接收数据的代码示例: ```cpp QUdpSocket *udpSocket = new QUdpSocket(this); udpSocket->bind(QHostAddress::AnyIPv4, 1234); // 绑定本地地址和端口号 QByteArray datagram; // 用于存储接收到的数据 datagram.resize(udpSocket->pendingDatagramSize()); QHostAddress senderAddress; // 存储数据发送者的地址 quint16 senderPort = 0; // 存储数据发送者的端口号 udpSocket->readDatagram(datagram.data(), datagram.size(), &senderAddress, &senderPort); // 使用datagram中的数据进行后续处理 delete udpSocket; ``` 在以上代码中,通过udpSocket->bind函数指定了本地地址AnyIPv4和端口号1234,表示接收来自任意地址的UDP数据包。通过udpSocket->readDatagram函数接收数据,并将数据存储在datagram中,同时获取发送者的地址和端口号。 需要注意的是,接收数据的操作是阻塞的,即在调用udpSocket->readDatagram函数时,如果没有接收到数据,程序将一直等待,直到接收到数据或发生错误。 总之,通过以上代码,我们可以在Qt中实现UDP客户端接收服务器端发送的数据。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值