HTTP server git

http://fuzhii.com/2015/05/09/httpserver/

基于QT的QTcpServer类实现简单的HTTP服务器Tinyhttpd。代码存放在Github


QThread和QTimer设置

当子类化QThread时,构造函数在旧线程中执行,然而run()在新线程中执行。如果一个成员变量的访问来自两个函数,然后从两个不同的线程访问变量,需要检查这样做是否安全。

QTimer不能在一个线程里实例化,而在另一个线程start或者stop。采取的方法是:

QTimer* keep_alive_timer;

void Request::run()
{
    keep_alive_timer = new QTimer();
    if (s_keep_alive_enable)
    {
        keep_alive = s_keep_alive_default;
        keep_alive_timeout = s_keep_alive_timeout * 1000;// the wait time
        connect(keep_alive_timer,SIGNAL(timeout()),this,SLOT(onTimeout()));
        keep_alive_timer->setSingleShot(true);
        keep_alive_timer->setInterval(keep_alive_timeout);
        keep_alive_timer->start();
    }
    socket = new QTcpSocket();
    if (!socket->setSocketDescriptor(socketDescriptor))
        return;
    connect(socket, SIGNAL(readyRead()), this, SLOT(onReadyRead()), 
    Qt::DirectConnection);
    connect(socket, SIGNAL(disconnected()), this, SLOT(onDisconnected()),
    Qt::DirectConnection);
    exec();
}

QT事件循环机制

在上面的run()之后有一个exec()函数,它会进入事件循环。

Qt的事件循环是异步的,当调用QApplication::exec()时,就进入了事件循环。该循环可以简化的描述为如下的代码:

while ( !app_exit_loop )
{
   while( !postedEvents ) { processPostedEvents() }
   while( !qwsEvnts ){ qwsProcessEvents();   }
   while( !postedEvents ) { processPostedEvents() }

}

先处理Qt事件队列中的事件,直至为空。再处理系统消息队列中的消息,直至为空。在处理系统消息的时候会产生新的Qt事件,需要对其再次进行处理。


QT垃圾回收机制

在程序中有很多地方new了一个对象并没有delete,这样会不会造成内存泄漏呢。

在QT中不会,它实现了自己的GC机制。

所有继承自QObject类的类,如果在new的时候指定了父亲,那么它的清理是在父亲被delete的时候。如果一个程序中,所有的QObject类都指定了父亲,那么他们是会一级级的在最上面的父亲清理时被清理。我们需要显式释放的是那些没有父对象的孤立的指针对象。

千万不要在QT类的子类里写析构函数,不要问我为什么知道。今天已经调了一天了。


多端口监听实现

port的配置文件这么定义

[httpd]
port=1234|1235|1236|1237

读取配置

bool ok;
QString t_port = Settings::instance().value("httpd/port").toString();
QStringList port_list = t_port.split("|");

for(int i = 0; i < port_list.size(); i++)
    {
        int port = port_list[i].toInt(&ok,10);
        startInstance(port);
    }
    return true;

注意listen(QHostAddress::Any, 1234)指监听任何连上1234端口的IP。


incomingConnection虚函数

它是一个虚函数,当服务器收到连接请求时,它被QTcpServer调用。它在底层创建一个QTcpSocket,设置socket描述符并把描述符加入内部的列表(内部可能用select进行数据异步读写)。当重载这个函数时,可以改变服务器收到连接请求时的行为,相当于实现了一个hook。这样就可以在这个函数里实现http服务器。

void Server::incomingConnection(int socketDescriptor)
{
    //有新的连接时,代替默认的功能(新建socket)
    Request * request = new Request(socketDescriptor);
    request->start();
}

可见socket描述符传到了Request实例里,这个描述符可能是实现多client并发连接的关键。


Request类

Request是QThread的子类,它对每一个描述符新建一个线程来处理它,实质就是处理每个client的请求。Request类的核心是Request::run(),它还实现了incommingConnection新建socket的base功能,通过start()来进行调用。

void Request::run()
{
    socket = new QTcpSocket();
    //用socket描述符作为新socket的描述符
    if (!socket->setSocketDescriptor(socketDescriptor))
        return;
    connect(socket, SIGNAL(readyRead()), this, SLOT(onReadyRead()),
    Qt::DirectConnection);
    exec();
}

这个函数里需要用socketDescriptor作为新QTcpSocket对象的描述符。

If you want to handle an incoming connection as a new QTcpSocket object in another thread you have to pass the socketDescriptor to the other thread and create the QTcpSocket object there and use its setSocketDescriptor() method.

注意这个函数的最后一行是exec(),会进入事件循环,ServerThread::run()中也有exec()函数。

当数据准备好可读的时候,内核select发出readyRead()信号触发onReadyRead()函数。这样看来,onReadyRead()是一脉相承下来最重要的函数。


onReadyRead函数

这个函数里开始解析client发过来的request数据,并构造封装response数据。

这里是用浏览器抓取的标准的Request Headers和Response Headers。

onReadyRead函数里的执行顺序如下:

  1. getRequestHeader 负责读取request headers,确认client要求的是长\短连接。
  2. tryResponseFile 返回请求路径的目录,有4种情况。ResponseDirectory()返回目录;直接返回页面;404;403
  3. ResonseFile 调用response->response返回网页
  4. clearStatus 如果是长连接,clearStatus()后继续socket监听。同时计时,如果超过timeout则删除socket连接。如果是短连接,直接删除socket连接。

状态记录

基于Redis的C官方客户端hiredis封装一个类Database,用来记录访问的数据。

安装hiredis

git clone https://github.com/antirez/hiredis.git && cd hiredis 
make 
sudo make install 
sudo ldconfig

下面命令用来获取访问数据

redis-cli -p 6378 HMGET vistor:[n] time ip port agent


UI namespace

namespace Ui {
class MonitorUI;
}

class MonitorUI : public QMainWindow
{
    Q_OBJECT

public:
    explicit MonitorUI(QWidget *parent = 0);
    ~MonitorUI();
private:
    Ui::MonitorUI *ui;
};

前面声明了一个namespace Ui,它是一个匿名名称空间。该namespace 中的成员(变量或函数)具有独一无二的全局名称,避免名字碰撞 (name collisions)它的定义存在于自动生成的ui_monitorui.h文件中

namespace Ui {
    class MonitorUI: public Ui_MonitorUI {};
} 

因此Ui::MonitorUI和MonitorUI是不同的东西。

MonitorUI::MonitorUI(QWidget *parent) :
    QMainWindow(parent),
    ui(new Ui::MonitorUI)
{
    ui->setupUi(this);
}

可以看到ui是Ui::MonitorUI的一个实例。调用setupUi(this)。

setupUi() creates the actual instances of widgets for you. A form that you create in QtDesigner is stored just as XML file. So to be able to build the actual "window" with all the elements that you put on it in QtDesiner and display it in your application, setupUi() is created for you automatically by UIC (UI compiler - a Qt tool) so you don't have to do that manually.

两个一样的类名用namespace来区分开,实现逻辑与显示的分离(类似MVC)。


总结

Singleton Pattern用在日志系统和配置系统中,这个例子中不是线程安全的。Singleton Pattern的实例具有和程序一样的生存期,因为instance是new出来的,它一直存在时。所以析构函数没有机会调用了。这里会产生memory leak。


Reference

[1].http://blog.chinaunix.net/uid-27685749-id-3847998.html

[2].http://www.linuxidc.com/Linux/2011-03/33810p2.htm

[3].http://blog.csdn.net/envenler/article/details/8020064

[4].http://blog.csdn.net/jandunlab/article/details/14108595

[5].http://blog.sina.com.cn/s/blog_a6fb6cc90101hohu.html

[6].http://qimo601.iteye.com/blog/1407911

[7].http://tuzhii.com/2015/03/19/dessignpattern/

[8].http://stackoverflow.com/questions/2496918/singleton-pattern-in-c

[9].http://blog.csdn.net/solstice/article/details/6186978

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏web

background-color和background-image问题

1112
来自专栏西安-晁州

纯数字验证码

登录时常常要用到验证码验证,下面是一个简单的纯数字验证码,详细代码见image.jsp,将每次生成的验证码文本直接存入session中,登录时方便后台从sess...

2700
来自专栏linux运维学习

linux学习第二十五篇:cut,sort,wc,uniq,tee,tr,split命令,shell特殊符号

* 任意个任意字符 ? 任意一个字符 # 注释字符 \ 脱义字符 | 管道符 find /etc/ -type f -name “*conf” -exec ca...

2337
来自专栏我的技术专栏

Linux工具入门:make工具与Makefile文件

1653
来自专栏安恒信息

s2-029 Apache Struts2 标签远程代码执行分析

>>>> 标签介绍 Struts2标签库提供了主题、模板支持,极大地简化了视图页面的编写,而且,struts2的主题、模板都提供了很好的扩展性。实现了更好的代码...

2696
来自专栏Petrichor的专栏

git: .gitignore文件 (忽略文件)

Note: 切记目录名后面 一定要加 反斜杠 / ,不然会当成 单文件 处理。

1132
来自专栏丑胖侠

《Drools7.0.0.Final规则引擎教程》第4章 4.1 规则文件

一个标准的规则文件的格式为已“.drl”结尾的文本文件,因此可以通过记事本工具进行编辑。规则放置于规则文件当中,一个规则文件可以放置多条规则。在规则文件当中也可...

2585
来自专栏Linyb极客之路

从Java内存模型角度理解安全初始化

如大家所知,Java代码在编译和运行的过程中会对代码有很多意想不到且不受开发人员控制的操作:

933
来自专栏orientlu

FreeRTOS 消息队列

上面这几中方式中, 除了消息通知, 其他几种实现都是基于消息队列。消息队列作为主要的通信方式, 支持在任务间, 任务和中断间传递消息内容。 这一章介绍 Fre...

3242
来自专栏python3

Django的路由控制

URL配置(URLconf)就像Django 所支撑网站的目录。它的本质是URL与要为该URL调用的视图函数之间的映射表;你就是以这种方式告诉Django,对于...

3092

扫码关注云+社区