由于工作需求,需要在QT上实现一个HttpServer,查找了一些开源的库最终选择了QtWebApp来完成这个需求,这个相对于其他的C++库来说比较契合QT,实现信号槽方便。下面这个五子棋就i是利用该库实现的一个demo
QtWebApp官网地址:QtWebApp HTTP server in C++ (stefanfrings.de)
五子棋项目地址:KingJamesGyq/FiveGo
一、服务器设置
封装了一个类用来完成服务器监听和post请求
#ifndef HTTPSERVER_H
#define HTTPSERVER_H
#include<QObject>
#include<QNetworkAccessManager>
#include<QNetworkRequest>
#include<QNetworkReply>
#include<QJsonDocument>
#include<QJsonObject>
#include<QJsonValue>
#include<QJsonArray>
#include<QTimer>
#include<QEventLoop>
#include<QThread>
#include<QDebug>
#include<QSettings>
#include<queue>
#include<map>
#include<QMutex>
#include <QHostAddress>
#include "QtWebApp/requesthandler.h"
#include "QtWebApp/httpserver/httplistener.h"
#include "QtWebApp/logging/filelogger.h"
using namespace stefanfrings;
class HttpServer : public QObject
{
Q_OBJECT
public:
HttpServer();
~HttpServer();
int start();
void stop();
void setPort(int);
void setIP(QString);
static HttpServer& GetInst();
HttpListener *server;
RequestHandler *request;
signals:
private:
QSettings* settings;
int port;
QString ip;
};
typedef enum _httpType
{
JSON,
x_www_form_urlencoded,
}httpType;
/*
http请求引擎类
注意!!!
在线程中使用时应在线程中进行声明
主线程声明而在支线程中使用会出现下述警告,可能会出现不可预知的错误
QObject: Cannot create children for a parent that is in a different thread.
(Parent is QNetworkAccessManager(0x26da2e841e0), parent's thread is QThread(0x26da2e6b2e0), current thread is QThread(0x26da2eaa7b0)
*/
class HttpEngine : public QObject
{
Q_OBJECT
public:
HttpEngine();
~HttpEngine();
QString err_msg;
/*
根据url建立post请求阻塞线程直到获取数据或超时
@url http地址
@input 输入参数QJsonObject格式
@ouput 服务器返回信息QByteArray格式 根据返回类型自行解析
@timeOut 单位ms 超时参数 超过指定时间未获取到返回信息则报错
*/
bool postUrl(const QString &url, const QJsonObject &input, QByteArray &output, httpType type = httpType::JSON, int timeOut = 3000);
/*
根据url建立get请求阻塞线程直到获取数据或超时
@url http地址 参数应拼接到url地址中
@ouput 服务器返回信息QJsonObject格式
@timeOut 单位ms 超时参数 超过指定时间未获取到返回信息则报错
*/
bool getUrl(const QString &url, QByteArray &output, int timeOut = 3000);
private:
//请求句柄
QNetworkAccessManager *manager;
};
#endif
以下是部分实现
服务器设置:
HttpServer::HttpServer()
{
this->settings = new QSettings("", QSettings::IniFormat);
//settings->setValue("port", "192.168.0.1"); //端口号
//settings->setValue("host", "8080"); //ip
settings->setValue("minThreads", "50"); //始终保持运行的线程数量,用来确保一段时间不活动后的良好响应时间。
settings->setValue("maxThreads", "500");//QtWebApp可以同时处理多个http请求,该参数指定并发工作线程的最大数量。
settings->setValue("cleanupInterval", "6000");//每隔一个cleanupInterval时间间隔(以毫秒为单位),服务器都将关闭一个空闲线程。
settings->setValue("readTimeout", "60000");//设置通过打开大量连接而不使用它们,来保护服务器免受简单的拒绝服务攻击。静默连接将在设定的毫秒数后被关闭。
settings->setValue("maxRequestSize", "9999999");//保护服务器免受非常多的HTTP请求而导致内存过载的影响。此值适用于常规请求。
settings->setValue("maxMultiPartSize", "10000000");//适用于网络浏览器将文件上传到服务器时发生的大部分请求。如果要接受10 MB的文件,由于HTTP协议开销,必须将此值设置得更大一些。
}
对请求进行处理:
void RequestHandler::service(HttpRequest& request, HttpResponse& response)
{
qDebug("Conroller: path=%s", request.getPath().data());
if (request.getMethod() == "GET")
{
// Set a response header
response.setHeader("Content-Type", "text/html; charset=ISO-8859-1");
// Return a simple HTML document
response.write("<html><body>Hello World!</body></html>", true);
qDebug("RequestHandler: finished request");
}
else if (request.getMethod() == "POST")
{
QJsonObject json = QJsonDocument::fromJson(request.getBody()).object();
auto m = json.toVariantMap();
QJsonObject data;
QString path = request.getPath().data();
auto ip = request.getPeerAddress();//客户端IP地址
//地址分发
if (path == "/fiveGo/chess")
{
emit sendPoint(MyPoint(json["x"].toInt(), json["y"].toInt()));
}
else if (path == "/fiveGo/start")
{
emit getStart(ip.toString());
}
else if (path == "/fiveGo/startRes")
{
emit getStartRes(json["res"].toBool());
}
else if (path == "/fiveGo/msg")
{
emit getMsg(json["msg"].toString());
}
else
{
data.insert("code", 1001);
data.insert("msg", u8"接受失败");
}
data.insert("code", 1000);
data.insert("msg", u8"接受成功");
QJsonDocument document;
document.setObject(data);
QByteArray dataArray = document.toJson(QJsonDocument::Compact);
response.setHeader("Content-Type", "application/json; charset=UTF-8");
response.write(dataArray);
}
}
post请求:
bool HttpEngine::postUrl(const QString & url, const QJsonObject & input, QByteArray &output, httpType type, int timeOut)
{
//设置超时处理定时器
QTimer timer;
timer.setInterval(timeOut);
timer.setSingleShot(true);//单次触发
QNetworkReply *reply;
QByteArray dataArray;
QNetworkRequest request;
switch (type)
{
case httpType::JSON: //发送Json格式数据
{
// 根据Json构造数据
QJsonDocument document;
document.setObject(input);
dataArray = document.toJson(QJsonDocument::Compact);
qDebug() << " post msg :" << QString(dataArray).toLocal8Bit().data();
// 构造请求Json格式数据
request.setUrl(QUrl(url));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/json");
break;
}
case httpType::x_www_form_urlencoded: //按x_www_form_urlencoded构造数据
{
QStringList list;
for (auto &value : input.toVariantMap().toStdMap())
{
QString data = QString("%1=%2").arg(value.first).arg(value.second.toString());
list.append(data);
}
dataArray.append(list.join("&"));
// 构造请求数据
request.setUrl(QUrl(url));
request.setHeader(QNetworkRequest::ContentTypeHeader, "application/x-www-form-urlencoded");
break;
}
default:
return false;
break;
}
// 发送请求
reply = manager->post(request, dataArray);
if (reply->error() != QNetworkReply::NoError)
{
err_msg = reply->errorString();
reply->deleteLater();
return false;
}
//启动事件循环等待相应
QEventLoop eventLoop;
connect(&timer, &QTimer::timeout, &eventLoop, &QEventLoop::quit);
connect(manager, &QNetworkAccessManager::finished, &eventLoop, &QEventLoop::quit);
//启动定时器
timer.start();
eventLoop.exec();//启动事件循环
//定时器激活状态,处理响应
if (timer.isActive())
{
timer.stop();//停止定时器
//http请求出错,进行错误处理
if (reply->error() != QNetworkReply::NoError)
{
err_msg = reply->errorString();
err_msg.append(reply->readAll());
reply->deleteLater();
return false;
}
else
{
//读取数据
output = reply->readAll();
return true;
}
}
else//超时处理
{
disconnect(manager, &QNetworkAccessManager::finished, &eventLoop, &QEventLoop::quit);
reply->abort();//关闭所有连接
err_msg = QString("connect time out !!!");
return false;
}
return false;
}
二、棋盘绘制
棋盘基于QGraphicsView绘制,下面是绘制棋盘背景部分代码
void BackgroundItem::init()
{
QPixmap map(480, 480);
map.fill(color);
QPainter painter(&map);
painter.setPen(QPen(Qt::black));
for (int i = 1; i < 16; i++)
{
painter.drawLine(i * S, S,i * S, 15* S);
}
for (int i = 1; i < 16; i++)
{
painter.drawLine( S, i * S, 15 * S, i * S);
}
//绘制棋盘标号1-15
for (int i = 0; i < 15; i++)
{
painter.drawText(QRect(5, (i + 1) * S - 5,10,10), Qt::AlignHCenter, QString::number(i + 1));
}
//绘制棋盘标号A-O
for (int i = 0; i < 15; i++)
{
painter.drawText(QRect((i + 1) * S - 5,5 , 10, 10), Qt::AlignHCenter, QString(char('A' + i)));
}
//绘制5个小圆
painter.setBrush(QBrush(Qt::black));
painter.drawEllipse(S * 4 - 8, S * 4 - 8, 16, 16);
painter.drawEllipse(S * 12 - 8, S * 12 - 8, 16, 16);
painter.drawEllipse(S * 12 - 8, S * 4 - 8, 16, 16);
painter.drawEllipse(S * 4 - 8, S * 12 - 8, 16, 16);
painter.drawEllipse(S * 8 - 8, S * 8 - 8, 16, 16);
back = map.copy();
backUsed = back.copy();
this->setPixmap(backUsed);
}
三、下棋流程
黑棋先下,然后白棋下,自己下完之后post提交该落子点位信息给对方,然后轮到对方下,轮转直到游戏结束
//对方落子
void fiveGo::on_getPoint(MyPoint point)
{
//当前棋子不为自己时接收落子点位
if (nowChess != yourChess)
{
//胜负判定
if (map.find(point) == map.end())
{
map.insert(point, nowChess);
munual.append(std::pair<MyPoint, MyChess>(point, nowChess));
auto it = map.find(point);
auto res = check(point, nowChess);
emit drawPoint(point, nowChess);
if (res)
{
emit isWiner(nowChess, munual);
save();
ui.btn_history->setEnabled(true);
ui.btn_start->setEnabled(true);
ui.btn_connect->setEnabled(true);
}
else
{
//回合轮转
nowChess = yourChess;
}
//刷新界面
refresh();
}
}
}
//自己落子
void fiveGo::on_nowflag(MyPoint point)
{
if (nowChess != yourChess)
return;
//胜负判定
if (map.find(point) == map.end())
{
map.insert(point, nowChess);
munual.append(std::pair<MyPoint, MyChess>(point, nowChess));
auto it = map.find(point);
auto res = check(point, nowChess);
emit drawPoint(point, nowChess);
//http发送落子信息
QJsonObject obj;
obj.insert("x", point.x());
obj.insert("y", point.y());
http.postUrl(url + "chess", obj, QByteArray());
if (res)
{
emit isWiner(nowChess, munual);
save();
ui.btn_history->setEnabled(true);
ui.btn_start->setEnabled(true);
ui.btn_connect->setEnabled(true);
}
nowChess = (nowChess == MyChess::black) ? MyChess::white : MyChess::black;
if (isStandAlone)
yourChess = nowChess;
//刷新界面
refresh();
}
}
四、胜负判定
通过一个Map保存棋盘上的棋子,每次落子判定一次是否存在五子连珠
判定则是从落子位置开始从四个方向判定是否存在五颗同颜色的棋子(为啥是四个方向,因为±方向可以算做一条线)
bool fiveGo::check(MyPoint p, MyChess f)
{
//左上到右下检查
int i = 1;
int num = 1;
auto new_p = MyPoint(p.x() - i * S, p.y() - i * S);
while (map.find(new_p) != map.end() && map.find(new_p).value() == f)
{
i++;
num++;
if (num == 5)
return true;
else
{
new_p = MyPoint(p.x() - i * S, p.y() - i * S);
}
}
i = 1;
new_p = MyPoint(p.x() + i * S, p.y() + i * S);
while (map.find(new_p) != map.end() && map.find(new_p).value() == f)
{
i++;
num++;
if (num == 5)
return true;
else
{
new_p = MyPoint(p.x() + i * S, p.y() + i * S);
}
}
//上到下检查
i = 1; num = 1;
new_p = MyPoint(p.x(), p.y() - i * S);
while (map.find(new_p) != map.end() && map.find(new_p).value() == f)
{
i++;
num++;
if (num == 5)
return true;
else
{
new_p = MyPoint(p.x(), p.y() - i * S);
}
}
i = 1;
new_p = MyPoint(p.x(), p.y() + i * S);
while (map.find(new_p) != map.end() && map.find(new_p).value() == f)
{
i++;
num++;
if (num == 5)
return true;
else
{
new_p = MyPoint(p.x(), p.y() + i * S);
}
}
//右上到左下检查
i = 1; num = 1;
new_p = MyPoint(p.x() + i * S, p.y() - i * S);
while (map.find(new_p) != map.end() && map.find(new_p).value() == f)
{
i++;
num++;
if (num == 5)
return true;
else
{
new_p = MyPoint(p.x() + i * S, p.y() - i * S);
}
}
i = 1;
new_p = MyPoint(p.x() - i * S, p.y() + i * S);
while (map.find(new_p) != map.end() && map.find(new_p).value() == f)
{
i++;
num++;
if (num == 5)
return true;
else
{
new_p = MyPoint(p.x() - i * S, p.y() + i * S);
}
}
//左到右检查
i = 1; num = 1;
new_p = MyPoint(p.x() + i * S, p.y());
while (map.find(new_p) != map.end() && map.find(new_p).value() == f)
{
i++;
num++;
if (num == 5)
return true;
else
{
new_p = MyPoint(p.x() + i * S, p.y());
}
}
i = 1;
new_p = MyPoint(p.x() - i * S, p.y());
while (map.find(new_p) != map.end() && map.find(new_p).value() == f)
{
i++;
num++;
if (num == 5)
return true;
else
{
new_p = MyPoint(p.x() - i * S, p.y());
}
}
return false;
}
五、棋谱保存与读取
通过一个List保存落子顺序,游戏结束时按顺序存入json文件然后保存。
void fiveGo::save()
{
QJsonObject obj;
int i = 0;
for (auto &value : munual)
{
QJsonObject ob;
ob.insert("x", value.first.x());
ob.insert("y", value.first.y());
ob.insert("chess", value.second);
obj.insert(QString::number(i), ob);
i++;
}
QDateTime dateTime = QDateTime::currentDateTime();//获取系统当前的时间
QString str = QCoreApplication::applicationDirPath() + "/history/" + dateTime.toString("yyyy-MM-dd_hh_mm_ss") + ".his";//存储路径
QJsonDocument doc(obj);
QByteArray result = doc.toJson().toBase64(); //Base64加密
QFile f(str);
if (f.open(QIODevice::WriteOnly))
{
f.write(result);
f.close();
}
}
至此,五子棋小游戏就基本完成了。完结撒花