本人在学习QT时,想用QT制作一个可以处理HTTP请求的服务器,那么这个小项目该怎么实现呢,
首先,我们需要在项目的pro文件中添加 network,因为其中包含了许多网络编程的头文件。如图所示:
导入之后·,我们就要思考,要想处理HTTP请求,首先要让浏览器与服务器建立TCP连接,只有建立连接后,二者才能收发数据。因此首先,我们先建立TCP连接,当我们在linux中建立TCP连接时,我们通常的步骤是
1.创建用于监听的文件描述符
2.将该文件描述符与本机ip和预设端口(例如10000)进行绑定
3.监听该文件描述符,当有新连接时,创建用于通信的文件描述符。
这时我们的TCP连接就算完成了,在此之后就可以进行通信了。
但是对于QT来说,我们并不需要做这么多操作,我们只需要调用QTcpServer类中的listen函数就可以实现对指定IP的端口的监听,其参数为 (IP,端口),以本地为服务器为例(当然这种方法只有在同一局域网中别人才可以访问问到,要想实现所有人都可以访问到的服务器,可以将程序在云电脑上运行,这样就可以通过公网IP所有人都可以访问到)
QTcpServer::listen(QHostAddress::Any, 10000);//本机ip,端口号为10000
同时,当有新的客户端发送连接请求时,QTcpserver会自动的调用incomingConnection函数,传递的参数(socketDescriptor)是接受的连接的本机套接字描述符。基本实现创建一个 QTcpSocket,
要想得到用于通信的套接字,可以用以下方式得到:
QTcpSocket* socket=new QTcpSocket;
socket->setSocketDescriptor(socketDescriptor);
那么得到用于通信的文件描述符后,TCP连接就建立成功了,接下来我们要实现HTTP请求的处理和HTTP响应的发送。
要想处理HTTP请求(这里我们只讨论get请求),我们首先要知道HTTP请求到底是在请求什么,也就是对方发过来的一堆数据到底想表达什么,我们就要读懂HTTP请求数据的格式,下图是我在服务器得到的请求数据,每一行以\\r\\n结尾:
第一行表示请求行,其数据分别表示 请求数据的方法 请求的路径 http协议版本
第二行至空行之前表示请求头 ,每一行都有请求的属性
对于get请求,http请求报文以空行结尾(即\\r\\n)。
知道了HTTP的请求格式,我们就能从请求中获取到我们想要的数据,首先,我们要知道请求使用的是get还是post方法来请求数据,其次我们需要知道对方是想要获取的数据是什么,至于其他的数据,我们没有必要保存。
其代码实现如下:
QByteArray &buffer = buffers[socket];
buffer.append(socket->readAll()); // 读取数据到缓存
// 检查是否接收到完整的HTTP头部(以\\r\\n\\r\\n结尾)
int endOfHeader = buffer.indexOf("\\r\\n\\r\\n");
if (endOfHeader == -1) return; // 数据不完整,等待后续数据
QString requestHeader = buffer.left(endOfHeader); // 提取头部
buffer.clear(); // 清空缓存
QTextStream stream(&requestHeader, QIODevice::ReadOnly);
QString requestLine = stream.readLine(); // 解析请求行
QStringList parts = requestLine.split(' ');
if (parts.size() < 3) {
sendResponse(socket, 400, "Bad Request", "text/html", "<h1>400 Bad Request</h1>");
return;
}
QString method = parts[0].toUpper();
QString path = parts[1];
知道了对方想要的是什么,我们就要判断我们是否有对应的文件或目录,如果没有我们就返回一个404 NotFound网页,如果有,我们就读取对应的文件然后发送给客户端。那么我们发送也有格式要求,要不然浏览器也不知道我们发过来的是什么东西,
对应的格式就是:
状态行:HTTP/1.1 200 OK (其中的200表示状态码)
响应头: Content-Type: (用来表述响应数据的一些属性,其中内容类型是必须写入其中的)
空行:\\r\\n
响应体:(真正响应想要传递给浏览器的数据)
其代码实现如下:
QByteArray response;
response.append(QString("HTTP/1.1 %1 %2\\r\\n").arg(statusCode).arg(statusText).toUtf8());
response.append(QString("Content-Type: %1\\r\\n").arg(contentType).toUtf8());
response.append(QString("Content-Length: %1\\r\\n").arg(content.toUtf8().size()).toUtf8());
response.append("Connection: close\\r\\n"); // 发送后关闭连接
response.append("\\r\\n"); // 头部结束
response.append(content.toUtf8()); // 添加内容
socket->write(response); // 发送响应
至此,HTTP数据的收发均已实现。
最后附上完整的代码:
头文件:
class HttpServer : public QTcpServer
{
Q_OBJECT
public:
explicit HttpServer(QObject *parent = nullptr);
void startServer();
virtual void incomingConnection(qintptr socketDescriptor);
protected slots:
// void handleNewConnection();
void readRequest();
void sendResponse(QTcpSocket *socket, int statusCode, const QString &statusText, const QString &contentType, const QString &content);
private:
QMap<QTcpSocket*, QByteArray> buffers; // 用于缓存每个socket的数据
};
cpp文件:
HttpServer::HttpServer(QObject *parent) : QTcpServer(parent)
{
}
void HttpServer::startServer()
{
if (!this->listen(QHostAddress::Any, 10000)) {
qDebug() << "Could not start server";
} else {
qDebug() << "Listening on port 10000…";
}
}
void HttpServer::incomingConnection(qintptr socketDescriptor)
{
QTcpSocket* socket=new QTcpSocket;
socket->setSocketDescriptor(socketDescriptor);
connect(socket, &QTcpSocket::readyRead, this, &HttpServer::readRequest);
connect(socket, &QTcpSocket::disconnected, socket, &QTcpSocket::deleteLater);
connect(socket, &QTcpSocket::disconnected, this, [this, socket]() {
buffers.remove(socket); // 清理缓存
});
buffers.insert(socket, QByteArray()); // 初始化缓存
qDebug() << "New connection from" << socket->peerAddress().toString();
}
void HttpServer::readRequest()
{
qDebug()<<"准备解析请求";
QTcpSocket *socket = qobject_cast<QTcpSocket*>(sender());
if (!socket) return;
QByteArray &buffer = buffers[socket];
buffer.append(socket->readAll()); // 读取数据到缓存
// 检查是否接收到完整的HTTP头部(以\\r\\n\\r\\n结尾)
int endOfHeader = buffer.indexOf("\\r\\n\\r\\n");
if (endOfHeader == -1) return; // 数据不完整,等待后续数据
QString requestHeader = buffer.left(endOfHeader); // 提取头部
buffer.clear(); // 清空缓存(本例中处理完即关闭连接)
QTextStream stream(&requestHeader, QIODevice::ReadOnly);
QString requestLine = stream.readLine(); // 解析请求行
QStringList parts = requestLine.split(' ');
if (parts.size() < 3) {
sendResponse(socket, 400, "Bad Request", "text/html", "<h1>400 Bad Request</h1>");
return;
}
QString method = parts[0].toUpper();
QString path = parts[1];
// 仅处理GET请求
if (method != "GET") {
sendResponse(socket, 501, "Not Implemented", "text/html", "<h1>501 Not Implemented</h1>");
return;
}
// 根据路径生成响应内容
QString content;
QString contentType = "text/html";
int statusCode = 200;
QString statusText = "OK";
if (path == "/") {
content = "tem: 23";
} else if (path == "/test") {
content = "<h1>Test Page</h1><p>This is a test response.</p>";
} else if (path.startsWith("/api/data")) {
contentType = "application/json";
content = "{\\"message\\": \\"Hello, World!\\"}";
} else {
statusCode = 404;
statusText = "Not Found";
content = "<h1>404 Not Found</h1>";
}
sendResponse(socket, statusCode, statusText, contentType, content);
}
void HttpServer::sendResponse(QTcpSocket *socket, int statusCode, const QString &statusText, const QString &contentType, const QString &content)
{
QByteArray response;
response.append(QString("HTTP/1.1 %1 %2\\r\\n").arg(statusCode).arg(statusText).toUtf8());
response.append(QString("Content-Type: %1\\r\\n").arg(contentType).toUtf8());
response.append(QString("Content-Length: %1\\r\\n").arg(content.toUtf8().size()).toUtf8());
response.append("Connection: close\\r\\n"); // 发送后关闭连接
response.append("\\r\\n"); // 头部结束
response.append(content.toUtf8()); // 添加内容
socket->write(response); // 发送响应
socket->disconnectFromHost(); // 断开连接
}
评论前必须登录!
注册