zoukankan      html  css  js  c++  java
  • 用线程池实现的简单C++ web服务器

                                              用线程池实现的简单C++ Web服务器

    写了一个基于半同步/半反应堆模式的线程池实现的简单web服务器,主要可以复习IO复用,线程池,信号,有限状态机,HTTP协议解析等内容。

    自己总结了一下项目过程中遇到的问题,最后给出代码,代码是基于《Linux高性能服务器编程》编写的。

    0 服务器的功能和基本架构

    功能:结合线程池实现的一个并发web服务器,能够解析HTTP的GET请求,支持HTTP长连接,使用浏览器访问可以返回对应的内容。

    基本架构:采用Reactor模式(事件驱动+非阻塞IO) + 线程池。epoll循环用于事件通知,如果是listenfd上可读,则调用accept将新建的fd加入到epoll例程中;如果是已经连接的fd,将其加入到生产者-消费者队列中由工作线程竞争执行任务。

    1 为什么使用线程池?线程池的具体实现?

    1)多进程调用fork函数时,虽然地址空间是写时复制的,但是需要复制父进程的页表(CSAPP-P584),开销大,采用线程可以解决fork开销的问题,但是调度器和内存的问题还是没法解决,所以采用线程池,线程的数量固定。可以解决上面的问题。由于TCP连接是长连接,read读取会一直等待数据读完,造成阻塞,所以要设置成非阻塞。

    2)线程池的实现:使用C++封装一个线程池类,大致是创建固定数目的线程(比如和内核数目相当),然后类内部维护一个生产者-消费者队列(采用mutex和semaphore),提供相应的添加任务(生产者)和执行任务(消费者)的处理函数。

    其中semaphore保证队列满了不要继续压入,队列为空的时候不要取任务操作,mutex互斥锁保证每次只有一个线程在执行队列的读写操作。

     2 epoll使用边缘触发(Edge Trigger)还是条件触发(level Trigger)?

    答:考虑这样的情况,如果将fd放入生产者-消费者队列中后,拿到这个任务的工作线程还没有读完这个fd,因为没读完数据,所以这个fd可读,那么下一次事件循环又返回这个fd,又分给别的线程,就会出现数据一半给1号线程,另一半给了2号线程,没法处理数据,怎么处理?

    //水平触发LT每次读取数据,没读完fd的数据就注册事件,等到下一个epollwait再处理。
    //边缘触发ET每次都需要读完数据,用了while循环读取输入数据。

    采用边缘触发ET,边缘触发中输入缓冲收到数据仅注册一次该事件,即使输入缓冲中还有数据,也不会注册事件。

    用法是:将文件描述符fd设置为非阻塞,如果该fd可读,就使用一个while循环读取输入数据,直到返回-1且EAGAIN为止。

    3 如果某个线程在处理fd的同时,又有新的一批数据发来,该fd可读(注意和上面那个问题的区别,一个是处理同一批数据时,一个是处理新来的一批数据),那么该fd会被分给另一个线程,这样两个线程处理同一个fd肯定就不对了。

    答:即使我们使用ET模式,一个socket上的某个事件还是可能被触发多次,这不是我们希望的,所以可以使用epoll的EPOLLONESHOT事件解决。

    对于注册了EPOLLONSESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件,且只触发一次,除非我们使用epoll_ctl函数重置该文件描述符上注册的EPOLLONESHOT事件。这样,当一个线程在处理某个socket时,其他线程是不可能有机会操作该socket的。

    一旦注册了EPOLLONSESHOT事件的socket被某个线程处理完毕,该线程就应该立即重置这个socket上的EPOLLONESHOT事件,以确保这个socket下一次可读时,其EPOLLIN事件能被触发,进而让其他工作线程有机会继续处理这个socket。

    4 如何实现HTTP的解析?数据读到一半该怎么办?

    答:HTTP的请求每行结束都是回车换行 ,读取的数据长度等于0,说明读到了一个空行。

    首先需要明白HTTP的头部没有提供头部长度字段,我们判断HTTP头部结束的依据是遇到一个空行,该空行仅包括一对回车换行符(<CR><LF>),如果一次读操作没有读入HTTP请求的整个头部,即没有遇到空行,那我们必须等待客户继续写数据并再次读入。在不断寻找空行的过程中,我们可以完成对整个HTTP请求头部的分析(包括请求行和消息头),我们使用主从两个有限状态机实现HTTP请求的读取和分析。

    主状态机在内部调用从状态机,从状态机有三种状态:读取到一个完整的行LINE_OK,行出错LINE_BAD,行数据尚且不完整LINE_OPEN,从状态机是分析是否已经读取一行内容,主状态机分析多行内容,即一个HTTP完整请求。

    从状态机即parse_line函数,它从buffer中解析出一个行,下图描述了其可能的状态及状态转移过程。

     当从状态机读取到一行完整的内容后,就可以将内容交给主状态机process_read进行处理。

    主状态机使用checkstate变量来记录当前的状态。

    如果当前状态是CHECK_STATE_REQUESTLINE,则表示parse_line函数解析出的行是请求行,于是主状态机调用parse_requestline来分析请求行;

    如果当前状态是CHECK_STATE_HEADER,则表示parse_line函数解析出的是头部字段,于是主状态机调用parse_headers来分析头部字段。

    如果当前状态是CHECK_STATE_CONTENT,则表示parse_line函数解析出的是消息体,于是主状态机调用parse_content来分析消息体。(实际上只是分析是否读入了消息体)

    checkstate变量的初始值是CHECK_STATE_REQUESTLINE,parse_requestline函数在成功地分析请求行之后将其设置为CHECK_STATE_HEADER,从而实现状态转移。

    主状态机的核心代码如下:

    复制代码
    //主状态机,用于从buffer中取出所有完整的行
    http_conn::HTTP_CODE http_conn::process_read(){
        LINE_STATUS line_status = LINE_OK;//记录当前行的读取状态
        HTTP_CODE ret = NO_REQUEST;//记录HTTP请求的处理结果
        char* text = 0;
    
    </span><span style="color: #0000ff;">while</span>(((m_check_state == CHECK_STATE_CONTENT) &amp;&amp; (line_status ==<span style="color: #000000;"> LINE_OK)) 
        </span>|| (line_status = parse_line()) == LINE_OK) {<span style="color: #008000;">//</span><span style="color: #008000;">m_check_state记录主状态机当前的状态</span>
         text =<span style="color: #000000;"> get_line();
         m_start_line </span>=<span style="color: #000000;"> m_checked_idx;
         printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">got 1 http line:%s
    </span><span style="color: #800000;">"</span><span style="color: #000000;">,text);
    
         </span><span style="color: #0000ff;">switch</span><span style="color: #000000;">(m_check_state){
             </span><span style="color: #0000ff;">case</span> CHECK_STATE_REQUESTLINE:{<span style="color: #008000;">//</span><span style="color: #008000;">分析请求行</span>
                 ret =<span style="color: #000000;"> parse_request_line(text);
                 </span><span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> BAD_REQUEST){
                     printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">111
    </span><span style="color: #800000;">"</span><span style="color: #000000;">);
                     </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> BAD_REQUEST;
                 }
                 </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
             }
             </span><span style="color: #0000ff;">case</span> CHECK_STATE_HEADER:{<span style="color: #008000;">//</span><span style="color: #008000;">分析头部字段</span>
                ret =<span style="color: #000000;"> parse_headers(text);
                </span><span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> BAD_REQUEST){
                    printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">222
    </span><span style="color: #800000;">"</span><span style="color: #000000;">);
                    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> BAD_REQUEST;
                }
                </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> GET_REQUEST){
                    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> do_request();
                }
                </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
             }
             </span><span style="color: #0000ff;">case</span> CHECK_STATE_CONTENT:{<span style="color: #008000;">//</span><span style="color: #008000;">分析消息体</span>
                 ret =<span style="color: #000000;"> parse_content(text);
                 </span><span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> GET_REQUEST){
                     </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> do_request();
                 }
                 line_status </span>=<span style="color: #000000;"> LINE_OPEN;
                 </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
             }
             </span><span style="color: #0000ff;">default</span><span style="color: #000000;">:{
                 </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> INTERNAL_ERROR;
             }
    
         }
    
    }
    
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> NO_REQUEST;
    

    }

    复制代码

    5 忽略SIGPIPE信号

     SIGPIPE信号默认行为是终止,相应的事件是向一个没有读用户的管道做写操作。

    如果客户端意外关闭,那么服务器可能也就跟着直接挂了,这显然不是我们想要的。所以网络程序中服务端一般会忽略SIGPIPE信号。

    SIGPIPE产生的原因为,对方已经关闭了这个socket,但进程还往里面写。比如压力测试webbench设置了一个定时器,在正常测试时间会读取server返回的数据,并正常close;而当测试时间一到就直接close掉所有socket,不会读server返回的数据,这就导致了server往一个已被对方关闭的socket里写数据,系统发送了SIGPIPE。

    解决方案也非常简单,把SIGPIPE的信号handler设置为SIG_IGN,忽略该信号即可。

    但是,我的网络服务器程序使用了pthread线程库。而pthread线程库对于信号的捕获是逐线程的,上述代码段的线程和引起SIGPIPE的线程如果不是一个的话,将不起作用。因此,对于pthread线程库,还需执行下面代码段:

    复制代码
    sigset_t signal_mask;  
    sigemptyset (&signal_mask);  
    sigaddset (&signal_mask, SIGPIPE);  
    int rc = pthread_sigmask (SIG_BLOCK, &signal_mask, NULL);  
    if (rc != 0) {  
        printf("block sigpipe error
    ");  
    }   
    复制代码

    6 HTTP响应

    readv&writev函数有助于提高数据通信效率,功能是对数据进行整合传输及发送的函数。服务器端明确阻止Nagle算法能够提高效率。

    通过writev函数可以将分散保存在多个缓冲区中的数据一并发送,通过readv函数可以由多个缓冲区分别接收,适当使用这两个函数可以减少I/O的调用次数。

    复制代码
    #include<sys/uio.h>
    ssize_t writev(int filedes,const struct iovec* iov,int iovcnt);
    //成功时返回发送的字节数,失败时返回-1
    ssize_t readv(int filedes,const struct iovec* iov,int iovcnt);
    //成功时返回接收的字节数,失败时返回-1
    struct iovec{
        void* iov_base;//缓冲地址
        ize_t iov_len;//缓冲大小
    }
    //iovec表示数组的长度
    复制代码

    一个HTTP响应的组成是:一个响应行,后面跟随零个或者更多的响应报头(response header),在跟随一个终止报头的空行,再跟随一个响应主体。一个响应行的格式是:version status-code status-message。

    mmap函数来创建新的虚拟内存空间,并将对象映射到这些区域中。munmap函数删除虚拟内存的区域。

    7 调试

    写完后调试期间出了一些问题,以下是调试过程的总结。

      服务器运行后,使用浏览器访问对应网址总是显示服务器已经重置,然后自己思考服务器的整个逻辑,先看服务器是否正确解析HTTP请求,收到GET请求,发现parse_request_line解析请求行的函数没法解析请求,发现是strpbrk函数第二个参数应该写成空格而不是 。

    然后客户端还是显示服务器已经重置,使用curl命令代替浏览器,如下命令,显示

    (56) Recv failure: Connection reset by peer

    客户端一直没有接收到数据!!!而且服务器端显示404错误(我自己填充的http状态码)。

      能够解析请求报文,接下来看响应报文是否正确,响应报文是保存在自己开辟的一个write_buffer空间里面的,通过打印,发现响应报文正确,这时有个误导,发现响应报文里面connection响应报头是close,不是keep-alive,就认为是这个connfd已经断开连接了,然后去找到对应函数一直不关闭浏览器连接,发现不是这个问题,因为server的响应报文只是告诉客户端我想要断开连接,然后客户端解析响应报文,经过四次挥手将连接关闭,还有个time-wait保证中间响应不丢失可以重传,并且我设置了套接字属性,必须发送完数据套接字才能关闭,所以排除了这个假设。

    curl 127.0.0.1:9090

      服务器端请求报文解析和响应报文生成都没有问题,而客户端就是没接收到,下一步我就去定位是不是写给客户端的过程中出了问题,一看果然,因为我的程序中服务器需要找网站根目录然后在找到index.html,但是我的根目录写错了,导致找不到,-_-||,苦逼。不过通过这几天的调试,进一步熟悉了整个流程。 

    8 压力测试

    1
     
    复制代码
    需要设置虚拟机端口转发,VMware虚拟机配置端口转发(端口映射),实现远程访问
    Webbench能测试处在相同硬件上,不同服务的性能以及不同硬件上同一个服务的运行状况。webbench的标准测试可以
    向我们展示服务器的两项内容:每秒钟响应请求数和每秒钟传输数据量。webbench不但能具有标准静态页面的测试能力,
    还能对动态页面(ASP,PHP,JAVA,CGI)进行测试的能力。还有就是他支持对含有SSL的安全网站例如电子商务网站进
    行静态或动态的性能测试。
    Webbench最多可以模拟3万个并发连接去测试网站的负载能力。
    官方主页:http://home.tiscali.cz/~cz210552/webbench.html
    官方介绍:
    Web Bench is very simple tool for benchmarking WWW or proxy servers. Uses fork() 
    for simulating multiple clients and can use HTTP/0.9-HTTP/1.1 requests. This benchmark 
    is not very realistic, but it can test if your HTTPD can realy handle that many clients
    at once (try to run some CGIs) without taking your machine down. Displays pages/min and 
    bytes/sec. Can be used in more aggressive mode with -f switch.
    1、WebBench安装:
    wget http://www.ha97.com/code/webbench-1.5.tar.gz
    tar zxvf webbench-1.5.tar.gz
    cd webbench-1.5
    make
    make install
    

    2、WebBench使用:
    webbench
    -c 1000 -t 60 http://127.0.0.1:8080/index.html
    webbench -c 并发数 -t 运行测试时间 URL

    复制代码

    瓶颈:

    内存,最大文件描述符数目

    测试结果

    • 测试工具为Webbench,测试时间为60s,并发数及工作线程数为测试内容。

    • 测试环境为本地,配置4核心i7处理器。

    1000并发量

    在1K并发量下,并未因线程数增多性能有所提升,相反吞吐量还略微下降并且还有191个failed产生,通常线程数为CPU核心数。在测试中1分钟内4线程下请求671102个页面(515 bytes/page),全部成功。另外,在满负荷情况下TKeed总CPU使用率约40%(10% * 4threads或5% * 8threads)。请求1KB标准页面时,吞吐量稍有下降。

    • 4工作线程

      • 性能结果

        4worker

      • 系统负载

        4works

    • 8工作线程(结果)

      • 性能结果

        8worker

      • 系统负载

        8works

    • 标准1KB页面测试

      • 性能结果

        1KB_Page

     

    C10K问题本质?

    C10K问题本质是操作系统问题,创建线程多了,数据频繁拷贝(I/O,内核数据拷贝到用户进程空间、阻塞),

    进程/线程上下文切换消耗大,从而导致操作系统崩溃。

    10 为什么使用Reactor模型而不是Proactor模型?

    • Reactor为同步非阻塞模型,Proactor为异步非阻塞模型。核心区别在于I/O具体是由产生I/O的主线程完成还是交给操作系统来完成。

    • 异步I/O需要操作系统支持,因为当前Linux下AIO(异步I/O)还不够成熟,所以TKeed使用Reactor模型。

    • Reactor模型网络库:libevent, libev, libuv。

    • Proactor模型网络库:Boost.Asio,IOCP。

     

    11 程序代码

    整个逻辑是:main函数只负责读写,套接字的创建,读取数据后,由线程池中的工作线程处理任务,线程池里面包含的元素有:工作队列,线程,取出一个线程处理一个队列中的任务,处理的类有封装的函数,通过从状态机分析是否读完一行,然后主状态机解析请求报文,发送响应报文。

    其中注释掉的内容可以为以后定位服务器故障有一定指导。当然有一些Linux工具还是可以进一步熟悉,比如GDB调试多线程,trace跟踪。注意C++中用到模板的类,应该将x.h和x.cpp写到一起。这里为了阅读效果分开写的。

    //线程同步机制包装类
    #ifndef LOCKER_H
    #define LOCKER_H
    

    include<semaphore.h>

    include<exception>

    include<pthread.h>

    /封装信号量的类/
    class sem{
    public:
    //创建并初始化信号量
    sem(){
    if(sem_init(&m_sem,0,0) != 0){
    //失败可以通过抛出异常来报告错误
    throw std::exception();
    }
    }
    //销毁信号量
    ~sem(){
    sem_destroy(
    &m_sem);
    }
    //等待信号量
    bool wait(){
    return sem_wait(&m_sem) == 0;
    }
    //增加信号量
    bool post(){
    return sem_post(&m_sem) == 0;
    }
    private:
    sem_t m_sem;
    };

    /封装互斥锁的类/
    class locker{
    public:
    //创建并初始化互斥锁
    locker(){
    if(pthread_mutex_init(&m_mutex,NULL) != 0){
    throw std::exception();
    }
    }
    //销毁互斥锁
    ~locker(){
    pthread_mutex_destroy(
    &m_mutex);
    }
    //获取互斥锁
    bool lock(){
    return pthread_mutex_lock(&m_mutex) == 0;
    }
    //释放互斥锁
    bool unlock(){
    return pthread_mutex_unlock(&m_mutex) == 0;
    }
    private:
    pthread_mutex_t m_mutex;
    };
    /封装条件变量的类/
    class cond{
    public:
    //创建并初始化条件变量
    cond(){
    if(pthread_mutex_init(&m_mutex,NULL) != 0){
    throw std::exception();
    }
    if(pthread_cond_init(&m_cond,NULL) != 0){
    //出现问题,立即释放成功分配的资源
    pthread_mutex_destroy(&m_mutex);
    throw std::exception();
    }
    }
    //销毁条件变量
    ~cond(){
    pthread_mutex_destroy(
    &m_mutex);
    pthread_cond_destroy(
    &m_cond);
    }
    //等待条件变量
    bool wait(){
    int ret = 0;
    pthread_mutex_lock(
    &m_mutex);
    ret
    = pthread_cond_wait(&m_cond,&m_mutex);
    pthread_mutex_unlock(
    &m_mutex);
    return ret == 0;
    }
    //唤醒等待条件变量的线程
    bool singal(){
    return (pthread_cond_signal(&m_cond) == 0);
    }
    private:
    pthread_cond_t m_cond;
    pthread_mutex_t m_mutex;
    };

    #endif

    locker.h
    #ifndef HTTPCONNECTION_H
    #define HTTPCONNECTION_H
    

    include<unistd.h>

    include<signal.h>

    include<sys/types.h>

    include<sys/epoll.h>

    include<fcntl.h>

    include<sys/socket.h>

    include<netinet/in.h>

    include<arpa/inet.h>

    include<assert.h>

    include<sys/stat.h>

    include<string.h>

    include<pthread.h>

    include<stdio.h>

    include<stdlib.h>

    include<sys/mman.h>

    include<stdarg.h>//可变参数需要的头文件

    include<errno.h>

    include"locker.h"

    class http_conn
    {
    public:
    static const int FILENAME_LEN = 200;//文件名的最大长度
    static const int READ_BUFFER_SIZE = 2048;//读缓冲区的大小
    static const int WRITE_BUFFER_SIZE = 1024;//写缓冲区的大小
    /HTTP请求方法,但我们仅支持GET/
    enum METHOD{GET = 0,POST,HEAD,PUT,DELETE,TRACE,OPTIONS,CONNECT,PATCH};
    /解析客户请求时,主状态机所处的状态/
    enum CHECK_STATE{CHECK_STATE_REQUESTLINE = 0,CHECK_STATE_HEADER,CHECK_STATE_CONTENT};
    /服务器处理HTTP请求的可能结果/
    enum HTTP_CODE{NO_REQUEST,GET_REQUEST,BAD_REQUEST,NO_RESOURCE,
    FORBIDDEN_REQUEST,FILE_REQUEST,INTERNAL_ERROR,CLOSED_CONNECTION};

     </span><span style="color: #008000;">/*</span><span style="color: #008000;">行的读取状态</span><span style="color: #008000;">*/</span>   
     <span style="color: #0000ff;">enum</span> LINE_STATUS{LINE_OK = <span style="color: #800080;">0</span><span style="color: #000000;">,LINE_BAD,LINE_OPEN};
    

    public:
    http_conn();
    ~http_conn();

    public:
    void init(int sockfd,const sockaddr_in& addr);//初始化新接受的连接
    void close_conn(bool real_close = true);//关闭连接
    void process();//处理客户请求
    bool read();//非阻塞读操作
    bool write();//非阻塞写操作

    private:
    void init();//初始化连接
    HTTP_CODE process_read();//解析HTTP请求
    bool process_write(HTTP_CODE ret);//填充HTTP应答

    </span><span style="color: #008000;">//</span><span style="color: #008000;">下面这一组函数被process_read调用以分析HTTP请求</span>
    HTTP_CODE parse_request_line(<span style="color: #0000ff;">char</span>*<span style="color: #000000;"> text);
    HTTP_CODE parse_headers(</span><span style="color: #0000ff;">char</span>*<span style="color: #000000;"> text);
    HTTP_CODE parse_content(</span><span style="color: #0000ff;">char</span>*<span style="color: #000000;"> text);
    HTTP_CODE do_request();
    </span><span style="color: #0000ff;">char</span>* get_line(){<span style="color: #0000ff;">return</span> m_read_buf +<span style="color: #000000;"> m_start_line;}
    LINE_STATUS parse_line();
    
    </span><span style="color: #008000;">//</span><span style="color: #008000;">下面这一组函数被process_write调用以填充HTTP应答</span>
    <span style="color: #0000ff;">void</span><span style="color: #000000;"> unmap();
    </span><span style="color: #0000ff;">bool</span> add_response(<span style="color: #0000ff;">const</span> <span style="color: #0000ff;">char</span>* format,...);<span style="color: #008000;">//</span><span style="color: #008000;">可以允许参数个数的不确定</span>
    <span style="color: #0000ff;">bool</span> add_content(<span style="color: #0000ff;">const</span> <span style="color: #0000ff;">char</span>*<span style="color: #000000;"> content);
    </span><span style="color: #0000ff;">bool</span> add_status_line(<span style="color: #0000ff;">int</span> status,<span style="color: #0000ff;">const</span> <span style="color: #0000ff;">char</span>*<span style="color: #000000;"> title);
    </span><span style="color: #0000ff;">bool</span> add_headers(<span style="color: #0000ff;">int</span><span style="color: #000000;"> content_length);
    </span><span style="color: #0000ff;">bool</span><span style="color: #000000;"> add_linger();
    </span><span style="color: #0000ff;">bool</span><span style="color: #000000;"> add_blank_line();
    

    public:
    /所有socket上的事件都被注册到同一个epoll内核事件表中,所以将epoll文件描述符设置为静态的/
    static int m_epollfd;
    static int m_user_count;//统计用户数量

    private:
    //该HTTP连接的socket和对方的socket地址
    int m_sockfd;
    sockaddr_in m_address;

    </span><span style="color: #0000ff;">char</span> m_read_buf[READ_BUFFER_SIZE];<span style="color: #008000;">//</span><span style="color: #008000;">读缓冲区</span>
    <span style="color: #0000ff;">int</span> m_read_idx;<span style="color: #008000;">//</span><span style="color: #008000;">标识读缓冲区已经读入的客户数据的最后一个字节的下一个位置</span>
    <span style="color: #0000ff;">int</span> m_checked_idx;<span style="color: #008000;">//</span><span style="color: #008000;">当前正在分析的字符在读缓冲区中的位置</span>
    <span style="color: #0000ff;">int</span> m_start_line;<span style="color: #008000;">//</span><span style="color: #008000;">当前正在解析的行的初始位置</span>
    <span style="color: #0000ff;">char</span> m_write_buf[WRITE_BUFFER_SIZE];<span style="color: #008000;">//</span><span style="color: #008000;">写缓冲区</span>
    <span style="color: #0000ff;">int</span> m_write_idx;<span style="color: #008000;">//</span><span style="color: #008000;">写缓冲区中待发送的字节数</span>
    
    CHECK_STATE m_check_state;//主状态机当前所处的状态 METHOD m_method;//请求方法 //客户请求的目标文件的完整路径,其内容等于doc_root+m_url,doc_root是网站根目录 char m_real_file[FILENAME_LEN]; char* m_url;//客户请求的目标文件的文件名 char* m_version;//HTTP协议版本号,我们仅支持HTTP/1.1 char* m_host;//主机名 int m_content_length;//HTTP请求的消息体的长度 bool m_linger;//HTTP请求是否要求保持连接
    <span style="color: #0000ff;">char</span>* m_file_address;<span style="color: #008000;">//</span><span style="color: #008000;">客户请求的目标文件被mmap到内存中的起始位置</span>
    <span style="color: #0000ff;">struct</span> stat m_file_stat;<span style="color: #008000;">//</span><span style="color: #008000;">目标文件的状态。通过它我们可以判断文件是否存在/是否为目录/是否可读,并获得文件大小等信息
    </span><span style="color: #008000;">//</span><span style="color: #008000;">我们将采用writev来执行写操作,所以定义下面两个成员,其中m_iv_count表示被写内存块的数量</span>
    <span style="color: #0000ff;">struct</span> iovec m_iv[<span style="color: #800080;">2</span><span style="color: #000000;">];
    </span><span style="color: #0000ff;">int</span><span style="color: #000000;"> m_iv_count;
    

    };
    #endif

    http_conn.h
    #include"http_conn.h"
    

    //定义http响应的一些状态信息
    const char* ok_200_title = "OK";
    const char* error_400_title = "Bad Request";
    const char* error_400_form = "You request has had syntax or is inherently impossible to satisfy. ";
    const char* error_403_title = "Forbidden";
    const char* error_403_form = "You do not have permission to get file from this server. ";
    const char* error_404_title = "Not Found";
    const char* error_404_form = "The requested file was not found on this server. ";
    const char* error_500_title = "Internal Error";
    const char* error_500_form = "There was an unusual problem serving the requested file. "
    //网站的一些根目录
    const char* doc_root = "/var/www/html";

    int setnonblocking(int fd){//将文件描述符设置为非阻塞(边缘触发搭配非阻塞)
    int old_option = fcntl(fd,F_GETFL);
    int new_option = old_option| O_NONBLOCK;
    fcntl(fd,F_SETFL,new_option);
    return old_option;
    }

    void addfd(int epollfd,int fd,bool one_shot){//向epoll例程中注册监视对象文件描述符
    epoll_event event;
    event.data.fd = fd;
    event.events = EPOLLIN | EPOLLET | EPOLLRDHUB;//注册三种事件类型
    if(one_shot){
    event.events |= EPOLLONESHOT;
    }
    epoll_ctl(epollfd,EPOLL_CTL_ADD,fd,
    &event);
    setnonblocking(fd);
    }

    void removefd(int epollfd,int fd){//移除并关闭文件描述符
    epoll_ctl(epollfd,EPOLL_CTL_DEL,fd,0);
    close(fd);
    }

    void modfd(int epollfd,int fd,int ev){//重置事件,更改文件描述符,可以接受ev对应的读/写/异常事件
    epoll_event event;
    event.data.fd = fd;
    event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLEDHUB;
    epoll_cet(epollfd,EPOLL_CTL_MOD,fd,
    &event);
    }

    int http_conn :: m_user_count = 0;//用户数量
    int http_conn :: m_epollfd = -1;

    void http_conn :: close_conn(bool real_close){
    if(real_close && (m_sockfd != -1)){
    removefd(m_epollfd,m_sockfd);
    m_sockfd
    = -1;
    m_user_count
    --;//关闭一个连接时,将客户总量减1
    }
    }

    void http_conn :: init(int sockfd,const sockaddr_in& addr){
    m_sockfd
    = sockfd;
    m_address
    = addr;
    //以下两行为了避免TIME_WAIT状态
    int reuse = 1;
    setsockopt(m
    +sockfd,SOL_SOCKET,SO_REUSEADDR,&reuse,sizeof(reuse));
    addfd(m_epollfd,sockfd,
    true);
    m_user_count
    ++;

    init();
    

    }

    void http_conn::init(){
    m_check_state
    = CHECK_STATE_REQUESTLINE;
    m_linger
    = false;

    m_method </span>=<span style="color: #000000;"> GET;
    m_url </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
    m_version </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
    m_content_length </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
    m_host </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
    m_start_line </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
    m_checked_idx </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
    m_read_idx </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
    m_write_idx </span>= <span style="color: #800080;">0</span><span style="color: #000000;">;
    memset(m_read_buf,</span><span style="color: #800000;">'</span><span style="color: #800000;"></span><span style="color: #800000;">'</span><span style="color: #000000;">,READ_BUFFER_SIZE);
    memset(m_write_buf,</span><span style="color: #800000;">'</span><span style="color: #800000;"></span><span style="color: #800000;">'</span><span style="color: #000000;">,WRITE_BUFER_SIZE);
    memset(m_real_file,</span><span style="color: #800000;">'</span><span style="color: #800000;"></span><span style="color: #800000;">'</span><span style="color: #000000;">,FILENAME_LEN);
    

    }
    //从状态机=>得到行的读取状态,分别表示读取一个完整的行,行出错,行的数据尚且不完整
    http_conn::LINE_STATUS http_conn::parse_line(){
    char temp;
    /m_checked_idx指向buffer中当前正在分析的字节,m_read_idx指向buffer中客户数据的尾部的下一字节。
    buffer中[0~m_checked_idx - 1]都已经分析完毕,下面的循环分析[m_checked_idx~m_read_idx- 1]的数据
    /
    for(;m_checked_idx < m_read_idx;++m_checked_idx){
    //获取当前要分析的字节
    temp = m_read_buf[m_checked_idx];
    //如果当前字符是‘ ’则有可能读取一行
    if(temp == ' '){
    //如果 是buffer中最后一个已经被读取的数据,那么当前没有读取一个完整的行,还需要继续读
    if(m_checked_idx + 1 == m_read_idx){
    return LINE_OPEN;
    }
    //如果下一个字节是 ,那么已经读取到完整的行
    else if(m_read_buf[m_checked_idx + 1] == ' '){
    m_read_buf[m_checked_idx
    ++] = '';
    m_read_buf[m_checked_idx
    ++] = '';
    return LINE_OK;
    }
    //否则的话,说明客户发送的HTTP请求存在语法问题
    return LINE_BAD;
    }
    //如果当前的字节是 ,也说明可能读取到一个完整的行
    else if(temp == ' '){
    if(m_checked_idx > 1 && m_read_buf[m_checked_idx - 1] == ' '){
    m_read_buf[m_checked_idx
    - 1] = '';
    m_read_buf[m_checked_idx
    ++] = '';
    return LINE_OK;
    }
    else {
    return LINE_BAD;
    }
    }
    }
    //如果所有内容分析完毕也没遇到 字符,则还需要继续读取客户数据
    return LINE_OPEN;
    }

    //循环读取客户数据,直到无数据可读或者对方关闭连接
    bool http_conn::read(){
    if(m_read_idx >= READ_BUFFER_SIZE){
    return false;
    }
    while(true){
    bytes_read
    = recv(m_sockfd,m_read_buf + m_read_idx,READ_BUFFER_SIZE - m_read_idx,0);
    if(bytes_read == -1){
    if(errno == EAGAIN || errno == EWOULDBLOCK){
    break;
    }
    return false;
    }
    else if(bytes_read == 0){
    return false;
    }
    m_read_idx
    += bytes_read;
    }
    return true;
    }

    //解析HTTP请求行,获得请求方法,目标URL,以及HTTP版本号
    // GET http://www.google.com:80/index.html HTTP/1.1
    http_conn :: HTTP_CODE http_conn::parse_request_line(char text){
    m_url
    = strpbrk(text," ");//A找到第一个等于空格或者制表符的下标位置GET
    if(!m_url){
    return BAD_REQUEST;
    }
    m_url++ = '';//GET

    <span style="color: #0000ff;">char</span>* method =<span style="color: #000000;"> text;
    </span><span style="color: #0000ff;">if</span>(strcasecmp(method,<span style="color: #800000;">"</span><span style="color: #800000;">GET</span><span style="color: #800000;">"</span>) == <span style="color: #800080;">0</span>){<span style="color: #008000;">//</span><span style="color: #008000;">忽略大小写比较大小</span>
        m_method =<span style="color: #000000;"> GET;
    }
    </span><span style="color: #0000ff;">else</span><span style="color: #000000;">{
        </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> BAD_REQUEST;
    }
    
    m_url </span>+= strspn(m_url,<span style="color: #800000;">"</span><span style="color: #800000;">	</span><span style="color: #800000;">"</span>);<span style="color: #008000;">//</span><span style="color: #008000;">去掉GET后面多余空格的影响,找到其中最后一个空格位置</span>
    m_version = strpbrk(m_url,<span style="color: #800000;">"</span><span style="color: #800000;">	</span><span style="color: #800000;">"</span>);<span style="color: #008000;">//</span><span style="color: #008000;">找到url的结束位置html和HTTP中间的空格位置</span>
    <span style="color: #0000ff;">if</span>(!<span style="color: #000000;">m_version){
        </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> BAD_REQUEST;
    }
    </span>*m_version++ = <span style="color: #800000;">'</span><span style="color: #800000;"></span><span style="color: #800000;">'</span>;<span style="color: #008000;">//</span><span style="color: #008000;">htmlHTTP</span>
    m_version += strspn(m_version,<span style="color: #800000;">"</span><span style="color: #800000;">	</span><span style="color: #800000;">"</span>);<span style="color: #008000;">//</span><span style="color: #008000;">去掉中间空格,此时m_version指向H,从状态机中已经将每行的结尾设置为</span>
    <span style="color: #0000ff;">if</span>(strcasecmp(m_version,<span style="color: #800000;">"</span><span style="color: #800000;">HTTP/1.1</span><span style="color: #800000;">"</span>) != <span style="color: #800080;">0</span>){<span style="color: #008000;">//</span><span style="color: #008000;">仅支持HTTP/1.1</span>
        <span style="color: #0000ff;">return</span><span style="color: #000000;"> BAD_REQUEST;
    }
    </span><span style="color: #0000ff;">if</span>(strncmp(m_url,<span style="color: #800000;">"</span><span style="color: #800000;">HTTP://</span><span style="color: #800000;">"</span>,<span style="color: #800080;">7</span>) == <span style="color: #800080;">0</span>){<span style="color: #008000;">//</span><span style="color: #008000;">检查url是否合法</span>
        m_url += <span style="color: #800080;">7</span><span style="color: #000000;">;
        m_url </span>= strchr(m_url,<span style="color: #800000;">'</span><span style="color: #800000;">/</span><span style="color: #800000;">'</span>);<span style="color: #008000;">//</span><span style="color: #008000;">找/index中的/        </span>
    

    }

    </span><span style="color: #0000ff;">if</span>(!m_url || m_url[<span style="color: #800080;">0</span>] != <span style="color: #800000;">'</span><span style="color: #800000;">/</span><span style="color: #800000;">'</span>){<span style="color: #008000;">//</span><span style="color: #008000;">记住URL后缀是/</span>
        <span style="color: #0000ff;">return</span><span style="color: #000000;"> NO_REQUEST;
    }
    </span><span style="color: #008000;">//</span><span style="color: #008000;">HTTP请求行处理完毕,状态转移到头部字段的分析</span>
    m_check_state =<span style="color: #000000;"> CHECK_STATE_HEADER;
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> NO_REQUEST;
    

    }

    //分析头部字段
    //HTTP请求的组成是一个请求行,后面跟随0个或者多个请求头,最后跟随一个空的文本行来终止报头列表
    HTTP_CODE parse_headers(char* temp){
    //遇到空行,说明头部字段解析完毕
    if(text[0] == ''){
    //如果HTTP请求有消息体,则还需要读取m_content_length字节的消息体,状态机转移到CHECK_STATE_CONTENT状态
    if(m_content_length != 0){
    m_check_state
    = CHECK_STATE_CONTENT;
    return NO_REQUEST;
    }
    //否则说明我们得到一个完整的HTTP请求
    return GET_REQUEST;
    }
    //下面处理多种请求报头
    //处理Connection头部字段
    else if(strncasecmp(text,"Connection:",11) == 0){
    text
    += 11;
    text
    += strspn(text," ");
    if(strcasecmp(text,"keep-alive") == 0){
    m_linger
    = true;
    }
    }
    //处理content-length头部字段
    else if(strcasecmp(text,"Content-Length:",15) == 0){
    text
    += 15;
    text
    += strspn(text," ");
    m_content_length
    = atol(text);
    }
    //处理Host头部信息
    else if(strncasecmp(text,"Host:",5) == 0){
    text
    += 5;
    text
    += strspn(text," ");
    m_host
    = text;
    }
    else{
    printf(
    "oop!unkonwn header %s ",text);
    }

    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> NO_REQUEST;
    

    }

    //我们没有真正的解析HTTP请求的消息体,只是判断它是否被完整的读入了
    http_conn::HTTP_CODE http_conn::parse_content(char* text){
    if(m_read_idx >= (m_content_length + m_checked_idx)){
    text[m_content_length]
    = '';
    return GET_REQUEST;
    }

    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> NO_REQUEST;
    

    }

    //主状态机,用于从buffer中取出所有完整的行
    http_conn::HTTP_CODE http_conn::process_read(){
    LINE_STATUS line_status
    = LINE_OK;//记录当前行的读取状态
    HTTP_CODE ret = NO_REQUEST;//记录HTTP请求的处理结果
    char* text = 0;

    </span><span style="color: #0000ff;">while</span>(((m_check_state == CHECK_STATE_CONTENT) &amp;&amp; (line_status ==<span style="color: #000000;"> LINE_OK)) 
        </span>|| (line_status = parse_line()) == LINE_OK) {<span style="color: #008000;">//</span><span style="color: #008000;">m_check_state记录主状态机当前的状态</span>
         text =<span style="color: #000000;"> get_line();
         m_start_line </span>=<span style="color: #000000;"> m_checked_idx;
         printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">got 1 http line:%s
    </span><span style="color: #800000;">"</span><span style="color: #000000;">,text);
    
         </span><span style="color: #0000ff;">switch</span><span style="color: #000000;">(m_check_state){
             </span><span style="color: #0000ff;">case</span> CHECK_STATE_REQUEST:{<span style="color: #008000;">//</span><span style="color: #008000;">分析请求行</span>
                 ret =<span style="color: #000000;"> parse_request_line(text);
                 </span><span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> BAD_REQUEST){
                     </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> BAD_REQUEST;
                 }
                 </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
             }
             </span><span style="color: #0000ff;">case</span> CHECK_STATE_HEADER:{<span style="color: #008000;">//</span><span style="color: #008000;">分析头部字段</span>
                ret =<span style="color: #000000;"> parse_header(text);
                </span><span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> BAD_REQUEST){
                    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> BAD_REQUEST;
                }
                </span><span style="color: #0000ff;">else</span> <span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> GET_REQUEST){
                    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> do_request();
                }
                </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
             }
             </span><span style="color: #0000ff;">case</span> CHECK_STATE_CONTENT:{<span style="color: #008000;">//</span><span style="color: #008000;">分析消息体</span>
                 ret =<span style="color: #000000;"> parse_content(text);
                 </span><span style="color: #0000ff;">if</span>(ret ==<span style="color: #000000;"> GET_REQUEST){
                     </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> do_request();
                 }
                 line_status </span>=<span style="color: #000000;"> LINE_OPEN;
                 </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
             }
             </span><span style="color: #0000ff;">default</span><span style="color: #000000;">:{
                 </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> INTERNAL_ERROR;
             }
    
         }
    
    }
    
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> NO_REQUEST;
    

    }

    /当得到一个完整正确的HTTP请求时,我们就分析目标文件的属性。如果目标文件存在,
    对所有用户可读,且不是目录,则使用mmap将其映射内存地址m_file_address处,并告诉调用者获取文件成功
    /
    http_conn::HTTP_CODE http_conn::do_request(){
    strcpy(m_real_file,doc_root);
    int len = strlen(doc_root);
    //m_real_file客户请求的目标文件的完整路径,其内容等于doc_root + m_url,doc_root是网站根目录
    strncpy( m_real_file + len,m_url,FILENAME_LEN - len - 1);
    if(stat(m_real_file,&m_file_stat)){//获取文件的状态并保存在m_file_stat中
    return NO_RESOURCE;
    }
    if(!(m_file_stat.st_mode & S_TROTH)){
    return FORBIDDEN_REQUEST;
    }
    if(S_ISDIR(m_file_stat.st_mode)){
    return BAD_REQUEST;
    }

    </span><span style="color: #0000ff;">int</span> fd =<span style="color: #000000;"> open(m_real_file,O_RDONLY);
    </span><span style="color: #008000;">//</span><span style="color: #008000;">创建虚拟内存区域,并将对象映射到这些区域</span>
    m_file_address = (<span style="color: #0000ff;">char</span>*)mmap(<span style="color: #800080;">0</span>,m_file_stat.st_size,PROT_READ,MAP_PRIVATE,fd,<span style="color: #800080;">0</span><span style="color: #000000;">);
    close(fd);
    </span><span style="color: #0000ff;">return</span><span style="color: #000000;"> FILE_REQUEST;
    

    }

    //对内存映射区执行munmap操作
    void http_conn::unmap(){
    if(m_file_address){
    munmap(m_file_address,m_file_stat.st_size);
    //删除虚拟内存的区域
    m_file_address = 0
    }
    }

    //写HTTP响应
    bool http_conn::write(){
    int temp = 0;
    int bytes_have_send = 0;
    int bytes_to_send = m_write_idx;
    if(bytes_to_send == 0){
    modfd(m_epollfd,m_sockfd,EPOLLIN);
    init();
    return true;
    }

    </span><span style="color: #0000ff;">while</span>(<span style="color: #800080;">1</span><span style="color: #000000;">){
        temp </span>=<span style="color: #000000;"> writev(m_sockfd,m_iv,m_iv_count);
        </span><span style="color: #0000ff;">if</span>(temp &lt;= -<span style="color: #800080;">1</span><span style="color: #000000;">){
        </span><span style="color: #008000;">//</span><span style="color: #008000;">如果TCP写缓存没有空间,则等待下一轮EPOLLOUT事件。虽然在此期间,服务器无法立即接收到同一客户的下一个请求,但是可以保证连接的完整性</span>
            <span style="color: #0000ff;">if</span>(errno == EAGAIN){<span style="color: #008000;">//</span><span style="color: #008000;">当前不可写</span>
    

    modfd(m_epollfd,m_sockfd,EPOLLOUT);
    return true;
    }
    unmap();
    return false;
    }

        bytes_to_send </span>-=<span style="color: #000000;"> temp;
        bytes_have_send </span>+=<span style="color: #000000;"> temp;
        </span><span style="color: #0000ff;">if</span>(nytes_to_send &lt;=<span style="color: #000000;"> bytes_have_send){
            </span><span style="color: #008000;">//</span><span style="color: #008000;">发送HTTP响应成功,根据HTTP请求中的Connection字段决定是否立即关闭连接</span>
    

    unmap();
    if(m_linger){
    init();
    modfd(m_epollfd,m_sockfd,EPOLLIN);
    return true;
    }
    else{
    modfd(m_epollfd,m_sockfd,EPOLLIN);
    return false;
    }
    }
    }
    }

    //往写缓冲区写入待发送的数据
    bool http_conn::add_response(const char*format,...){
    if(m_write_idx >= WRITE_BUFFER_SIZE){
    return false;
    }
    va_list arg_list;
    va_start(arg_list,format);
    //将可变参数格式化输出到一个字符数组
    int len = vsnprintf(m_write_buf + m_write_idx,WRITE_BUFFER_SIZE - 1 - m_write_idx,format,arg_list);
    if(len >= (WRITE_BUFFER_SIZE - 1 - m_write_idx)){
    return false;
    }
    m_write_idx
    += len;
    va_end(arg_list);
    return true;
    }

    bool http_conn::add_status_line(int status,const char* title){
    return add_response("%s %d %s ","HTTP/1.1",status,title);
    }

    bool http_conn::add_headers(int content_len){//头部就三种信息
    add_content_length(content_len);//内容长度
    add_linger();//客户连接信息
    add_blank_line();//空行
    }

    bool http_conn::add_content_length(int content_len){
    return add_response("Content-Length: %d ",content_len);
    }

    bool http_conn::add_linger(){
    return add_response("Connection: %s ",(m_linger == true) ? "keep-alive" : "close");
    }

    bool http_conn::add_blank_line(){
    return add_response("%s"," ");
    }

    bool http_conn::add_content(const char* content){
    return add_response("%s",content);
    }

    //根据服务器处理HTTP请求的结果,决定返回给客户端的内容
    bool http_conn::process_write(HTTP_CODE ret){
    switch(ret){
    case INTERNAL_ERROR:{
    add_status_line(
    500,error_500_title);
    add_headers(strlen(error_500_form));
    if(!add_content(error_500_form)){
    return false;
    }
    break;
    }
    case BAD_REQUEST:{
    add_status_line(
    400,error_400_title);
    add_headers(strlen(error_400_form));
    if(!add_content(error_400_form)){
    return false;
    }
    break;
    }
    case NO_RESOURCE:{
    add_status_line(
    404,error_404_title);
    add_headers(strlen(error_404_form));
    if(!add_content(error_404_form)){
    return false;
    }
    break;
    }
    case FORBIDDEN_REQUEST:{
    add_status_line(
    403,error_403_title);
    add_headers(strlen(error_403_form));
    if(!add_content(error_403_form)){
    return false;
    }
    break;
    }
    case FILE_REQUEST:{
    add_status_line(
    200,ok_200_title);
    if(m_file_stat.st_size != 0){//st_size表示文件的大小
    add_headers(m_file_stat.st_size);
    m_iv[
    0].iov_base = m_write_buf;
    m_iv[
    0].iov_len = m_write_idx;
    m_iv[
    1].iov_base = m_file_address;
    m_iv[
    1].iov_len = m_file_stat.st_size;
    m_iv_count
    = 2;
    return true;
    }
    else{
    const char* ok_string = "<html><body></body></html>";
    add_headers(strlen(strlen(ok_string)));
    if(!add_content(ok_string)){
    return false;
    }
    }
    }
    default:{
    return false;
    }
    m_iv[
    0].iov_base = m_write_buf;
    m_iv[
    0].iov_len = m_write_idx;
    m_iv_count
    = 1;
    return true;
    }
    }

    //有线程池中的工作线程调用,这是处理HTTP请求的入口函数
    void http_conn::process(){
    HTTP_CODE read_ret
    = process_read();
    if(read_ret == NO_REQUEST){
    modfd(m_epollfd,m_sockfd,EPOLLIN);
    return;
    }

    </span><span style="color: #0000ff;">bool</span> write_ret =<span style="color: #000000;"> process_write(read_ret);
    </span><span style="color: #0000ff;">if</span>(!<span style="color: #000000;">write_ret){
         close_conn();
    }
    modfd(m_epollfd,m_sockfd,EPOLLOUT);
    

    }

    http_conn.cpp
    #ifndef THREADPOOL_H
    #define THREADPOOL_H
    

    include<list>

    include<cstdio>

    include<exception>

    include<pthread.h>

    include"locker.h"

    /线程池类,把它定义为模板类是为了代码复用,模板参数T是任务类/
    template
    <typename T>
    class threadpool{
    public:
    /参数thread_number是线程池中线程的数量,max_requests是请求队列中最多允许的,等待处理的请求的数量/
    threadpool(
    int thread_number = 8,int max_requests = 1000);
    ~threadpool();
    //往请求队列中添加任务
    bool append(T* request);

    private:
    //工作线程运行的函数,它不断从工作队列中取出任务并执行之
    static void* worker(void* arg);
    void run();

    private:
    int m_thread_number;//线程池中的线程数
    int m_max_requests; //请求队列中允许的最大请求数
    pthread_t* m_threads;//描述线程池的数组,其大小为m_thread_number
    std::list<T*> m_workqueue;//请求队列
    locker m_queuelocker;//保护请求队列的互斥锁
    sem m_queuestat;//是否有任务需要处理
    bool m_stop;//是否结束线程
    };
    template
    <typename T>
    threadpool
    <T>::threadpool(int thread_number,int max_requests):
    m_thread_number(thread_number),m_max_requests(max_requests),
    m_stop(
    false),m_threads(NULL)

    {
    if(thread_number <= 0 || max_requests <= 0){
    throw std::exception();
    }
    m_threads
    = new pthread_t[m_thread_number];
    if(!m_threads){
    throw std::exception();
    }
    //创建thread_number个线程,并设置为分离线程
    for(int i = 0;i < thread_number;++i){
    printf(
    "create the %dth thread ",i + 1);
    if(pthread_create(&m_threads[i],NULL,worker,this) != 0){
    delete [] m_threads;
    throw std::exception();
    }
    if(pthread_detach(m_threads[i]) != 0){
    delete [] m_threads;
    throw std::exception();
    }
    }

    }

    template<typename T>
    threadpool
    <T> :: ~threadpool(){
    delete [] m_threads;
    throw std:: exception();
    }

    template<typename T>
    bool threadpool<T>::append(T request){
    /
    操作工作队列前一定要加锁,因为它被所有工作队列共享*/
    m_queuelocker.
    lock();
    if(m_workqueue.size() > m_max_requests){
    m_queuelocker.unlock();
    return false;
    }
    m_workqueue.push_back(request);
    m_queuelocker.unlock();
    m_queuestat.post();
    //保证队列非空
    return true;
    }

    template<typename T>
    void* threadpool<T> :: worker(void* arg){//传入的是this
    threadpool* pool = (threadpool)arg;//将void×指针转化为threadpool指针
    pool -> run();//就是下面实现的run函数
    return pool;
    }

    template<typename T>
    void threadpool<T>::run(){//消费者
    while(!m_stop){
    m_queuestat.wait();
    //执行p操作
    m_queuelocker.lock();//对工作队列操作钱加锁
    if(m_workqueue.empty()){
    m_queuelocker.unlock();
    continue;
    }
    T
    * request = m_workqueue.front();
    m_workqueue.pop_front();
    m_queuelocker.unlock();
    if(!request){
    continue;
    }
    request
    -> process();//任务中要有process处理函数
    }
    }

    #endif

    threadpool.h
    #include<sys/socket.h>
    #include<netinet/in.h>
    #include<arpa/inet.h>
    #include<stdio.h>
    #include<unistd.h>
    #include<errno.h>
    #include<string.h>
    #include<fcntl.h>
    #include<stdlib.h>
    #include<cassert>
    #include<sys/epoll.h>
    

    include"locker.h"

    include"threadpool.h"

    include"http_conn1.h"

    #define MAX_FD 65536
    #define MAX_EVENT_NUMBER 10000

    extern void addfd(int epollfd,int fd,bool one_shot);
    extern void removefd(int epollfd,int fd);

    void addsig(int sig,void(handler)(int),bool restart = true){
    struct sigaction sa;
    memset(
    &sa,'',sizeof(sa));
    sa.sa_handler
    = handler;
    if(restart){
    sa.sa_flags
    |= SA_RESTART;
    }
    sigfillset(
    &sa.sa_mask);
    assert(sigaction(sig,
    &sa,NULL) != -1);
    }

    void show_error(int connfd,const char* info){
    send(connfd,info,strlen(info),
    0);
    close(connfd);
    }

    int main(int argc,char argv[]){
    if(argc <= 2){//argv[0]可执行文件名/main,argv[1]IP地址,argv[2]是端口号
    printf("usage: %s ip_address port_number ",basename(argv[0]));//最后一个/的字符串内容
    return 1;
    }
    const char
    ip = argv[1];
    char* port = argv[2];

    </span><span style="color: #008000;">//</span><span style="color: #008000;">忽略SIGPIPE信号</span>
    addsig(SIGPIPE,SIG_IGN);<span style="color: #008000;">//</span><span style="color: #008000;">SIG_IGN表示忽略SIGPIPE那个注册的信号。
    
    </span><span style="color: #008000;">//</span><span style="color: #008000;">创建线程池</span>
    threadpool&lt;http_conn&gt;* pool =<span style="color: #000000;"> NULL;
    </span><span style="color: #0000ff;">try</span>{<span style="color: #008000;">//</span><span style="color: #008000;">这里的语句有任何异常就执行下面的return</span>
        pool = <span style="color: #0000ff;">new</span> threadpool&lt;http_conn&gt;<span style="color: #000000;">;        
    }
    </span><span style="color: #0000ff;">catch</span><span style="color: #000000;">(...){
        </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">1</span><span style="color: #000000;">;
    }
    
    </span><span style="color: #008000;">//</span><span style="color: #008000;">预先为每个可能的客户连接分配一个http_conn对象</span>
    http_conn* users = <span style="color: #0000ff;">new</span><span style="color: #000000;"> http_conn[MAX_FD];
    assert(users);
    </span><span style="color: #0000ff;">int</span> user_count = <span style="color: #800080;">0</span><span style="color: #000000;">;
    
    </span><span style="color: #0000ff;">int</span> listenfd = socket(PF_INET,SOCK_STREAM,<span style="color: #800080;">0</span><span style="color: #000000;">);
    assert(listenfd </span>&gt;= <span style="color: #800080;">0</span><span style="color: #000000;">);
    </span><span style="color: #0000ff;">struct</span> linger tmp = {<span style="color: #800080;">1</span>,<span style="color: #800080;">0</span>};<span style="color: #008000;">//</span><span style="color: #008000;">1表示还有数据没发送完毕的时候容许逗留,0表示逗留时间</span>
    setsockopt(listenfd,SOL_SOCKET,SO_LINGER,&amp;tmp,<span style="color: #0000ff;">sizeof</span>(tmp));<span style="color: #008000;">//</span><span style="color: #008000;">即让没发完的数据发送出去后在关闭socket</span>
    
    <span style="color: #0000ff;">int</span> ret = <span style="color: #800080;">0</span><span style="color: #000000;">;
    </span><span style="color: #0000ff;">struct</span><span style="color: #000000;"> sockaddr_in address;
    bzero(</span>&amp;address,<span style="color: #0000ff;">sizeof</span><span style="color: #000000;">(address));
    address.sin_family </span>=<span style="color: #000000;"> AF_INET;
    </span><span style="color: #008000;">//</span><span style="color: #008000;">address.sin_addr.s_addr = htonl(ip);</span>
    inet_aton(ip,&amp;<span style="color: #000000;">address.sin_addr);
    address.sin_port </span>=<span style="color: #000000;"> htons(atoi(port));
    
    ret </span>= bind(listenfd,(<span style="color: #0000ff;">struct</span> sockaddr*)&amp; address,<span style="color: #0000ff;">sizeof</span><span style="color: #000000;">(address));
    assert(ret </span>&gt;= <span style="color: #800080;">0</span><span style="color: #000000;">);
    
    ret </span>= listen(listenfd,<span style="color: #800080;">5</span><span style="color: #000000;">);
    epoll_event events[MAX_EVENT_NUMBER];
    </span><span style="color: #0000ff;">int</span> epollfd = epoll_create(<span style="color: #800080;">5</span><span style="color: #000000;">);
    assert(epollfd </span>!= -<span style="color: #800080;">1</span><span style="color: #000000;">);
    addfd(epollfd,listenfd,</span><span style="color: #0000ff;">false</span><span style="color: #000000;">);
    http_conn::m_epollfd </span>=<span style="color: #000000;"> epollfd;
    
    </span><span style="color: #0000ff;">while</span>(<span style="color: #0000ff;">true</span><span style="color: #000000;">){
        </span><span style="color: #0000ff;">int</span> number = epoll_wait(epollfd,events,MAX_EVENT_NUMBER,-<span style="color: #800080;">1</span><span style="color: #000000;">);
        </span><span style="color: #0000ff;">if</span>((number &lt; <span style="color: #800080;">0</span>) &amp;&amp; (errno !=<span style="color: #000000;"> EINTR)){
            printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">epoll failure
    </span><span style="color: #800000;">"</span><span style="color: #000000;">);
            </span><span style="color: #0000ff;">break</span><span style="color: #000000;">;
        }
        
        </span><span style="color: #0000ff;">for</span>(<span style="color: #0000ff;">int</span> i = <span style="color: #800080;">0</span>;i &lt; number;++<span style="color: #000000;">i){
            </span><span style="color: #0000ff;">int</span> sockfd =<span style="color: #000000;"> events[i].data.fd;
            </span><span style="color: #0000ff;">if</span>(sockfd ==<span style="color: #000000;"> listenfd){
                </span><span style="color: #0000ff;">struct</span><span style="color: #000000;"> sockaddr_in client_address;
                socklen_t client_addrlength </span>= <span style="color: #0000ff;">sizeof</span><span style="color: #000000;">(client_address);                
                </span><span style="color: #0000ff;">int</span> connfd = accept(listenfd,(<span style="color: #0000ff;">struct</span> sockaddr*)&amp; client_address,&amp;<span style="color: #000000;">client_addrlength);
                </span><span style="color: #0000ff;">if</span>(connfd &lt; <span style="color: #800080;">0</span><span style="color: #000000;">){
                    printf(</span><span style="color: #800000;">"</span><span style="color: #800000;">error is: %d
    </span><span style="color: #800000;">"</span><span style="color: #000000;">,errno);
                    </span><span style="color: #0000ff;">continue</span><span style="color: #000000;">;
                }
                </span><span style="color: #0000ff;">if</span>(http_conn::m_user_count &gt;=<span style="color: #000000;"> MAX_FD){
                    show_error(connfd,</span><span style="color: #800000;">"</span><span style="color: #800000;">Internal server busy</span><span style="color: #800000;">"</span><span style="color: #000000;">);
                    </span><span style="color: #0000ff;">continue</span><span style="color: #000000;">;
                }
                </span><span style="color: #008000;">//</span><span style="color: #008000;">初始化客户连接</span>
    

    users[connfd].init(connfd,client_address);
    printf(
    "sock_close ");
    }
    else if(events[i].events & (EPOLLRDHUP | EPOLLHUP |EPOLLERR)){
    //如果有异常,直接关闭客户连接
    printf("sock_exception_close ");
    users[sockfd].close_conn();
    }
    else if(events[i].events & EPOLLIN){
    //根据读的结果,决定将任务添加到线程池,还是关闭连接
    if(users[sockfd].read()){
    pool
    ->append(users + sockfd);
    }
    else{
    printf(
    "sock_read_close ");
    users[sockfd].close_conn();
    }
    }
    else if(events[i].events & EPOLLOUT){
    //根据写的结果,决定是否关闭连接
    printf("write_main ");
    if(!users[sockfd].write()){
    printf(
    "sock_write_close ");
    users[sockfd].close_conn();
    }
    }
    else
    {printf(
    "close ");}
    }

    }
    
    close(epollfd);
    close(listenfd);
    </span><span style="color: #0000ff;">delete</span><span style="color: #000000;"> [] users;
    </span><span style="color: #0000ff;">delete</span><span style="color: #000000;"> pool;
    </span><span style="color: #0000ff;">return</span> <span style="color: #800080;">0</span><span style="color: #000000;">;
    

    }

    main.cpp

    12 参考资料

    [1]. 游双. Linux高性能服务器编程[M]. 机械工业出版社, 2013.

    [2]. 如何写一个Web服务器 http://lifeofzjs.com/blog/2015/05/16/how-to-write-a-server/

    [3]. 尹圣雨. TCP/IP网络编程[M]. 人民邮电出版社, 2014.

    [4]. Randal E.Bryant.深入理解计算机系统[M].机械工业出版社,2016.

  • 相关阅读:
    zend guard 4/5 破解版和免过期办法,已补授权Key一枚,成功注册。
    一身冷汗,PHP应该禁用的函数
    CentOS 5.5 安装和卸载桌面
    php加速模块 eAccelerator open_basedir错误解决办法
    配置电信网通双线双IP的解决办法
    php安装igbinary模块
    ubuntu系统VNC服务器安装配置
    python3 之 闭包实例解析 Be
    python3 之 内置函数enumerate Be
    python3 之 匿名函数 Be
  • 原文地址:https://www.cnblogs.com/xjyxp/p/11466356.html
Copyright © 2011-2022 走看看