基于半同步/半反应堆线程池实现的HTTP解析服务端程序

时间:2022-10-02 14:58:20

简介:

     半同步/半反应堆线程池是通过一个线程往工作队列添加任务T,然后工作线程竞争工作队列获得任务T。HTTP请求解析服务端程序:逐行解析客户端发送来的HTTP请求然后作出HTTP回答。采用线程池就是:服务端创建一个线程池,然后有HTTP请求到达就将HTTP请求的处理添加到线程池任务队列中去,线程池工作线程通过竞态机制(信号量)竞争任务T(HTTP请求处理)。

   HTTP请求内容:

      GET http://www.baidu.com/index.html HTTP/1.1       //该行是HTTP请求行,GET是请求方法表示以只读方式获取资源  http那一串是url   HTTP那个是客户端HTTP版本

      User-Agent:Wget/1.12 (linux-gnu)                               //客户端使用的程序

      Host: www.baidu.com                                                     //目的主机名,必须有

      Connection: close                                                            //用于告诉服务器处理完这个请求后关闭连接,可选其它

      注意:第一行为请求行,后面3行是HTTP头部,HTTP请求每行均以回车符和换行符结束,所有头部字段结束后必须包含一个空行,该空行只能有一个 回车和换行符。HTTP回答类似。可见HTTP解析分为请求行和头部,每行按照\r\t提取

  半同步/半异步线程池:threadpool.h

  1. #ifndef THREADPOOL_H  
  2. #define THREADPOOL_H  
  3.   
  4. #include <list>  
  5. #include <cstdio>  
  6. #include <exception>  
  7. #include <pthread.h>  
  8. #include "locker.h"//简单封装了互斥量和信号量的接口  
  9.   
  10. templatetypename T >  
  11. class threadpool//线程池类模板参数T是任务类型,T中必须有接口process  
  12. {  
  13. public:  
  14.     threadpool( int thread_number = 8, int max_requests = 10000 );//线程数目和最大连接处理数  
  15.     ~threadpool();  
  16.     bool append( T* request );  
  17.   
  18. private:  
  19.     static void* worker( void* arg );//线程工作函数  
  20.     void run();//启动线程池  
  21.   
  22. private:  
  23.     int m_thread_number;//线程数量  
  24.     int m_max_requests;//最大连接数目  
  25.     pthread_t* m_threads;//线程id  
  26.     std::list< T* > m_workqueue;//工作队列:各线程竞争该队列并处理相应的任务逻辑T  
  27.     locker m_queuelocker;//工作队列互斥量  
  28.     sem m_queuestat;//信号量:用于工作队列  
  29.     bool m_stop;//终止标志  
  30. };  
  31.   
  32. templatetypename T >  
  33. threadpool< T >::threadpool( int thread_number, int max_requests ) :   
  34.         m_thread_number( thread_number ), m_max_requests( max_requests ), m_stop( false ), m_threads( NULL )  
  35. {  
  36.     if( ( thread_number <= 0 ) || ( max_requests <= 0 ) )  
  37.     {  
  38.         throw std::exception();  
  39.     }  
  40.   
  41.     m_threads = new pthread_t[ m_thread_number ];//工作线程数组  
  42.     if( ! m_threads )  
  43.     {  
  44.         throw std::exception();  
  45.     }  
  46.   
  47.     for ( int i = 0; i < thread_number; ++i )//创建工作线程  
  48.     {  
  49.         printf( "create the %dth thread\n", i );  
  50.         if( pthread_create( m_threads + i, NULL, worker, this ) != 0 )//注意C++调用pthread_create函数的第三个参数必须是一个静态函数,一个静态成员使用动态成员的方式:通过类静态对象、将类对象作为参数传给静态函数。这里使用了后者所以有this  
  51.         {  
  52.             delete [] m_threads;  
  53.             throw std::exception();  
  54.         }  
  55.         if( pthread_detach( m_threads[i] ) )//线程分离后其它线程无法pthread_join等待  
  56.         {  
  57.             delete [] m_threads;  
  58.             throw std::exception();  
  59.         }  
  60.     }  
  61. }  
  62.   
  63. templatetypename T >  
  64. threadpool< T >::~threadpool()  
  65. {  
  66.     delete [] m_threads;  
  67.     m_stop = true;  
  68. }  
  69.   
  70. templatetypename T >  
  71. bool threadpool< T >::append( T* request )//向工作队列添加任务T  
  72. {  
  73.     m_queuelocker.lock();//非原子操作需要互斥量保护  
  74.     if ( m_workqueue.size() > m_max_requests )//任务队列满了  
  75.     {  
  76.         m_queuelocker.unlock();  
  77.         return false;  
  78.     }  
  79.     m_workqueue.push_back( request );  
  80.     m_queuelocker.unlock();  
  81.     m_queuestat.post();//信号量的V操作,即信号量+1多了个工作任务T  
  82.     return true;  
  83. }  
  84.   
  85. templatetypename T >  
  86. void* threadpool< T >::worker( void* arg )//工作线程函数  
  87. {  
  88.     threadpool* pool = ( threadpool* )arg;//获取进程池对象  
  89.     pool->run();//调用线程池run函数  
  90.     return pool;  
  91. }  
  92.   
  93. templatetypename T >  
  94. void threadpool< T >::run()//工作线程真正工作逻辑:从任务队列领取任务T并执行任务T  
  95. {  
  96.     while ( ! m_stop )  
  97.     {  
  98.         m_queuestat.wait();//信号量P操作,申请信号量获取任务T  
  99.         m_queuelocker.lock();//互斥量保护任务队列,和前面的信号量顺序不能呼唤。。。你懂的  
  100.         if ( m_workqueue.empty() )  
  101.         {  
  102.             m_queuelocker.unlock();//任务队列空那就没任务呗  
  103.             continue;  
  104.         }  
  105.         T* request = m_workqueue.front();//获取任务T  
  106.         m_workqueue.pop_front();  
  107.         m_queuelocker.unlock();  
  108.         if ( ! request )  
  109.         {  
  110.             continue;  
  111.         }  
  112.         request->process();//执行任务T的相应逻辑,任务T中必须有process接口  
  113.     }  
  114. }  
  115.   
  116. #endif  


HTTP请求任务T:http_conn.h接收到一个HTTP请求后解析该请求,然后作出应答

  1. #ifndef HTTPCONNECTION_H  
  2. #define HTTPCONNECTION_H  
  3.   
  4. #include <unistd.h>  
  5. #include <signal.h>  
  6. #include <sys/types.h>  
  7. #include <sys/epoll.h>  
  8. #include <fcntl.h>  
  9. #include <sys/socket.h>  
  10. #include <netinet/in.h>  
  11. #include <arpa/inet.h>  
  12. #include <assert.h>  
  13. #include <sys/stat.h>  
  14. #include <string.h>  
  15. #include <pthread.h>  
  16. #include <stdio.h>  
  17. #include <stdlib.h>  
  18. #include <sys/mman.h>  
  19. #include <stdarg.h>  
  20. #include <errno.h>  
  21. #include "locker.h"//互斥量和信号量的简单封装  
  22.   
  23. class http_conn//HTTP连接任务类型,用于线程池工作队列的任务类型T  
  24. {  
  25. public:  
  26.     static const int FILENAME_LEN = 200;//文件名最大长度,文件是HTTP请求的资源页文件  
  27.     static const int READ_BUFFER_SIZE = 2048;//读缓冲区,用于读取HTTP请求  
  28.     static const int WRITE_BUFFER_SIZE = 1024;//写缓冲区,用于HTTP回答  
  29.     enum METHOD { GET = 0, POST, HEAD, PUT, DELETE, TRACE, OPTIONS, CONNECT, PATCH };//HTTP请求方法,本程序只定义了GET逻辑  
  30.     enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT };//HTTP请求状态:正在解析请求行、正在解析头部、解析中  
  31.     enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, NO_RESOURCE, FORBIDDEN_REQUEST, FILE_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };//HTTP请求结果:未完整的请求(客户端仍需要提交请求)、完整的请求、错误请求...只用了前三个  
  32.     enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };//HTTP每行解析状态:改行解析完毕、错误的行、正在解析行  
  33.   
  34. public:  
  35.     http_conn(){}  
  36.     ~http_conn(){}  
  37.   
  38. public:  
  39.     void init( int sockfd, const sockaddr_in& addr );//初始化新的HTTP连接  
  40.     void close_conn( bool real_close = true );  
  41.     void process();//处理客户请求  
  42.     bool read();//读取客户发送来的数据(HTTP请求)  
  43.     bool write();//将请求结果返回给客户端  
  44.   
  45. private:  
  46.     void init();//重载init初始化连接,用于内部调用  
  47.     HTTP_CODE process_read();//解析HTTP请求,内部调用parse_系列函数  
  48.     bool process_write( HTTP_CODE ret );//填充HTTP应答,通常是将客户请求的资源页发送给客户,内部调用add_系列函数  
  49.   
  50.     HTTP_CODE parse_request_line( char* text );//解析HTTP请求的请求行  
  51.     HTTP_CODE parse_headers( char* text );//解析HTTP头部数据  
  52.     HTTP_CODE parse_content( char* text );//获取解析结果  
  53.     HTTP_CODE do_request();//处理HTTP连接:内部调用process_read(),process_write()  
  54.     char* get_line() { return m_read_buf + m_start_line; }//获取HTTP请求数据中的一行数据  
  55.     LINE_STATUS parse_line();//解析行内部调用parse_request_line和parse_headers  
  56.     //下面的函数被process_write填充HTTP应答     
  57.     void unmap();//解除内存映射,这里内存映射是指将客户请求的资源页文件映射通过mmap映射到内存  
  58.     bool add_response( const char* format, ... );  
  59.     bool add_content( const char* content );  
  60.     bool add_status_line( int status, const char* title );  
  61.     bool add_headers( int content_length );  
  62.     bool add_content_length( int content_length );  
  63.     bool add_linger();  
  64.     bool add_blank_line();  
  65.   
  66. public:  
  67.     static int m_epollfd;//所有socket上的事件都注册到一个epoll事件表中所以用static  
  68.     static int m_user_count;//用户数量  
  69.   
  70. private:  
  71.     int m_sockfd;//HTTP连接对应的客户在服务端的描述符m_sockfd和地址m_address  
  72.     sockaddr_in m_address;  
  73.   
  74.     char m_read_buf[ READ_BUFFER_SIZE ];//读缓冲区,读取HTTP请求  
  75.     int m_read_idx;//已读入的客户数据最后一个字节的下一个位置,即未读数据的第一个位置  
  76.     int m_checked_idx;//当前已经解析的字节(HTTP请求需要逐个解析)  
  77.     int m_start_line;//当前解析行的起始位置  
  78.     char m_write_buf[ WRITE_BUFFER_SIZE ];//写缓冲区  
  79.     int m_write_idx;//写缓冲区待发送的数据  
  80.   
  81.     CHECK_STATE m_check_state;//HTTP解析的状态:请求行解析、头部解析  
  82.     METHOD m_method;//HTTP请求方法,只实现了GET  
  83.   
  84.     char m_real_file[ FILENAME_LEN ];//HTTP请求的资源页对应的文件名称,和服务端的路径拼接就形成了资源页的路径  
  85.     char* m_url;//请求的具体资源页名称,如:www.baidu.com/index.html  
  86.     char* m_version;//HTTP协议版本号,一般是:HTTP/1.1  
  87.     char* m_host;//主机名,客户端要在HTTP请求中的目的主机名  
  88.     int m_content_length;//HTTP消息体的长度,简单的GET请求这个为空  
  89.     bool m_linger;//HTTP请求是否保持连接  
  90.   
  91.     char* m_file_address;//资源页文件内存映射后的地址  
  92.     struct stat m_file_stat;//资源页文件的状态,stat文件结构体  
  93.     struct iovec m_iv[2];//调用writev集中写函数需要m_iv_count表示被写内存块的数量,iovec结构体存放了一段内存的起始位置和长度,  
  94.     int m_iv_count;//m_iv_count是指iovec结构体数组的长度即多少个内存块  
  95. };  
  96.   
  97. #endif  

任务T:http_conn.cpp

  1. #include "http_conn.h"  
  2. //定义了HTTP请求的返回状态信息,类似大家都熟悉的404  
  3. const char* ok_200_title = "OK";  
  4. const char* error_400_title = "Bad Request";  
  5. const char* error_400_form = "Your request has bad syntax or is inherently impossible to satisfy.\n";  
  6. const char* error_403_title = "Forbidden";  
  7. const char* error_403_form = "You do not have permission to get file from this server.\n";  
  8. const char* error_404_title = "Not Found";  
  9. const char* error_404_form = "The requested file was not found on this server.\n";  
  10. const char* error_500_title = "Internal Error";  
  11. const char* error_500_form = "There was an unusual problem serving the requested file.\n";  
  12. const char* doc_root = "/var/www/html";//服务端资源页的路径,将其和HTTP请求中解析的m_url拼接形成资源页的位置  
  13.   
  14. int setnonblocking( int fd )//将fd设置为非阻塞  
  15. {  
  16.     int old_option = fcntl( fd, F_GETFL );  
  17.     int new_option = old_option | O_NONBLOCK;  
  18.     fcntl( fd, F_SETFL, new_option );  
  19.     return old_option;  
  20. }  
  21.   
  22. void addfd( int epollfd, int fd, bool one_shot )//将fd添加到事件表epollfd  
  23. {  
  24.     epoll_event event;  
  25.     event.data.fd = fd;  
  26.     event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;  
  27.     if( one_shot )//采用EPOLLONESHOT事件避免了同一事件被多次触发,因为一个事件只被触发一次且需要重置事件才能侦听下次是否发生  
  28.     {  
  29.         event.events |= EPOLLONESHOT;  
  30.     }  
  31.     epoll_ctl( epollfd, EPOLL_CTL_ADD, fd, &event );  
  32.     setnonblocking( fd );  
  33. }  
  34.   
  35. void removefd( int epollfd, int fd )//将fd从事件表epollfd中移除  
  36. {  
  37.     epoll_ctl( epollfd, EPOLL_CTL_DEL, fd, 0 );  
  38.     close( fd );  
  39. }  
  40.   
  41. void modfd( int epollfd, int fd, int ev )//EPOLLONESHOT需要重置事件后事件才能进行下次侦听  
  42. {  
  43.     epoll_event event;  
  44.     event.data.fd = fd;  
  45.     event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;  
  46.     epoll_ctl( epollfd, EPOLL_CTL_MOD, fd, &event );//注意是EPOLL_CTL_MOD修改  
  47. }  
  48.   
  49. int http_conn::m_user_count = 0;//连接数  
  50. int http_conn::m_epollfd = -1;//事件表,注意是static故所有http_con类对象共享一个事件表  
  51.   
  52. void http_conn::close_conn( bool real_close )//关闭连接,从事件表中移除描述符  
  53. {  
  54.     if( real_close && ( m_sockfd != -1 ) )//m_sockfd是该HTTP连接对应的描述符  
  55.     {  
  56.         //modfd( m_epollfd, m_sockfd, EPOLLIN );  
  57.         removefd( m_epollfd, m_sockfd );  
  58.         m_sockfd = -1;  
  59.         m_user_count--;  
  60.     }  
  61. }  
  62.   
  63. void http_conn::init( int sockfd, const sockaddr_in& addr )//初始化连接  
  64. {  
  65.     m_sockfd = sockfd;//sockfd是http连接对应的描述符用于接收http请求和http回答  
  66.     m_address = addr;//客户端地址  
  67.     int error = 0;  
  68.     socklen_t len = sizeof( error );  
  69.     getsockopt( m_sockfd, SOL_SOCKET, SO_ERROR, &error, &len );  
  70.     int reuse = 1;  
  71.     setsockopt( m_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof( reuse ) );//获取描述符状态,可以在调试时用  
  72.     addfd( m_epollfd, sockfd, true );  
  73.     m_user_count++;//多了一个http用户  
  74.   
  75.     init();//调用重载函数  
  76. }  
  77.   
  78. void http_conn::init()//重载init函数进行些连接前的初始化操作  
  79. {  
  80.     m_check_state = CHECK_STATE_REQUESTLINE;  
  81.     m_linger = false;  
  82.   
  83.     m_method = GET;  
  84.     m_url = 0;  
  85.     m_version = 0;  
  86.     m_content_length = 0;  
  87.     m_host = 0;  
  88.     m_start_line = 0;  
  89.     m_checked_idx = 0;  
  90.     m_read_idx = 0;  
  91.     m_write_idx = 0;  
  92.     memset( m_read_buf, '\0', READ_BUFFER_SIZE );  
  93.     memset( m_write_buf, '\0', WRITE_BUFFER_SIZE );  
  94.     memset( m_real_file, '\0', FILENAME_LEN );  
  95. }  
  96.   
  97. http_conn::LINE_STATUS http_conn::parse_line()//解析HTTP数据:将HTTP数据的每行数据提取出来,每行以回车\r和换行符\n结束  
  98. {  
  99.     char temp;  
  100.     for ( ; m_checked_idx < m_read_idx; ++m_checked_idx )//m_checked_idx是当前正在解析的字节,m_read_idx是读缓冲区中已有的数据(客户端发送了多少HTTP请求数据到来),解析到m_read_idx号字节  
  101.     {  
  102.         temp = m_read_buf[ m_checked_idx ];//当前解析字节  
  103.         if ( temp == '\r' )//若为回车符:  
  104.         {  
  105.             if ( ( m_checked_idx + 1 ) == m_read_idx )//若为回车符:若此回车符是已读取数据的最后一个则仍需要解析改行(即该行数据还没有接收完整)  
  106.             {  
  107.                 return LINE_OPEN;  
  108.             }  
  109.             else if ( m_read_buf[ m_checked_idx + 1 ] == '\n' )//若回车符的下一个是换行符\r则表明该行解析完毕(回车+换行是HTTP请求每行固定结束规则)  
  110.             {  
  111.                 m_read_buf[ m_checked_idx++ ] = '\0';//将该行数据送给缓冲区  
  112.                 m_read_buf[ m_checked_idx++ ] = '\0';  
  113.                 return LINE_OK;//返回状态:该行解析成功  
  114.             }  
  115.   
  116.             return LINE_BAD;//否则解析失败  
  117.         }  
  118.         else if( temp == '\n' )//解析的字符是换行符则前一个必须是回车才解析成功  
  119.         {  
  120.             if( ( m_checked_idx > 1 ) && ( m_read_buf[ m_checked_idx - 1 ] == '\r' ) )  
  121.             {  
  122.                 m_read_buf[ m_checked_idx-1 ] = '\0';  
  123.                 m_read_buf[ m_checked_idx++ ] = '\0';  
  124.                 return LINE_OK;  
  125.             }  
  126.             return LINE_BAD;  
  127.         }  
  128.     }  
  129.   
  130.     return LINE_OPEN;//正在解析,还有HTTP请求数据没有接收到....  
  131. }  
  132.   
  133. bool http_conn::read()//读取HTTP请求数据  
  134. {  
  135.     if( m_read_idx >= READ_BUFFER_SIZE )//读缓冲区已满  
  136.     {  
  137.         return false;  
  138.     }  
  139.   
  140.     int bytes_read = 0;//记录接收的字节数  
  141.     whiletrue )//循环读取的原因是EPOLLONESHOT一个事件只触发一次所以需要一次性读取完全否则数据丢失  
  142.     {  
  143.         bytes_read = recv( m_sockfd, m_read_buf + m_read_idx, READ_BUFFER_SIZE - m_read_idx, 0 );//接收客户端的HTTP请求  
  144.         if ( bytes_read == -1 )  
  145.         {  
  146.             if( errno == EAGAIN || errno == EWOULDBLOCK )//非阻塞描述符这两个errno不是网络出错而是设备当前不可得,在这里就是一次事件的数据读取完毕  
  147.             {  
  148.                 break;  
  149.             }  
  150.             return false;//否则recv出错  
  151.         }  
  152.         else if ( bytes_read == 0 )//客户端关闭了连接  
  153.         {  
  154.             return false;  
  155.         }  
  156.   
  157.         m_read_idx += bytes_read;//更新读缓冲区的已读大小(用于解析行函数)  
  158.     }  
  159.     return true;  
  160. }  
  161.   
  162. http_conn::HTTP_CODE http_conn::parse_request_line( char* text )//解析HTTP的请求行部分  
  163. {  
  164.     m_url = strpbrk( text, " \t" );//在text搜索\t的位置  
  165.     if ( ! m_url )  
  166.     {  
  167.         return BAD_REQUEST;  
  168.     }  
  169.     *m_url++ = '\0';  
  170.   
  171.     char* method = text;  
  172.     if ( strcasecmp( method, "GET" ) == 0 )//忽略大小写比较mehtod和GET的大小返回值和strcmp定义相同  
  173.     {  
  174.         m_method = GET;  
  175.     }  
  176.     else  
  177.     {  
  178.         return BAD_REQUEST;  
  179.     }  
  180.   
  181.     m_url += strspn( m_url, " \t" );//strspn函数是在m_url找到第一个\t位置,拼接资源页文件路径  
  182.     m_version = strpbrk( m_url, " \t" );  
  183.     if ( ! m_version )  
  184.     {  
  185.         return BAD_REQUEST;  
  186.     }  
  187.     *m_version++ = '\0';  
  188.     m_version += strspn( m_version, " \t" );  
  189.     if ( strcasecmp( m_version, "HTTP/1.1" ) != 0 )//HTTP版本  
  190.     {  
  191.         return BAD_REQUEST;  
  192.     }  
  193.   
  194.     if ( strncasecmp( m_url, "http://", 7 ) == 0 )  
  195.     {  
  196.         m_url += 7;  
  197.         m_url = strchr( m_url, '/' );  
  198.     }  
  199.   
  200.     if ( ! m_url || m_url[ 0 ] != '/' )  
  201.     {  
  202.         return BAD_REQUEST;  
  203.     }  
  204.   
  205.     m_check_state = CHECK_STATE_HEADER;//将HTTP解析状态更新为解析头部,那么HTTP解析进入解析HTTP头部。这是有限状态机  
  206.     return NO_REQUEST;  
  207. }  
  208.   
  209. http_conn::HTTP_CODE http_conn::parse_headers( char* text )//解析HTTP头部  
  210. {  
  211.     if( text[ 0 ] == '\0' )  
  212.     {  
  213.         if ( m_method == HEAD )  
  214.         {  
  215.             return GET_REQUEST;//已经获取了一个完整的HTTP请求  
  216.         }  
  217.   
  218.         if ( m_content_length != 0 )//若HTTP请求消息长度不为空  
  219.         {  
  220.             m_check_state = CHECK_STATE_CONTENT;//则解析头部后还要解析消息体,所以HTTP解析状态仍为正在解析中...GET请求不会出现这个...  
  221.             return NO_REQUEST;  
  222.         }  
  223.   
  224.         return GET_REQUEST;  
  225.     }  
  226.     else if ( strncasecmp( text, "Connection:", 11 ) == 0 )  
  227.     {  
  228.         text += 11;  
  229.         text += strspn( text, " \t" );  
  230.         if ( strcasecmp( text, "keep-alive" ) == 0 )  
  231.         {  
  232.             m_linger = true;  
  233.         }  
  234.     }  
  235.     else if ( strncasecmp( text, "Content-Length:", 15 ) == 0 )  
  236.     {  
  237.         text += 15;  
  238.         text += strspn( text, " \t" );  
  239.         m_content_length = atol( text );  
  240.     }  
  241.     else if ( strncasecmp( text, "Host:", 5 ) == 0 )  
  242.     {  
  243.         text += 5;  
  244.         text += strspn( text, " \t" );  
  245.         m_host = text;  
  246.     }  
  247.     else  
  248.     {  
  249.         printf( "oop! unknow header %s\n", text );  
  250.     }  
  251.   
  252.     return NO_REQUEST;  
  253.   
  254. }  
  255.   
  256. http_conn::HTTP_CODE http_conn::parse_content( char* text )//解析结果  
  257. {  
  258.     if ( m_read_idx >= ( m_content_length + m_checked_idx ) )//若解析到缓冲区的最后位置则获得一个一个完整的连接请求  
  259.     {  
  260.         text[ m_content_length ] = '\0';  
  261.         return GET_REQUEST;  
  262.     }  
  263.   
  264.     return NO_REQUEST;//请求不完整  
  265. }  
  266.   
  267. http_conn::HTTP_CODE http_conn::process_read()//完整的HTTP解析  
  268. {  
  269.     LINE_STATUS line_status = LINE_OK;  
  270.     HTTP_CODE ret = NO_REQUEST;  
  271.     char* text = 0;  
  272.   
  273.     while ( ( ( m_check_state == CHECK_STATE_CONTENT ) && ( line_status == LINE_OK  ) )  
  274.                 || ( ( line_status = parse_line() ) == LINE_OK ) ){//满足条件:正在进行HTTP解析、读取一个完整行  
  275.         text = get_line();//从读缓冲区(HTTP请求数据)获取一行数据  
  276.         m_start_line = m_checked_idx;//行的起始位置等于正在每行解析的第一个字节  
  277.         printf( "got 1 http line: %s\n", text );  
  278.   
  279.         switch ( m_check_state )//HTTP解析状态跳转  
  280.         {  
  281.             case CHECK_STATE_REQUESTLINE://正在分析请求行  
  282.             {  
  283.                 ret = parse_request_line( text );//分析请求行  
  284.                 if ( ret == BAD_REQUEST )  
  285.                 {  
  286.                     return BAD_REQUEST;  
  287.                 }  
  288.                 break;  
  289.             }  
  290.             case CHECK_STATE_HEADER://正在分析请求头部  
  291.             {  
  292.                 ret = parse_headers( text );//分析头部  
  293.                 if ( ret == BAD_REQUEST )  
  294.                 {  
  295.                     return BAD_REQUEST;  
  296.                 }  
  297.                 else if ( ret == GET_REQUEST )  
  298.                 {  
  299.                     return do_request();//当获得一个完整的连接请求则调用do_request分析处理资源页文件  
  300.                 }  
  301.                 break;  
  302.             }  
  303.             case CHECK_STATE_CONTENT://HTTP解析状态仍为正在解析...没有办法只好继续解析呗....解析消息体  
  304.             {  
  305.                 ret = parse_content( text );  
  306.                 if ( ret == GET_REQUEST )  
  307.                 {  
  308.                     return do_request();  
  309.                 }  
  310.                 line_status = LINE_OPEN;  
  311.                 break;  
  312.             }  
  313.             default:  
  314.             {  
  315.                 return INTERNAL_ERROR;//内部错误  
  316.             }  
  317.         }  
  318.     }  
  319.   
  320.     return NO_REQUEST;  
  321. }  
  322.   
  323. http_conn::HTTP_CODE http_conn::do_request()//用于获取资源页文件的状态  
  324. {  
  325.     strcpy( m_real_file, doc_root );  
  326.     int len = strlen( doc_root );  
  327.     strncpy( m_real_file + len, m_url, FILENAME_LEN - len - 1 );  
  328.     if ( stat( m_real_file, &m_file_stat ) < 0 )  
  329.     {  
  330.         return NO_RESOURCE;//若资源页不存在则HTTP解析结果为:没有资源...万恶的404  
  331.     }  
  332.   
  333.     if ( ! ( m_file_stat.st_mode & S_IROTH ) )  
  334.     {  
  335.         return FORBIDDEN_REQUEST;//资源没有权限获取  
  336.     }  
  337.   
  338.     if ( S_ISDIR( m_file_stat.st_mode ) )  
  339.     {  
  340.         return BAD_REQUEST;//请求有错  
  341.     }  
  342.   
  343.     int fd = open( m_real_file, O_RDONLY );  
  344.     m_file_address = ( char* )mmap( 0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );//将资源页文件映射到内存  
  345.     close( fd );  
  346.     return FILE_REQUEST;//资源页请求成功  
  347. }  
  348.   
  349. void http_conn::unmap()//解除资源页文件映射的内存  
  350. {  
  351.     if( m_file_address )  
  352.     {  
  353.         munmap( m_file_address, m_file_stat.st_size );//解除映射  
  354.         m_file_address = 0;  
  355.     }  
  356. }  
  357.   
  358. bool http_conn::write()//将资源页文件发送给客户端  
  359. {  
  360.     int temp = 0;  
  361.     int bytes_have_send = 0;  
  362.     int bytes_to_send = m_write_idx;  
  363.     if ( bytes_to_send == 0 )  
  364.     {  
  365.         modfd( m_epollfd, m_sockfd, EPOLLIN );//EPOLLONESHOT事件每次需要重置事件  
  366.         init();  
  367.         return true;  
  368.     }  
  369.   
  370.     while( 1 )//  
  371.     {  
  372.         temp = writev( m_sockfd, m_iv, m_iv_count );//集中写,m_sockfd是http连接对应的描述符,m_iv是iovec结构体数组表示内存块地址,m_iv_count是数组的长度即多少个内存块将一次集中写到m_sockfd  
  373.         if ( temp <= -1 )//集中写失败  
  374.         {  
  375.             if( errno == EAGAIN )  
  376.             {  
  377.                 modfd( m_epollfd, m_sockfd, EPOLLOUT );//重置EPOLLONESHOT事件,注册可写事件表示若m_sockfd没有写失败则关闭连接  
  378.                 return true;  
  379.             }  
  380.             unmap();//解除内存映射  
  381.             return false;  
  382.         }  
  383.   
  384.         bytes_to_send -= temp;//待发送数据  
  385.         bytes_have_send += temp;//已发送数据  
  386.         if ( bytes_to_send <= bytes_have_send )  
  387.         {  
  388.             unmap();//该资源页已经发送完毕该解除映射  
  389.             if( m_linger )//若要保持该http连接  
  390.             {  
  391.                 init();//初始化http连接  
  392.                 modfd( m_epollfd, m_sockfd, EPOLLIN );  
  393.                 return true;  
  394.             }  
  395.             else  
  396.             {  
  397.                 modfd( m_epollfd, m_sockfd, EPOLLIN );  
  398.                 return false;  
  399.             }   
  400.         }  
  401.     }  
  402. }  
  403.   
  404. bool http_conn::add_response( const char* format, ... )//HTTP应答主要是将应答数据添加到写缓冲区m_write_buf  
  405. {  
  406.     if( m_write_idx >= WRITE_BUFFER_SIZE )  
  407.     {  
  408.         return false;  
  409.     }  
  410.     va_list arg_list;  
  411.     va_start( arg_list, format );  
  412.     int len = vsnprintf( m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list );//将fromat内容输出到m_write_buf  
  413.     if( len >= ( WRITE_BUFFER_SIZE - 1 - m_write_idx ) )  
  414.     {  
  415.         return false;  
  416.     }  
  417.     m_write_idx += len;  
  418.     va_end( arg_list );  
  419.     return true;  
  420. }  
  421.   
  422. bool http_conn::add_status_line( int status, const char* title )  
  423. {  
  424.     return add_response( "%s %d %s\r\n""HTTP/1.1", status, title );//  
  425. }  
  426.   
  427. bool http_conn::add_headers( int content_len )  
  428. {  
  429.     add_content_length( content_len );  
  430.     add_linger();  
  431.     add_blank_line();//加空行:回车+换行  
  432. }  
  433.   
  434. bool http_conn::add_content_length( int content_len )  
  435. {  
  436.     return add_response( "Content-Length: %d\r\n", content_len );//  
  437. }  
  438.   
  439. bool http_conn::add_linger()  
  440. {  
  441.     return add_response( "Connection: %s\r\n", ( m_linger == true ) ? "keep-alive" : "close" );//  
  442. }  
  443.   
  444. bool http_conn::add_blank_line()  
  445. {  
  446.     return add_response( "%s""\r\n" );//  
  447. }  
  448.   
  449. bool http_conn::add_content( const char* content )  
  450. {  
  451.     return add_response( "%s", content );  
  452. }  
  453.   
  454. bool http_conn::process_write( HTTP_CODE ret )//填充HTTP应答  
  455. {  
  456.     switch ( ret )  
  457.     {  
  458.         case INTERNAL_ERROR:  
  459.         {  
  460.             add_status_line( 500, error_500_title );  
  461.             add_headers( strlen( error_500_form ) );  
  462.             if ( ! add_content( error_500_form ) )  
  463.             {  
  464.                 return false;  
  465.             }  
  466.             break;  
  467.         }  
  468.         case BAD_REQUEST:  
  469.         {  
  470.             add_status_line( 400, error_400_title );  
  471.             add_headers( strlen( error_400_form ) );  
  472.             if ( ! add_content( error_400_form ) )  
  473.             {  
  474.                 return false;  
  475.             }  
  476.             break;  
  477.         }  
  478.         case NO_RESOURCE:  
  479.         {  
  480.             add_status_line( 404, error_404_title );  
  481.             add_headers( strlen( error_404_form ) );  
  482.             if ( ! add_content( error_404_form ) )  
  483.             {  
  484.                 return false;  
  485.             }  
  486.             break;  
  487.         }  
  488.         case FORBIDDEN_REQUEST:  
  489.         {  
  490.             add_status_line( 403, error_403_title );  
  491.             add_headers( strlen( error_403_form ) );  
  492.             if ( ! add_content( error_403_form ) )  
  493.             {  
  494.                 return false;  
  495.             }  
  496.             break;  
  497.         }  
  498.         case FILE_REQUEST://资源页文件可用  
  499.         {  
  500.             add_status_line( 200, ok_200_title );  
  501.             if ( m_file_stat.st_size != 0 )  
  502.             {  
  503.                 add_headers( m_file_stat.st_size );//m_file_stat资源页文件状态  
  504.                 m_iv[ 0 ].iov_base = m_write_buf;//写缓冲区  
  505.                 m_iv[ 0 ].iov_len = m_write_idx;//长度  
  506.                 m_iv[ 1 ].iov_base = m_file_address;//资源页数据内存映射后在m_file_address地址  
  507.                 m_iv[ 1 ].iov_len = m_file_stat.st_size;//文件长度就是该块内存长度  
  508.                 m_iv_count = 2;  
  509.                 return true;  
  510.             }  
  511.             else  
  512.             {  
  513.                 const char* ok_string = "<html><body></body></html>";//请求页位空白  
  514.                 add_headers( strlen( ok_string ) );  
  515.                 if ( ! add_content( ok_string ) )  
  516.                 {  
  517.                     return false;  
  518.                 }  
  519.             }  
  520.         }  
  521.         default:  
  522.         {  
  523.             return false;  
  524.         }  
  525.     }  
  526.   
  527.     m_iv[ 0 ].iov_base = m_write_buf;  
  528.     m_iv[ 0 ].iov_len = m_write_idx;  
  529.     m_iv_count = 1;  
  530.     return true;  
  531. }  
  532.   
  533. void http_conn::process()//处理HTTP请求  
  534. {  
  535.     HTTP_CODE read_ret = process_read();//读取HTTP请求数据  
  536.     if ( read_ret == NO_REQUEST )  
  537.     {  
  538.         modfd( m_epollfd, m_sockfd, EPOLLIN );  
  539.         return;  
  540.     }  
  541.   
  542.     bool write_ret = process_write( read_ret );//发送资源页给客户端  
  543.     if ( ! write_ret )  
  544.     {  
  545.         close_conn();  
  546.     }  
  547.   
  548.     modfd( m_epollfd, m_sockfd, EPOLLOUT );  
  549. }  

服务端程序:接收HTTP请求并交给线程池处理这些请求

  1. #include <sys/socket.h>  
  2. #include <netinet/in.h>  
  3. #include <arpa/inet.h>  
  4. #include <stdio.h>  
  5. #include <unistd.h>  
  6. #include <errno.h>  
  7. #include <string.h>  
  8. #include <fcntl.h>  
  9. #include <stdlib.h>  
  10. #include <cassert>  
  11. #include <sys/epoll.h>  
  12.   
  13. #include "locker.h"//该头文件封装了信号量和互斥量  
  14. #include "threadpool.h"//半同步/半反应堆线程池  
  15. #include "http_conn.h"//HTTP连接任务类T  
  16.   
  17. #define MAX_FD 65536//最大文件数目  
  18. #define MAX_EVENT_NUMBER 10000//最大事件数目  
  19.   
  20. extern int addfd( int epollfd, int fd, bool one_shot );//采用http_conn.h的addfd函数  
  21. extern int removefd( int epollfd, int fd );//这也是http_conn.h中的函数  
  22.   
  23. void addsig( int sig, void( handler )(int), bool restart = true )//安装信号,用于统一事件源(将信号和IO事件统一监听)  
  24. {  
  25.     struct sigaction sa;  
  26.     memset( &sa, '\0'sizeof( sa ) );  
  27.     sa.sa_handler = handler;  
  28.     if( restart )  
  29.     {  
  30.         sa.sa_flags |= SA_RESTART;  
  31.     }  
  32.     sigfillset( &sa.sa_mask );  
  33.     assert( sigaction( sig, &sa, NULL ) != -1 );  
  34. }  
  35.   
  36. void show_error( int connfd, const char* info )  
  37. {  
  38.     printf( "%s", info );  
  39.     send( connfd, info, strlen( info ), 0 );  
  40.     close( connfd );  
  41. }  
  42.   
  43.   
  44. int main( int argc, char* argv[] )  
  45. {  
  46.     if( argc <= 2 )  
  47.     {  
  48.         printf( "usage: %s ip_address port_number\n", basename( argv[0] ) );  
  49.         return 1;  
  50.     }  
  51.     const char* ip = argv[1];  
  52.     int port = atoi( argv[2] );  
  53.   
  54.     addsig( SIGPIPE, SIG_IGN );  
  55.   
  56.     threadpool< http_conn >* pool = NULL;  
  57.     try  
  58.     {  
  59.         pool = new threadpool< http_conn >;//创建线程池  
  60.     }  
  61.     catch( ... )  
  62.     {  
  63.         return 1;  
  64.     }  
  65.   
  66.     http_conn* users = new http_conn[ MAX_FD ];//创建超大的用户HTTP连接任务数组,给定一个http连接的描述符作为下标即可索引到这个任务,空间换时间  
  67.     assert( users );  
  68.     int user_count = 0;  
  69.   
  70.     int listenfd = socket( PF_INET, SOCK_STREAM, 0 );  
  71.     assert( listenfd >= 0 );  
  72.     struct linger tmp = { 1, 0 };  
  73.     setsockopt( listenfd, SOL_SOCKET, SO_LINGER, &tmp, sizeof( tmp ) );  
  74.   
  75.     int ret = 0;  
  76.     struct sockaddr_in address;  
  77.     bzero( &address, sizeof( address ) );  
  78.     address.sin_family = AF_INET;  
  79.     inet_pton( AF_INET, ip, &address.sin_addr );  
  80.     address.sin_port = htons( port );  
  81.   
  82.     ret = bind( listenfd, ( struct sockaddr* )&address, sizeof( address ) );  
  83.     assert( ret >= 0 );  
  84.   
  85.     ret = listen( listenfd, 5 );  
  86.     assert( ret >= 0 );  
  87.   
  88.     epoll_event events[ MAX_EVENT_NUMBER ];  
  89.     int epollfd = epoll_create( 5 );//创建事件表  
  90.     assert( epollfd != -1 );  
  91.     addfd( epollfd, listenfd, false );//将监听端口添加到事件表,false表示不注册EPOLLONESHOT事件,注意不能将监听端口注册为EPOLLONESHOT事件因为该事件每次发生只触发一次,而accept每次只能连接一个客户,那么多个客户连接请求到来,则必然丢失客户连接请求  
  92.     http_conn::m_epollfd = epollfd;  
  93.   
  94.     whiletrue )  
  95.     {  
  96.         int number = epoll_wait( epollfd, events, MAX_EVENT_NUMBER, -1 );//无限期等待sockfd上的注册事件  
  97.         if ( ( number < 0 ) && ( errno != EINTR ) )//若epoll_wait不是因中断EINTR是出错  
  98.         {  
  99.             printf( "epoll failure\n" );  
  100.             break;  
  101.         }  
  102.   
  103.         for ( int i = 0; i < number; i++ )  
  104.         {  
  105.             int sockfd = events[i].data.fd;//获取就绪事件描述符  
  106.             if( sockfd == listenfd )//监听端口有可读事件则表明有HTTP请求  
  107.             {  
  108.                 struct sockaddr_in client_address;  
  109.                 socklen_t client_addrlength = sizeof( client_address );  
  110.                 int connfd = accept( listenfd, ( struct sockaddr* )&client_address, &client_addrlength );//建立客户连接  
  111.                 if ( connfd < 0 )  
  112.                 {  
  113.                     printf( "errno is: %d\n", errno );  
  114.                     continue;  
  115.                 }  
  116.                 if( http_conn::m_user_count >= MAX_FD )//HTTP客户数超过MAX_FD  
  117.                 {  
  118.                     show_error( connfd, "Internal server busy" );  
  119.                     continue;  
  120.                 }  
  121.                   
  122.                 users[connfd].init( connfd, client_address );//利用connfd快速索引到http_conn任务类  
  123.             }  
  124.             else if( events[i].events & ( EPOLLRDHUP | EPOLLHUP | EPOLLERR ) )  
  125.             {  
  126.                 users[sockfd].close_conn();  
  127.             }  
  128.             else if( events[i].events & EPOLLIN )//数据可读:  
  129.             {  
  130.                 if( users[sockfd].read() )  
  131.                 {  
  132.                     pool->append( users + sockfd );  
  133.                 }  
  134.                 else  
  135.                 {  
  136.                     users[sockfd].close_conn();  
  137.                 }  
  138.             }  
  139.             else if( events[i].events & EPOLLOUT )//数据可写,哪里注册了可写EPOLLOUT事件?http_conn工作任务类中write函数将那个http连接的描述符m_sockfd注册了可写事件  
  140.             {  
  141.                 if( !users[sockfd].write() )//若该http_conn任务对应的http连接写失败了则关闭该http连接  
  142.                 {  
  143.                     users[sockfd].close_conn();  
  144.                 }  
  145.             }  
  146.             else  
  147.             {}  
  148.         }  
  149.     }  
  150.   
  151.     close( epollfd );  
  152.     close( listenfd );//这里要提醒的是listenfd由创建它的函数关闭,谁污染谁治理的原则  
  153.     delete [] users;  
  154.     delete pool;  
  155.     return 0;  
  156. }  


互斥量和信号量的简单封装:locker.h

  1. #ifndef LOCKER_H  
  2. #define LOCKER_H  
  3.   
  4. #include <exception>  
  5. #include <pthread.h>  
  6. #include <semaphore.h>  
  7.   
  8. class sem  
  9. {  
  10. public:  
  11.     sem()  
  12.     {  
  13.         if( sem_init( &m_sem, 0, 0 ) != 0 )  
  14.         {  
  15.             throw std::exception();  
  16.         }  
  17.     }  
  18.     ~sem()  
  19.     {  
  20.         sem_destroy( &m_sem );  
  21.     }  
  22.     bool wait()  
  23.     {  
  24.         return sem_wait( &m_sem ) == 0;  
  25.     }  
  26.     bool post()  
  27.     {  
  28.         return sem_post( &m_sem ) == 0;  
  29.     }  
  30.   
  31. private:  
  32.     sem_t m_sem;  
  33. };  
  34.   
  35. class locker  
  36. {  
  37. public:  
  38.     locker()  
  39.     {  
  40.         if( pthread_mutex_init( &m_mutex, NULL ) != 0 )  
  41.         {  
  42.             throw std::exception();  
  43.         }  
  44.     }  
  45.     ~locker()  
  46.     {  
  47.         pthread_mutex_destroy( &m_mutex );  
  48.     }  
  49.     bool lock()  
  50.     {  
  51.         return pthread_mutex_lock( &m_mutex ) == 0;  
  52.     }  
  53.     bool unlock()  
  54.     {  
  55.         return pthread_mutex_unlock( &m_mutex ) == 0;  
  56.     }  
  57.   
  58. private:  
  59.     pthread_mutex_t m_mutex;  
  60. };  
  61.   
  62. class cond  
  63. {  
  64. public:  
  65.     cond()  
  66.     {  
  67.         if( pthread_mutex_init( &m_mutex, NULL ) != 0 )  
  68.         {  
  69.             throw std::exception();  
  70.         }  
  71.         if ( pthread_cond_init( &m_cond, NULL ) != 0 )  
  72.         {  
  73.             pthread_mutex_destroy( &m_mutex );  
  74.             throw std::exception();  
  75.         }  
  76.     }  
  77.     ~cond()  
  78.     {  
  79.         pthread_mutex_destroy( &m_mutex );  
  80.         pthread_cond_destroy( &m_cond );  
  81.     }  
  82.     bool wait()  
  83.     {  
  84.         int ret = 0;  
  85.         pthread_mutex_lock( &m_mutex );  
  86.         ret = pthread_cond_wait( &m_cond, &m_mutex );  
  87.         pthread_mutex_unlock( &m_mutex );  
  88.         return ret == 0;  
  89.     }  
  90.     bool signal()  
  91.     {  
  92.         return pthread_cond_signal( &m_cond ) == 0;  
  93.     }  
  94.   
  95. private:  
  96.     pthread_mutex_t m_mutex;  
  97.     pthread_cond_t m_cond;  
  98. };  
  99.   
  100. #endif  


压力测试程序:通常有IO复用、多线程、多进程实现压力测试,其中IO复用施压程度最高其它的要切换CPU

本程序是客户端通过命令行参数指定num个客户连接,并向服务端发送HTTP请求,服务端HTTP应答,客户端输出是请求和应答数据交替出现

  1. #include <stdlib.h>  
  2. #include <stdio.h>  
  3. #include <assert.h>  
  4. #include <unistd.h>  
  5. #include <sys/types.h>  
  6. #include <sys/epoll.h>  
  7. #include <fcntl.h>  
  8. #include <sys/socket.h>  
  9. #include <netinet/in.h>  
  10. #include <arpa/inet.h>  
  11. #include <string.h>  
  12. //向服务器发送HTTP请求内容  
  13. static const char* request = "GET http://localhost/index.html HTTP/1.1\r\nConnection: keep-alive\r\n\r\nxxxxxxxxxxxx";  
  14.   
  15. int setnonblocking( int fd )//设置非阻塞描述符  
  16. {  
  17.     int old_option = fcntl( fd, F_GETFL );  
  18.     int new_option = old_option | O_NONBLOCK;  
  19.     fcntl( fd, F_SETFL, new_option );  
  20.     return old_option;  
  21. }  
  22.   
  23. void addfd( int epoll_fd, int fd )//添加描述符到事件表  
  24. {  
  25.     epoll_event event;  
  26.     event.data.fd = fd;  
  27.     event.events = EPOLLOUT | EPOLLET | EPOLLERR;//可写事件  
  28.     epoll_ctl( epoll_fd, EPOLL_CTL_ADD, fd, &event );  
  29.     setnonblocking( fd );  
  30. }  
  31.   
  32. bool write_nbytes( int sockfd, const char* buffer, int len )//向服务器写函数即发送HTTP请求  
  33. {  
  34.     int bytes_write = 0;  
  35.     printf( "write out %d bytes to socket %d\n", len, sockfd );  
  36.     while( 1 ) //循环写直至写完一次buffer也就是HTTP requst  
  37.     {     
  38.         bytes_write = send( sockfd, buffer, len, 0 );  
  39.         if ( bytes_write == -1 )  
  40.         {     
  41.             return false;  
  42.         }     
  43.         else if ( bytes_write == 0 )   
  44.         {     
  45.             return false;  
  46.         }     
  47.   
  48.         len -= bytes_write;  
  49.         buffer = buffer + bytes_write;  
  50.         if ( len <= 0 )   
  51.         {     
  52.             return true;  
  53.         }     
  54.     }     
  55. }  
  56.   
  57. bool read_once( int sockfd, char* buffer, int len )//读一次,接收服务器发送来的HTTP应答  
  58. {  
  59.     int bytes_read = 0;  
  60.     memset( buffer, '\0', len );  
  61.     bytes_read = recv( sockfd, buffer, len, 0 );  
  62.     if ( bytes_read == -1 )  
  63.     {  
  64.         return false;  
  65.     }  
  66.     else if ( bytes_read == 0 )  
  67.     {  
  68.         return false;  
  69.     }  
  70.     printf( "read in %d bytes from socket %d with content: %s\n", bytes_read, sockfd, buffer );  
  71.   
  72.     return true;  
  73. }  
  74.   
  75. void start_conn( int epoll_fd, int num, const char* ip, int port )//发起num个连接  
  76. {  
  77.     int ret = 0;  
  78.     struct sockaddr_in address;  
  79.     bzero( &address, sizeof( address ) );  
  80.     address.sin_family = AF_INET;  
  81.     inet_pton( AF_INET, ip, &address.sin_addr );  
  82.     address.sin_port = htons( port );  
  83.   
  84.     for ( int i = 0; i < num; ++i )  
  85.     {  
  86.         sleep( 1 );  
  87.         int sockfd = socket( PF_INET, SOCK_STREAM, 0 );  
  88.         printf( "create 1 sock\n" );  
  89.         if( sockfd < 0 )  
  90.         {  
  91.             continue;  
  92.         }  
  93.   
  94.         if (  connect( sockfd, ( struct sockaddr* )&address, sizeof( address ) ) == 0  )  
  95.         {  
  96.             printf( "build connection %d\n", i );  
  97.             addfd( epoll_fd, sockfd );//初始注册为可写事件  
  98.         }  
  99.     }  
  100. }  
  101.   
  102. void close_conn( int epoll_fd, int sockfd )//关闭连接  
  103. {  
  104.     epoll_ctl( epoll_fd, EPOLL_CTL_DEL, sockfd, 0 );  
  105.     close( sockfd );  
  106. }  
  107.   
  108. int main( int argc, char* argv[] )  
  109. {  
  110.     assert( argc == 4 );  
  111.     int epoll_fd = epoll_create( 100 );  
  112.     start_conn( epoll_fd, atoi( argv[ 3 ] ), argv[1], atoi( argv[2] ) );  
  113.     epoll_event events[ 10000 ];  
  114.     char buffer[ 2048 ];  
  115.     while ( 1 )  
  116.     {  
  117.         int fds = epoll_wait( epoll_fd, events, 10000, 2000 );//2000ms内等待最多10000个事件  
  118.         for ( int i = 0; i < fds; i++ )  
  119.         {     
  120.             int sockfd = events[i].data.fd;  
  121.             if ( events[i].events & EPOLLIN )//HTTP连接上可读事件即服务端发送给客户端HTTP回答报文  
  122.             {     
  123.                 if ( ! read_once( sockfd, buffer, 2048 ) )//读取HTTP应答  
  124.                 {  
  125.                     close_conn( epoll_fd, sockfd );  
  126.                 }  
  127.                 struct epoll_event event;  
  128.                 event.events = EPOLLOUT | EPOLLET | EPOLLERR;//更改为可写事件  
  129.                 event.data.fd = sockfd;  
  130.                 epoll_ctl( epoll_fd, EPOLL_CTL_MOD, sockfd, &event );//  
  131.             }  
  132.             else if( events[i].events & EPOLLOUT ) //可写事件初始就是可写  
  133.             {  
  134.                 if ( ! write_nbytes( sockfd, request, strlen( request ) ) )//向服务端发送HTTP请求  
  135.                 {  
  136.                     close_conn( epoll_fd, sockfd );  
  137.                 }  
  138.                 struct epoll_event event;  
  139.                 event.events = EPOLLIN | EPOLLET | EPOLLERR;//更改为可写事件  
  140.                 event.data.fd = sockfd;  
  141.                 epoll_ctl( epoll_fd, EPOLL_CTL_MOD, sockfd, &event );//这样做的目的是客户端发送HTTP请求和服务端HTTP回答交替出现  
  142.             }  
  143.             else if( events[i].events & EPOLLERR )  
  144.             {  
  145.                 close_conn( epoll_fd, sockfd );  
  146.             }  
  147.         }  
  148.     }  
  149. }