设计思路
qt自身不带httpserver相关的类,因此,需要我们自己来封装。
http相较于tcp,多了一些包头信息,已经get/post的请求方式,需要给出及时的应答,需要附带包头信息。
我们可以用tcpserver来封装httpserver,做一些简单的通讯打印。
实际效果图
不足之处:
1、客户端数量没有做调试和优化,并不准确。
2、对于中文消息体未做任何处理。
源代码
CMakeLists.txt
cmake_minimum_required(VERSION 3.5)
project(HttpServer VERSION 0.1 LANGUAGES CXX)
set(CMAKE_INCLUDE_CURRENT_DIR ON)
#QT相关部分
#set(CMAKE_PREFIX_PATH "D:/install_APP/qt5.15/5.15.2/msvc2019/lib/cmake")
set(CMAKE_AUTOUIC ON)
set(CMAKE_AUTOMOC ON)
set(CMAKE_AUTORCC ON)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
find_package(QT NAMES Qt6 Qt5 REQUIRED COMPONENTS Widgets Network)
find_package(Qt${QT_VERSION_MAJOR} REQUIRED COMPONENTS Widgets Network)
set(PROJECT_SOURCES
main.cpp
mymainwindow.cpp
httpserver.cpp
httpserver.h
common.h
mymainwindow.h
mymainwindow.ui
)
if(${QT_VERSION_MAJOR} GREATER_EQUAL 6)
qt_add_executable(HttpServer
MANUAL_FINALIZATION
${PROJECT_SOURCES}
)
# Define target properties for Android with Qt 6 as:
# set_property(TARGET HttpServer APPEND PROPERTY QT_ANDROID_PACKAGE_SOURCE_DIR
# ${CMAKE_CURRENT_SOURCE_DIR}/android)
# For more information, see https://doc.qt.io/qt-6/qt-add-executable.html#target-creation
else()
if(ANDROID)
add_library(HttpServer SHARED
${PROJECT_SOURCES}
)
# Define properties for Android with Qt 5 after find_package() calls as:
# set(ANDROID_PACKAGE_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/android")
else()
add_executable(HttpServer
${PROJECT_SOURCES}
)
endif()
endif()
target_link_libraries(HttpServer PRIVATE
Qt${QT_VERSION_MAJOR}::Widgets
Qt${QT_VERSION_MAJOR}::Core
Qt${QT_VERSION_MAJOR}::Gui
Qt${QT_VERSION_MAJOR}::Network
# Qt${QT_VERSION_MAJOR}::Xml
)
set_target_properties(HttpServer PROPERTIES
MACOSX_BUNDLE_GUI_IDENTIFIER my.example.com
MACOSX_BUNDLE_BUNDLE_VERSION ${PROJECT_VERSION}
MACOSX_BUNDLE_SHORT_VERSION_STRING ${PROJECT_VERSION_MAJOR}.${PROJECT_VERSION_MINOR}
MACOSX_BUNDLE TRUE
WIN32_EXECUTABLE TRUE
)
if(QT_VERSION_MAJOR EQUAL 6)
qt_finalize_executable(HttpServer)
endif()
common.h
#ifndef COMMON_H
#define COMMON_H
#define MSG_CLIENT_IN 1 // 有客户端连接
#define MSG_CLIENT_OUT 2 // 有客户端断开
#define MSG_CLIENT_MSG 3 // 客户端发送来的消息
#endif // COMMON_H
httpserver.h
#ifndef HTTPSERVER_H
#define HTTPSERVER_H
#include <QObject>
#include <QTcpServer>
#include <QTcpSocket>
#include <QList>
#include "common.h"
class httpserver : public QObject
{
Q_OBJECT
public:
//explicit httpserver(QObject *parent = nullptr);
httpserver(quint16 port);
~httpserver();
bool GetState(QString &Info);
bool RespondClient(QTcpSocket* Sock, const QByteArray &data);
QString GetMsgText(const QByteArray &data, QByteArray &Content);
signals:
void SendMsg(int flag, QByteArray &data, QString Url = "");
public slots:
void DealDisconnection();
void NewConnection();
void ReadData();
void RecvNewPort(quint16 Port);
void OnConnectionDisc(QAbstractSocket::SocketError error);
private:
bool m_blistened;
quint16 m_Port;
QByteArray m_MsgData;
QString m_Info;
QTcpServer* m_pServer;
QList<QTcpSocket*> m_SockClient;
};
#endif // HTTPSERVER_H
mymainwindow.h
#ifndef MYMAINWINDOW_H
#define MYMAINWINDOW_H
#include <QMainWindow>
#include <QDateTime>
#include <QFile>
#include "httpserver.h"
#include "common.h"
#include <QUrl>
QT_BEGIN_NAMESPACE
namespace Ui { class MyMainWindow; }
QT_END_NAMESPACE
class MyMainWindow : public QMainWindow
{
Q_OBJECT
public:
MyMainWindow(QWidget *parent = nullptr);
~MyMainWindow();
signals:
void SendNewPort(quint16 Port); // 发送新的端口号
public slots:
void RecvMsg(int flag, QByteArray &data, QString url);
private slots:
void on_pb_Listen_clicked();
private:
Ui::MyMainWindow *ui;
int m_ClientNum;
quint16 m_Port;
httpserver* m_pHttpServer;
};
#endif // MYMAINWINDOW_H
httpserver.cpp
#include "httpserver.h"
httpserver::httpserver(quint16 port)
{
m_Info = "";
m_SockClient.clear();
m_MsgData.clear();
m_pServer = new QTcpServer(this);
if(false == m_pServer->listen(QHostAddress::Any, port))
{
m_Info = m_pServer->errorString();
m_pServer->close();
m_blistened = false;
}
else
{
m_blistened = true;
connect(m_pServer, &QTcpServer::newConnection, this, &httpserver::NewConnection);
connect(m_pServer, SIGNAL(acceptError(QAbstractSocket::SocketError)), this, SLOT(OnConnectionDisc(QAbstractSocket::SocketError)));
}
}
httpserver::~httpserver()
{
for(int i = 0; i < m_SockClient.size(); i ++)
{
m_SockClient[i]->deleteLater();
}
if(nullptr != m_pServer)
{
m_pServer->close();
delete m_pServer;
m_pServer = nullptr;
}
}
void httpserver::OnConnectionDisc(QAbstractSocket::SocketError error)
{
qDebug() << "Http server error:" << error;
}
/*===============================================
* 函数:GetState
* 功能:获取http服务器的状态
* 参数:Info 消息提示
* 返回:true 监听成功
* false 监听失败
===============================================*/
bool httpserver::GetState(QString &Info)
{
Info = m_Info;
return m_blistened;
}
/*===============================================
* 函数:RecvNewPort
* 功能:获取新的端口号
* 参数:Port 端口号
* 返回:
===============================================*/
void httpserver::RecvNewPort(quint16 Port)
{
m_Port = Port;
if(m_pServer->isListening())
{
m_pServer->close();
m_pServer->listen(QHostAddress::Any, m_Port);
}
}
/*===============================================
* 函数:NewConnection
* 功能:处理新发来的http客户端申请,并回应
* 参数:
* 返回:
===============================================*/
void httpserver::NewConnection()
{
if(m_pServer->hasPendingConnections())
{
QTcpSocket* Sock = m_pServer->nextPendingConnection();
RespondClient(Sock, "");
m_SockClient<<Sock;
emit SendMsg(MSG_CLIENT_IN, m_MsgData);
connect(Sock, &QTcpSocket::disconnected, this, &httpserver::DealDisconnection);
connect(Sock, &QTcpSocket::readyRead, this, &httpserver::ReadData);
}
}
/*===============================================
* 函数:DealDisconnection
* 功能:处理客户端断开问题
* 参数:
* 返回:
===============================================*/
void httpserver::DealDisconnection()
{
for(int i = 0; i < m_SockClient.size(); )
{
if(QAbstractSocket::ConnectedState != m_SockClient[i]->state())
{
m_SockClient.removeAt(i);
emit SendMsg(MSG_CLIENT_OUT, m_MsgData);
continue;
}
i ++;
}
}
/*===============================================
* 函数:RespondClient
* 功能:回应(发送)客户端消息
* 参数:Sock 客户端
* data 数据
* 返回:true 发送成功
* false 发送失败
===============================================*/
bool httpserver::RespondClient(QTcpSocket* Sock, const QByteArray &data)
{
if(nullptr == Sock)
{
return false;
}
char length[32];
memset(length, 0 ,sizeof(length));
sprintf(length, "Content-Length:%d\r\n", data.size());
Sock->write("HTTP/1.1 200 OK\r\n");
Sock->write("Access-Control-Allow-Origin: *\r\n");
Sock->write(length);
Sock->write("Content-Type: application/octet-stream\r\n");
Sock->write("Keep-Alive: timeout=5, max=5\r\n\r\n");
if(0 != data.size())
{
Sock->write(data.data(), data.size());
}
return true;
}
/*===============================================
* 函数:ReadData
* 功能:读取数据
* 参数:
* 返回:
===============================================*/
void httpserver::ReadData()
{
QByteArray data;
QByteArray Msg;
data.clear();
Msg.clear();
QTcpSocket* Sock = nullptr;
for(int i = 0; i < m_SockClient.size(); i ++)
{
data.clear();
Sock = m_SockClient.at(i);
data = Sock->readAll();
if(data.length() != 0)
{
qDebug()<<data;
QString url = Sock->peerAddress().toString() + ":" + QString::number(Sock->peerPort()) + "/" + GetMsgText(data, Msg);
emit SendMsg(MSG_CLIENT_MSG, Msg, url);
data.clear();
RespondClient(Sock, data);
}
}
}
/*===============================================
* 函数:GetMsgText
* 功能:获取客户端发送的内容
* 参数:data 原始数据
* Content 客户端发送的内容
* 返回:客户端的url
===============================================*/
QString httpserver::GetMsgText(const QByteArray &data, QByteArray &Content)
{
if(data.isEmpty())
{
return "";
}
QString strData = data;
// 获取url
int Start = strData.indexOf("/");
int End = strData.indexOf("HTTP/");
QString Url = strData.mid(Start + 1, End - Start - 2);
// 获取长度
int locat_length = strData.indexOf("Content-Length:");
int Locat_Content = strData.indexOf("\r\n\r\n");
locat_length += qstrlen("Content-Length:");
QString len = strData.mid(locat_length, Locat_Content - locat_length);
// 获取内容
Locat_Content += qstrlen("\r\n\r\n");
Content = strData.mid(Locat_Content, len.toInt()).toLatin1();
return Url;
}
mymainwindow.cpp
#include "mymainwindow.h"
#include "./ui_mymainwindow.h"
MyMainWindow::MyMainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MyMainWindow)
{
ui->setupUi(this);
setWindowTitle("httpServer");
m_pHttpServer = nullptr;
m_ClientNum = 0;
ui->Client_num->setText(QString::number(m_ClientNum));
// httpServer
m_Port = 8000;
ui->Port->setText(QString::number(m_Port));
m_pHttpServer = new httpserver(m_Port);
QString strInfo = "";
if(false == m_pHttpServer->GetState(strInfo))
{
ui->RecvMsg->setText(strInfo);
}
else
{
strInfo = "HttpServer start !!!";
ui->RecvMsg->setText(strInfo);
}
connect(m_pHttpServer, &httpserver::SendMsg, this, &MyMainWindow::RecvMsg);
connect(this, SIGNAL(SendNewPort(quint16)), m_pHttpServer, SLOT(RecvNewPort(quint16)));
}
MyMainWindow::~MyMainWindow()
{
delete ui;
}
/*==========================================
* 函数:on_pb_Listen_clicked
* 功能:启动http服务器,开始监听
* 参数:
* 返回:
==========================================*/
void MyMainWindow::on_pb_Listen_clicked()
{
quint16 Port = ui->Port->text().toUShort();
qDebug()<<"port:"<<Port;
if(Port <= 1024 || Port > 65534 || Port == m_Port)
{
return;
}
m_Port = Port;
emit SendNewPort(Port);
}
/*==========================================
* 函数:RecvMsg
* 功能:接收消息提示
* 参数:flag 标志位
* data 消息
* 返回:
==========================================*/
void MyMainWindow::RecvMsg(int flag, QByteArray &data, QString url)
{
switch(flag)
{
// 新加入客户端
case MSG_CLIENT_IN:
{
m_ClientNum ++;
ui->Client_num->setText(QString::number(m_ClientNum));
}
break;
// 有客户端退出
case MSG_CLIENT_OUT:
{
m_ClientNum --;
ui->Client_num->setText(QString::number(m_ClientNum));
}
break;
// 打印客户端发来的信息
case MSG_CLIENT_MSG:
{
QString strInfo = url + ":\n" + data + "\n";
ui->RecvMsg->append(strInfo);
}
break;
default:
{
}
}
}
main.cpp
#include "mymainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MyMainWindow w;
w.show();
return a.exec();
}
mymainwindow.ui
测试工具
可以使用postman进行调试