c++ Webserver的实现

这是为了掌握c++11的一些新特性和网络编程的知识而做的一个项目.

github:https://github.com/viktorika/Webserver

模型:

参考muduo部分代码,采用Reactor模型+EPOLL(ET)非阻塞IO模式.外加线程池提高服务器的并发性能.主线程accept,其他线程读-解析-写.

Reactor模型运行过程


事件循环loop->封装EPOLL_WAIT的poll->返回事件集handles->对每个handle执行事件handleEvent->handleEvent通过回调函数执行

线程池的实现:

muduo线程池的代码,讲道理我读得并不是很懂,等有空再仔细读读,但是它的one loop per thread思想起码我还是做到了,基本上就简单实现这个思想,一个线程一个loop.

HTTP解析问题:

因为采用的是ET模式,故每次都读完全部的数据再处理,处理的时候用状态机处理,我这里只用了4种状态,PARSE_ERROR,PARSE_METHOD,PARSE_HEADER,PARSE_SUCCESS,每一种状态都被我用bind绑定了实现方法,故不需要判断当前是哪种状态,写法比较优雅.每次当待分析串有一行时才进行分析,即发现"\r\n"才进行解析.主流程代码如下:

void Http_conn::parse(){
    bool zero=false;
    int readsum=readn(channel->getFd(),inbuffer,zero);
    if(readsum<0||zero){
        perror("read RST or FIN");
        initmsg();
        channel->setDeleted(true);
        channel->getLoop()->addTimer(channel,0);
        //读到RST和FIN默认FIN处理方式,这里的话因为我不太清楚读到RST该怎么处理,就一起这样处理好了
    }
    while(inbuffer.length()&&~inbuffer.find("\r\n",pos))
	parsestate=handleparse[parsestate]();
}

剩下的细节请看我的github里的代码.

计时器:

这个其实算是Reactor的一部分吧,就是用来处理超时连接的.timer的处理逻辑如下:

Timer里有两个数据结构:unordered_map和priority_queue,优先队列存放着TimerNode节点,只要节点的时间到了,或者该节点对应的channel事件关闭了那么该节点就可以删除,同时unorder_map是fd映射TimerNode节点,此TimerNode节点为存储在优先队列里的时间最久TimerNode节点,这里要注意,一个channel对应多个TimerNode节点,当该channel的TimerNode节点全部从优先队列中弹出时,删除掉该事件.这是我想到的比较好的Timer的处理方案.

涉及的知识点:

getopt():这个函数是Linux的c程序接口,用于转换命令行参数作为Linux命令选项和参数。

函数原型:int getopt(int argc,char * const argv[],const char * optstring);

前两个参数就是命令行参数,第三个就是选项字符串。

举个例子:选项字符串为"ab:c:d"

那么该程序有四个命令选项-a,-b,-c,-d。其中带冒号的b和c后面带参数,参数默认保存在optarg里。返回值为命令选项,返回-1证明没有参数了。


std::bind和std::function:这两个是C++11的新特性,用于函数调用。

std::bind,这个可以绑定函数参数,返回一个可用的临时变量,用于延迟调用。

std::function,作用类似函数指针,但是它比函数指针要安全,无需释放,可以认为是函数指针的智能指针。

通常两者结合使用,通过bind绑定参数返回给function,在需要的时候调用function就好了。


std::move:也是C++11的东西,用于赋值

当你一个变量a要赋值给另一个变量b的时候,如果赋值完后变量a就要销毁,那么这一步赋值的代价就不划算,故提供了一种新的方式,权限转移机制,你也可以理解为让这个变量a变成临时变量,也就是左值变成了右值。通过这种操作赋值完后a的值会变得未知。这里又谈到了左值和右值的问题,那么我们下面谈谈右值引用。


右值引用&&:这个顾名思义就是引用右值的。还是C++11的特性。

先来说说左值和右值

左值:可以出现在赋值号的左边或右边的变量。

右值:只能出现在赋值号右边的常量或临时变量。

举个例子a=3。a是变量可以出现在等号的左边和右边,所以是左值,3是常量只能出现在等号的右边是右值。

再有:a=a+b,a+b返回的是临时变量,只能出现在右边,故是右值。

然后右值引用就是用来处理无法引用右值的问题,在C++11以前,右值是没法引用的。


智能指针:虽然以前也有智能指针auto_ptr,但是到了C++11后已经摒弃这个东西了。

首先讲讲智能指针的原理,因为一般指针使用都是要new/malloc,然后离开作用域前要delete/free,那么你很有可能会忘了delete/free,而导致内存泄露。这时候智能指针就诞生了,通过把指针封装在模板类里面,调用构造函数分配内存,析构函数释放内存,那么只要离开作用域就会自动调用析构函数释放内存,就可以解决你忘了delete/free而导致的内存泄露问题了。

项目中使用了两种指针,这里把3种都讲一下吧。

shared_ptr:采用引用计数原理,每次复制都会把引用计数加1,销毁一个就减一,当引用计数为0时释放内存。

unique_ptr:控制权唯一,同一时间一个对象只能由一个unique_ptr指向它,离开作用域后释放内存,unique_ptr若要赋值给另一个unique_ptr,只能通过std::move变成右值,释放掉控制权才可以赋值给另一个unique_ptr。

weak_ptr:shared_ptr的辅助指针,只获得观测权,并不会使引用计数增加或减少。他的作用主要是解决循环引用问题,例如有两个类a和b,a的成员变量是shared_ptr <类b>,b的成员变量是shared_ptr<类a>,然后创建两个智能指针类,互相对成员变量赋值,最后导致a包含b,b包含a,那么他们的引用计数是2,当离开作用域后,假设b先销毁,那么b的引用计数减1,所以b的内存空间不释放,b的内存空间不释放意味着b里面的成员变量不释放,所以a的引用计数还是2,然后到a离开作用域,a也和b的结果一样,引用计数变成1,故两个都不能释放资源。为了解决这种循环引用问题,引入了weak_ptr来解决。


pthread_once_t:控制变量

其实这个我觉得也没什么好讲的,就是用来使某个函数在整个进程里只执行一次,所以叫控制变量。

这里如果有看过设计模式的朋友们会想起单例模式,没错单例模式使用控制变量的话就可以很优雅的实现了。不需要互斥锁+条件变量的使用。

用法:pthread_once(函数地址,控制变量地址);


mmap:内存映射

把文件或一个Posix共享内存区对象映射到进程的地址空间。

一般来说使用这个有三个目的:

(1)使用普通文件以提供内存映射I/O.

(2)使用特殊文件以提供匿名内存映射.

(3)使用shm_open以提供无亲缘关系进程间的Posix共享内存区.

项目中使用mmap的目的是就是第一个,通过映射可以不需要调用read和write系统调用,也可以释放fd资源。


gettimeofday:精确的获取时间

使用方法就不贴了,看github里的代码一看就懂的,要注意的问题是值都是32位的,所以*1000之后有可能溢出,这里我改成long long 以防溢出。

猜你喜欢

转载自blog.csdn.net/qq_34262582/article/details/80928476