Qt - 实现HTTP服务器和HTTP客户端

1. WebSocket服务器和HTTP服务器的区别

WebSocket服务器和HTTP服务器是两种不同的服务器类型,它们在协议、连接方式和通信模式等方面有所区别。

  • 协议:HTTP服务器使用HTTP协议进行通信,而WebSocket服务器使用WebSocket协议。HTTP协议是无状态的,客户端发起请求,服务器响应请求后立即关闭连接。WebSocket协议允许在客户端和服务器之间建立持久连接,双向通信。
  • 连接方式:HTTP服务器采用"请求-响应"模式,即客户端向服务器发送请求,服务器响应后断开连接。每个请求都需要重新建立连接。WebSocket服务器在初始握手后,建立一个持久连接,允许双向通信,客户端和服务器可以随时发送消息。
  • 通信模式:HTTP服务器基于请求-响应模式,客户端发起请求,服务器做出响应。每个请求和响应都是独立的,没有持久性。WebSocket服务器支持双向通信,客户端和服务器可以通过发送消息进行实时交互,服务器可以主动推送消息给客户端。

总体而言,HTTP服务器适用于传统的客户端-服务器通信,每次请求都需要重新建立连接,适合请求响应式的场景。WebSocket服务器适用于需要实时双向通信的场景,适合聊天应用、实时数据更新等。

需要注意的是,WebSocket协议在建立连接时会使用HTTP协议进行初始握手,因此可以在HTTP服务器上实现WebSocket服务器。但是,WebSocket服务器提供更多的功能和优化,以支持实时通信需求。

 

2. 实现简单的HTTP服务器

具体代码:

httpserver.h

 
#ifndef HTTPSERVER_H
#define HTTPSERVER_H

#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>

class HttpServer : public QObject
{
    Q_OBJECT
public:
    explicit HttpServer(QObject *parent = nullptr);
    ~HttpServer();

    QTcpSocket *socket;

public slots:
    void onReadyRead();
    void connection();

private:
    qint64 bytesAvailable() const;
    QTcpServer *server;

signals:

public slots:
};

#endif // HTTPSERVER_H

httpserver.cpp

 
#include "httpserver.h"
#include <QDebug>
#include <QFile>

HttpServer::HttpServer(QObject *parent) : QObject(parent)
{
    server = new QTcpServer(this);
    socket = new QTcpSocket(this);

    if(!server->listen(QHostAddress::Any, 8080))
    {
        qDebug() << "Web服务未启动";
    }
    else
    {
         qDebug() << "Web服务在端口8080等待客户端连接";
    }
    //有客户端连接时触发newConnection信号
    connect(server, &QTcpServer::newConnection, this, &HttpServer::connection);

}

void HttpServer::connection()
{
    //取出建立好连接的套接字
    socket = server->nextPendingConnection();
    //获取对方的IP和端口
    QString ip = socket->peerAddress().toString();
    qint16 port = socket->peerPort();
    QString temp = QString("[%1 : %2]: 成功连接").arg(ip).arg(port);
    qDebug() << temp;

    connect(socket,&QTcpSocket::readyRead,this,&HttpServer::onReadyRead);
}

void HttpServer::onReadyRead()
{
    //QTcpSocket* socket = qobject_cast<QTcpSocket*>(sender());

    if(socket)
    {
        //从通信套接字中取出内容
        QByteArray array = socket->readAll();
        QString request = QString::fromUtf8(array);
        qDebug() << "正在从浏览器读取数据= " << request;
        
        //QString::startsWith()函数判断一个字符串是否以某个字符串开头
        if(request.startsWith("GET"))//如果是GET请求
        {
            QFile file("/Users/Administrator/Documents/http_demo/index.txt");
            if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
            {
                qDebug() << "文件打开失败";
                return;
            }
            QString tempStr = "";//临时字符串
            while (!file.atEnd())//判断是否读到文件末尾,如果已经达到末尾,返回 true,否则返回 false。
            {
                //tempStr = file.readLine();  //读取文件中一行
                tempStr = file.readAll();   //读取文件中所有内容
                //qDebug() <<"tempStr = "<<tempStr;
            }

            char *ch = tempStr.toLatin1().data();

            socket->write("HTTP/1.1 200 OK\r\n");
            socket->write("Content-Type: text/html\r\n");
            socket->write("\r\n");
            socket->write(ch);
            socket->flush();
        }
    }
}


HttpServer::~HttpServer()
{
    socket->close();
}

main.cpp

 
#include <QApplication>
#include "httpserver.h"

int main(int argc, char *argv[])
{
    QApplication a(argc, argv);
    HttpServer server;

    return a.exec();
}

index.txt

 
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>简易的在线计算器</title>
</head>
<body>
    <form action="/test_cgi" method="get" align="center">
        操作数1:<br>
        <input type="text" name="x"><br>
        操作数2:<br>
        <input type="text" name="y"><br><br>
        <input type="submit" value="计算">
    </form>
</body>
</html>

 

运行结果:

注意:

如果访问服务器时没有指定要访问的资源路径,那么浏览器会自动帮我们添加/,但此时仍然没有指明要访问web根目录下的哪一个资源文件,这时默认访问的是目标服务的首页。
大部分URL中的端口号都是省略的,因为常见协议对应的端口号都是固定的,比如HTTP、HTTPS和SSH对应的端口号分别是80、443和22,在使用这些常见协议时不必指明协议对应的端口号,浏览器会自动帮我们进行填充。

 

优化版:

httpserver.h

#ifndef HTTPSERVER_H
#define HTTPSERVER_H

#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>

class HttpServer : public QObject
{
    Q_OBJECT
public:
    explicit HttpServer(QObject *parent = nullptr);
    ~HttpServer();

public slots:
    void onReadyRead();
    void connection();
    void onDisconnected();

private:
    qint64 bytesAvailable() const;
    QTcpServer *server;
    QList<QTcpSocket*> tcpSktList;//用来存储所有客户端socket的链表

signals:

public slots:
};

#endif // HTTPSERVER_H

httpserver.cpp

#include "httpserver.h"
#include <QDebug>
#include <QFile>
#include <QHostAddress>

HttpServer::HttpServer(QObject *parent) : QObject(parent)
{
    server = new QTcpServer(this);

    if(!server->listen(QHostAddress::Any, 8080))
    {
        qDebug() << "Web服务未启动";
    }
    else
    {
         qDebug() << "Web服务在端口8080等待客户端连接";
    }
    //有客户端连接时触发newConnection信号
    connect(server, &QTcpServer::newConnection, this, &HttpServer::connection);

}

void HttpServer::connection()
{
    //取出建立好连接的套接字
    QTcpSocket *socket = server->nextPendingConnection();

    //获取对方的IP和端口
    QHostAddress clientIp = socket->peerAddress();//客户端的IP
    qint16 port = socket->peerPort();//客户端的端口


    if(tcpSktList.contains(socket))
    {
        qDebug()<<QString("%1:%2的连接早已建立过").arg(clientIp.toString()).arg(port);
    }
    else
    {
        qDebug()<<QString("新的连接已建立 %1:%2").arg(clientIp.toString()).arg(port);
        tcpSktList.append(socket);//记录下客户端发起的连接
        connect(socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()));//客户端掉线处理
        connect(socket, SIGNAL(readyRead()), this, SLOT(onReadyRead()));//客户端发来的数据处理
    }
}

void HttpServer::onReadyRead()
{
    QTcpSocket* socket = static_cast<QTcpSocket*>(sender());

    if(socket)
    {
        //从通信套接字中取出内容
        QByteArray array = socket->readAll();
        QString request = QString::fromUtf8(array);
        qDebug() << "正在从浏览器读取数据= " << request;//打印收到的来自客户端的消息

        if(request.startsWith("GET"))//如果是GET请求
        {
            QFile file("/Users/Administrator/Documents/http_demo/index.txt");
            if (!file.open(QIODevice::ReadOnly | QIODevice::Text))
            {
                qDebug() << "文件打开失败";
                return;
            }
            QString tempStr = "";//临时字符串
            while (!file.atEnd())//判断是否读到文件末尾,如果已经达到末尾,返回 true,否则返回 false。
            {
                //tempStr = file.readLine();  //读取文件中一行
                tempStr = file.readAll();   //读取文件中所有内容
                //qDebug() <<"tempStr = "<<tempStr;
            }

            char *ch = tempStr.toLatin1().data();

            socket->write("HTTP/1.1 200 OK\r\n");
            socket->write("Content-Type: text/html\r\n");
            socket->write("\r\n");
            socket->write(ch);
            socket->flush();
        }
    }
}

void HttpServer::onDisconnected()
{
    QTcpSocket *socket = static_cast<QTcpSocket *>(sender());
    //获取对方的IP和端口
    QHostAddress clientIp = socket->peerAddress();//客户端的IP
    qint16 port = socket->peerPort();//客户端的端口
    qDebug()<<QString("客户端掉线 %1:%2").arg(clientIp.toString()).arg(port);

    tcpSktList.removeOne(socket);
}

HttpServer::~HttpServer()
{
    //socket->close();
}
posted @   [BORUTO]  阅读(189)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
点击右上角即可分享
微信分享提示