(二十二)深入淺出TCPIP之實戰篇—用c++開發一個http服務器

在當前的網絡編程專欄前十幾篇文章里,我已經說明了TCPIP常用的一些原理,那么接下來我將逐步進入到實戰編程階段:

本篇文章我將帶大家用C++做一個http服務器。既然想實現一個http服務器,首先必須要熟悉的就是http協議知識,然后在選擇具體的模塊來完成實現。下面先了解一些http協議知識,然后我們再一步一步來實現它。

http服務器

http協議知識

一、 網絡通信簡介

傳輸層及其以下的機制由內核提供,應用層由用戶進程提供,應用程序對通訊數據的含義進行解釋,而傳輸層及其以下處理通訊的細節,將數據從一臺計算機通過一定的路徑發送到另一臺計算機。應用層數據通過協議棧發到網絡上時,每層協議都要加上一個數據首部(header),稱為封裝(Encapsulation)。

假設現在應用層協議為http,那么其中的Data 可以看作是一個http請求或者應答,Data包含真正的消息正文和app首部(即報頭等)。?

二、HTTP協議詳解之請求篇

http請求由三部分組成,分別是:請求行、消息報頭、請求正文

1、請求行以一個方法符號開頭,以空格分開,后面跟著請求的URI和協議的版本,格式如下:Method Request-URI HTTP-Version CRLF 其中 Method表示請求方法;Request-URI是一個統一資源標識符;HTTP-Version表示請求的HTTP協議版本;CRLF表示回車和換行(除了作為結尾的CRLF外,不允許出現單獨的CR或LF字符)。

請求方法(所有方法全為大寫)有多種,各個方法的解釋如下:GET 請求獲取Request-URI所標識的資源 POST 在Request-URI所標識的資源后附加新的數據 HEAD 請求獲取由Request-URI所標識的資源的響應消息報頭 PUT 請求服務器存儲一個資源,并用Request-URI作為其標識 DELETE 請求服務器刪除Request-URI所標識的資源 TRACE 請求服務器回送收到的請求信息,主要用于測試或診斷 CONNECT 保留將來使用 OPTIONS 請求查詢服務器的性能,或者查詢與資源相關的選項和需求

2、請求報頭后述 3、請求正文(略)

三、HTTP協議詳解之響應篇

在接收和解釋請求消息后,服務器返回一個HTTP響應消息。

HTTP響應也是由三個部分組成,分別是:狀態行、消息報頭、響應正文 1、狀態行格式如下:HTTP-Version Status-Code Reason-Phrase CRLF 其中,HTTP-Version表示服務器HTTP協議的版本;Status-Code表示服務器發回的響應狀態代碼;Reason-Phrase表示狀態代碼的文本描述。

狀態代碼由三位數字組成,第一個數字定義了響應的類別,且有五種可能取值:1xx:指示信息--表示請求已接收,繼續處理 2xx:成功--表示請求已被成功接收、理解、接受 3xx:重定向--要完成請求必須進行更進一步的操作 4xx:客戶端錯誤--請求有語法錯誤或請求無法實現 5xx:服務器端錯誤--服務器未能實現合法的請求

2、響應報頭后述

3、響應正文就是服務器返回的資源的內容

四、HTTP協議詳解之消息報頭篇

HTTP消息由客戶端到服務器的請求和服務器到客戶端的響應組成。請求消息和響應消息都是由開始行(對于請求消息,開始行就是請求行,對于響應消息,開始行就是狀態行),消息報頭(可選),空行(只有CRLF的行),消息正文(可選)組成。

HTTP消息報頭包括普通報頭、請求報頭、響應報頭、實體報頭。每一個報頭域都是由名字+“:”+空格+值 組成,消息報頭域的名字是大小寫無關的。

1、普通報頭 在普通報頭中,有少數報頭域用于所有的請求和響應消息,但并不用于被傳輸的實體,只用于傳輸的消息。Cache-Control 用于指定緩存指令,緩存指令是單向的(響應中出現的緩存指令在請求中未必會出現),且是獨立的(一個消息的緩存指令不會影響另一個消息處理的緩存機制),HTTP1.0使用的類似的報頭域為Pragma。請求時的緩存指令包括:no-cache(用于指示請求或響應消息不能緩存)、no-store、max-age、max-stale、min-fresh、only-if-cached; 響應時的緩存指令包括:public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age、s-maxage.

Date普通報頭域表示消息產生的日期和時間

Connection普通報頭域允許發送指定連接的選項。

2、請求報頭 請求報頭允許客戶端向服務器端傳遞請求的附加信息以及客戶端自身的信息。常用的請求報頭 Accept Accept-Charset Accept-Encoding Accept-Language Authorization Host(發送請求時,該報頭域是必需的) User-Agent

3、響應報頭 響應報頭允許服務器傳遞不能放在狀態行中的附加響應信息,以及關于服務器的信息和對Request-URI所標識的資源進行下一步訪問的信息。常用的響應報頭 Location Server WWW-Authenticate

4、實體報頭 請求和響應消息都可以傳送一個實體。一個實體由實體報頭域和實體正文組成,但并不是說實體報頭域和實體正文要在一起發送,可以只發送實體報頭域。實體報頭定義了關于實體正文(eg:有無實體正文)和請求所標識的資源的元信息。常用的實體報頭 Content-Encoding Content-Language Content-Length Content-Type Last-Modified Expires GMT

五、HTTP協議相關技術補充

1、基礎:高層協議有:文件傳輸協議FTP、電子郵件傳輸協議SMTP、域名系統服務DNS、網絡新聞傳輸協議NNTP和HTTP協議等 中介由三種:代理(Proxy)、網關(Gateway)和通道(Tunnel),一個代理根據URI的絕對格式來接受請求,重寫全部或部分消息,通過 URI的標識把已格式化過的請求發送到服務器。網關是一個接收代理,作為一些其它服務器的上層,并且如果必須的話,可以把請求翻譯給下層的服務器協議。一 個通道作為不改變消息的兩個連接之間的中繼點。當通訊需要通過一個中介(例如:防火墻等)或者是中介不能識別消息的內容時,通道經常被使用。代理(Proxy):一個中間程序,它可以充當一個服務器,也可以充當一個客戶機,為其它客戶機建立請求。請求是通過可能的翻譯在內部或經過傳遞到其它的 服務器中。一個代理在發送請求信息之前,必須解釋并且如果可能重寫它。代理經常作為通過防火墻的客戶機端的門戶,代理還可以作為一個幫助應用來通過協議處 理沒有被用戶代理完成的請求。網關(Gateway):一個作為其它服務器中間媒介的服務器。與代理不同的是,網關接受請求就好象對被請求的資源來說它就是源服務器;發出請求的客戶機并沒有意識到它在同網關打交道。網關經常作為通過防火墻的服務器端的門戶,網關還可以作為一個協議翻譯器以便存取那些存儲在非HTTP系統中的資源。通道(Tunnel):是作為兩個連接中繼的中介程序。一旦激活,通道便被認為不屬于HTTP通訊,盡管通道可能是被一個HTTP請求初始化的。當被中繼 的連接兩端關閉時,通道便消失。當一個門戶(Portal)必須存在或中介(Intermediary)不能解釋中繼的通訊時通道被經常使用。

2、協議分析的優勢—HTTP分析器檢測網絡攻擊 以模塊化的方式對高層協議進行分析處理,將是未來入侵檢測的方向。HTTP及其代理的常用端口80、3128和8080在network部分用port標簽進行了規定

3、HTTP協議Content Lenth限制漏洞導致拒絕服務攻擊 使用POST方法時,可以設置ContentLenth來定義需要傳送的數據長度,例如ContentLenth:999999999,在傳送完成前,內 存不會釋放,攻擊者可以利用這個缺陷,連續向WEB服務器發送垃圾數據直至WEB服務器內存耗盡。這種攻擊方法基本不會留下痕跡。

4、利用HTTP協議的特性進行拒絕服務攻擊的一些構思 服務器端忙于處理攻擊者偽造的TCP連接請求而無暇理睬客戶的正常請求(畢竟客戶端的正常請求比率非常之小),此時從正常客戶的角度看來,服務器失去響應,這種情況我們稱作:服務器端受到了SYNFlood攻擊(SYN洪水攻擊)。而Smurf、TearDrop等是利用ICMP報文來Flood和IP碎片攻擊的。本文用“正常連接”的方法來產生拒絕服務攻擊。19端口在早期已經有人用來做Chargen攻擊了,即Chargen_Denial_of_Service,但是!他們用的方法是在兩臺Chargen 服務器之間產生UDP連接,讓服務器處理過多信息而DOWN掉,那么,干掉一臺WEB服務器的條件就必須有2個:1.有Chargen服務2.有HTTP 服務 方法:攻擊者偽造源IP給N臺Chargen發送連接請求(Connect),Chargen接收到連接后就會返回每秒72字節的字符流(實際上根據網絡實際情況,這個速度更快)給服務器。

如何實現一個Web服務器:

1.本服務器采用基于線程池和epoll實現的Reactor模式的簡單HTTP服務器。主進程只管理監聽socket,連接socket都由進程池中的worker進行管理。當有新的連接到來時,主進程會通過socketpair創建的套接字和worker進程通信,通知子進程接收新連接。子進程正確接收連接之后,會把該套接字上的讀寫事件注冊到自己的epll內核事件表中。之后該套接字上的任何I/O操作都由被選中的worker來處理,直到客戶關閉連接或超時。

epoll相關可查閱(二十)深入淺出TCPIP之epoll的一些思考

2.每個子進程都是一個reactor,采用epoll和非阻塞I/O實現事件循環。如下圖:

  • a. epoll負責監聽事件的發生,有事件到來將調用相應的事件處理單元進行處理

    • 1). 信號:信號是一種異步事件,信號處理函數和程序的主循環是兩條不同的執行路線,很顯然,信號處理函數需要盡可能的執行完成,以確保信號不被屏蔽(信號是不會排隊的)。一個典型的解決方案是把信號的主要處理邏輯放到事件循環里,當信號處理函數被觸發時只是通過管道將信號通知給主循環接收和處理信號,只需要將和信號處理函數通信的管道的可讀事件添加到epoll里。這樣信號就能和其他I/O事件一樣被處理。

    • 2). 定時器事件。使用timefd,同樣通過監聽timefd上的可讀事件來統一事件源。將其設置為邊沿觸發,不然timefd水平觸發將一直告知該事件。

    • a).忽略SIGPIPE信號(當讀寫一個對端關閉的連接時),將為SIGINT,SIGTERM,SIGCHILD(對父進程來說標識有子進程狀態發生變化,一般是子進程結束)設置信號處理函數。

    • a). 超時將通過連接池回收連接。

    • 1). 通過非阻塞I/o和事件循環來將阻塞進程的方法分解。例如:每次recv新數據時,如果recv返回EAGAIN錯誤,都不會一直循環recv,而是將現有數據先處理,然后記錄當前連接狀態,然后將讀事件接著放到epoll隊列中監聽等待下一個數據到來。因為每次都不會盡可能的將I/O上的數據讀取,所以我采用了水平觸發而不是邊沿觸發。send同理。

    • i. 對一個連接來說,主要監聽的就是讀就緒事件和寫就緒事件。

    • ii. 統一事件源:

  • b. 連接池和線程池的實現:

    • i. 連接池:連接池采用一個數組實現。連接池在構造時傳入最大連接數,初始化為最大連接數個連接,且后續數目不可變。新連接到來時調用http_conn::init,當客戶端有網絡數據發送的時候,將當前連接傳入到線程池里,通過線程池來處理邏輯。

    • ii. 線程池:線程池的實現是通過連接類來完成的。連接類在第一次被初始化時即第一次被使用,將申請相應的定時器,接收和發送緩存。之后將不會將申請的內存銷毀,直到進程結束。通過這樣來降低申請和釋放內存的次數來減少內存碎片以及節約時間。

  • c. 連接:
    每個連接都應該有一個void http_conn::init( int sockfd, const sockaddr_in& addr )函數,此函數會在第一次被調用時分配內存,另外還有void http_conn::process() ,這個函數將根據操作類型,來決定要進行的是讀process_read還是寫操作process_write。同時根據操作結果返回相應的狀態,來決定要給epoll添加什么事件。??

  • f. Http報文請求行和頭部解析:

    • i. 通過狀態機m_check_state 來實現HTTP報文的解析。因為一個請求有可能不是在一個tcp包中到來,所以需要記錄狀態機的狀態,以及上次check到的位置。在解析完HTTP報文后,還需要保存解析的結果,然后根據解析結果,來產生相應response。??

代碼結構

HttpServer

基于線程池和epoll實現的Reactor模式的簡單HTTP服務器

threadpool.h

基于C++實現的線程池類的封裝,采用模板來實現拓展性

locker.h

封裝了信號量、互斥鎖、條件變量的使用

http_conn.h

封裝了HTTP協議的解析方法,比較繁瑣

main.cpp

采用epoll實現了簡單的Reactor模式,主線程復雜接受連接,線程池負責處理任務。】

http_conn.h

#ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H
#include <unistd.h>
#include <signal.h>
#include <sys/types.h>
#include <sys/epoll.h>
#include <sys/uio.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;
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 };
enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, NO_RESOURCE, FORBIDDEN_REQUEST, FILE_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };
enum LINE_STATUS { LINE_OK = 0, 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();
bool process_write( HTTP_CODE ret );
HTTP_CODE parse_request_line( char* text );
HTTP_CODE parse_headers( char* text );
HTTP_CODE parse_content( char* text );
HTTP_CODE do_request();
char* get_line() { return m_read_buf + m_start_line; }
LINE_STATUS parse_line();
void unmap();
bool add_response( const char* format, ... );
bool add_content( const char* content );
bool add_status_line( int status, const char* title );
bool add_headers( int content_length );
bool add_content_length( int content_length );
bool add_linger();
bool add_blank_line();
public:
static int m_epollfd;
static int m_user_count;
private:
int m_sockfd;
sockaddr_in m_address;
char m_read_buf[ READ_BUFFER_SIZE ];
int m_read_idx;
int m_checked_idx;
int m_start_line;
char m_write_buf[ WRITE_BUFFER_SIZE ];
int m_write_idx;
CHECK_STATE m_check_state;
METHOD m_method;
char m_real_file[ FILENAME_LEN ];
char* m_url;
char* m_version;
char* m_host;
int m_content_length;
bool m_linger;
char* m_file_address;
struct stat m_file_stat;
struct iovec m_iv[2];
int m_iv_count;
};
#endif

http_conn.cpp

#include "http_conn.h"const char* ok_200_title = "OK";
const char* error_400_title = "Bad Request";
const char* error_400_form = "Your request has bad syntax or is inherently impossible to satisfy.\n";
const char* error_403_title = "Forbidden";
const char* error_403_form = "You do not have permission to get file from this server.\n";
const char* error_404_title = "Not Found";
const char* error_404_form = "The requested file was not found on this server.\n";
const char* error_500_title = "Internal Error";
const char* error_500_form = "There was an unusual problem serving the requested file.\n";
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_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLET | EPOLLRDHUP;
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 )
{
epoll_event event;
event.data.fd = fd;
event.events = ev | EPOLLET | EPOLLONESHOT | EPOLLRDHUP;
epoll_ctl( 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 ) )
{
//modfd( m_epollfd, m_sockfd, EPOLLIN );
removefd( m_epollfd, m_sockfd );
m_sockfd = -1;
m_user_count--;
}
}void http_conn::init( int sockfd, const sockaddr_in& addr )
{
m_sockfd = sockfd;
m_address = addr;
int error = 0;
socklen_t len = sizeof( error );
getsockopt( m_sockfd, SOL_SOCKET, SO_ERROR, &error, &len );
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 = GET;
m_url = 0;
m_version = 0;
m_content_length = 0;
m_host = 0;
m_start_line = 0;
m_checked_idx = 0;
m_read_idx = 0;
m_write_idx = 0;
memset( m_read_buf, '\0', READ_BUFFER_SIZE );
memset( m_write_buf, '\0', WRITE_BUFFER_SIZE );
memset( m_real_file, '\0', FILENAME_LEN );
}http_conn::LINE_STATUS http_conn::parse_line()
{
char temp;
for ( ; m_checked_idx < m_read_idx; ++m_checked_idx )
{
temp = m_read_buf[ m_checked_idx ];
if ( temp == '\r' )
{
if ( ( m_checked_idx + 1 ) == m_read_idx )
{
return LINE_OPEN;
}
else if ( m_read_buf[ m_checked_idx + 1 ] == '\n' )
{
m_read_buf[ m_checked_idx++ ] = '\0';
m_read_buf[ m_checked_idx++ ] = '\0';
return LINE_OK;
}return LINE_BAD;
}
else if( temp == '\n' )
{
if( ( m_checked_idx > 1 ) && ( m_read_buf[ m_checked_idx - 1 ] == '\r' ) )
{
m_read_buf[ m_checked_idx-1 ] = '\0';
m_read_buf[ m_checked_idx++ ] = '\0';
return LINE_OK;
}
return LINE_BAD;
}
}return LINE_OPEN;
}bool http_conn::read()
{
if( m_read_idx >= READ_BUFFER_SIZE )
{
return false;
}int bytes_read = 0;
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_conn::HTTP_CODE http_conn::parse_request_line( char* text )
{
m_url = strpbrk( text, " \t" );
if ( ! m_url )
{
return BAD_REQUEST;
}
*m_url++ = '\0';char* method = text;
if ( strcasecmp( method, "GET" ) == 0 )
{
m_method = GET;
}
else
{
return BAD_REQUEST;
}m_url += strspn( m_url, " \t" );
m_version = strpbrk( m_url, " \t" );
if ( ! m_version )
{
return BAD_REQUEST;
}
*m_version++ = '\0';
m_version += strspn( m_version, " \t" );
if ( strcasecmp( m_version, "HTTP/1.1" ) != 0 )
{
return BAD_REQUEST;
}if ( strncasecmp( m_url, "http://", 7 ) == 0 )
{
m_url += 7;
m_url = strchr( m_url, '/' );
}if ( ! m_url || m_url[ 0 ] != '/' )
{
return BAD_REQUEST;
}m_check_state = CHECK_STATE_HEADER;
return NO_REQUEST;
}http_conn::HTTP_CODE http_conn::parse_headers( char* text )
{
if( text[ 0 ] == '\0' )
{
if ( m_method == HEAD )
{
return GET_REQUEST;
}if ( m_content_length != 0 )
{
m_check_state = CHECK_STATE_CONTENT;
return NO_REQUEST;
}return GET_REQUEST;
}
else if ( strncasecmp( text, "Connection:", 11 ) == 0 )
{
text += 11;
text += strspn( text, " \t" );
if ( strcasecmp( text, "keep-alive" ) == 0 )
{
m_linger = true;
}
}
else if ( strncasecmp( text, "Content-Length:", 15 ) == 0 )
{
text += 15;
text += strspn( text, " \t" );
m_content_length = atol( text );
}
else if ( strncasecmp( text, "Host:", 5 ) == 0 )
{
text += 5;
text += strspn( text, " \t" );
m_host = text;
}
else
{
printf( "oop! unknow header %s\n", text );
}return NO_REQUEST;}http_conn::HTTP_CODE http_conn::parse_content( char* text )
{
if ( m_read_idx >= ( m_content_length + m_checked_idx ) )
{
text[ m_content_length ] = '\0';
return GET_REQUEST;
}return NO_REQUEST;
}http_conn::HTTP_CODE http_conn::process_read()
{
LINE_STATUS line_status = LINE_OK;
HTTP_CODE ret = NO_REQUEST;
char* text = 0;while ( ( ( m_check_state == CHECK_STATE_CONTENT ) && ( line_status == LINE_OK  ) )
|| ( ( line_status = parse_line() ) == LINE_OK ) )
{
text = get_line();
m_start_line = m_checked_idx;
printf( "got 1 http line: %s\n", text );switch ( m_check_state )
{
case CHECK_STATE_REQUESTLINE:
{
ret = parse_request_line( text );
if ( ret == BAD_REQUEST )
{
return BAD_REQUEST;
}
break;
}
case CHECK_STATE_HEADER:
{
ret = parse_headers( text );
if ( ret == BAD_REQUEST )
{
return BAD_REQUEST;
}
else if ( ret == GET_REQUEST )
{
return do_request();
}
break;
}
case CHECK_STATE_CONTENT:
{
ret = parse_content( text );
if ( ret == GET_REQUEST )
{
return do_request();
}
line_status = LINE_OPEN;
break;
}
default:
{
return INTERNAL_ERROR;
}
}
}return NO_REQUEST;
}http_conn::HTTP_CODE http_conn::do_request()
{
strcpy( m_real_file, doc_root );
int len = strlen( doc_root );
strncpy( m_real_file + len, m_url, FILENAME_LEN - len - 1 );
if ( stat( m_real_file, &m_file_stat ) < 0 )
{
return NO_RESOURCE;
}if ( ! ( m_file_stat.st_mode & S_IROTH ) )
{
return FORBIDDEN_REQUEST;
}if ( S_ISDIR( m_file_stat.st_mode ) )
{
return BAD_REQUEST;
}int fd = open( m_real_file, O_RDONLY );
m_file_address = ( char* )mmap( 0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );
close( fd );
return FILE_REQUEST;
}void http_conn::unmap()
{
if( m_file_address )
{
munmap( m_file_address, m_file_stat.st_size );
m_file_address = 0;
}
}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;
}while( 1 )
{
temp = writev( m_sockfd, m_iv, m_iv_count );
if ( temp <= -1 )
{
if( errno == EAGAIN )
{
modfd( m_epollfd, m_sockfd, EPOLLOUT );
return true;
}
unmap();
return false;
}bytes_to_send -= temp;
bytes_have_send += temp;
if ( bytes_to_send <= bytes_have_send )
{
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\r\n", "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\r\n", content_len );
}bool http_conn::add_linger()
{
return add_response( "Connection: %s\r\n", ( m_linger == true ) ? "keep-alive" : "close" );
}bool http_conn::add_blank_line()
{
return add_response( "%s", "\r\n" );
}bool http_conn::add_content( const char* content )
{
return add_response( "%s", content );
}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 )
{
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( 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;
}void http_conn::process()
{
HTTP_CODE read_ret = process_read();
if ( read_ret == NO_REQUEST )
{
modfd( m_epollfd, m_sockfd, EPOLLIN );
return;
}bool write_ret = process_write( read_ret );
if ( ! write_ret )
{
close_conn();
}modfd( m_epollfd, m_sockfd, EPOLLOUT );
}

?makefile

?

httpserver: main.cpp http_conn.cpp threadpool.hg++ main.cpp http_conn.cpp threadpool.h -o httpserver -pthread
clean:rm httpserver

運行make

執行 ./httpserver 0.0.0.0 80

代碼可在后臺回復 “http服務器”,作者會提供一個url下載鏈接

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/444439.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/444439.shtml
英文地址,請注明出處:http://en.pswp.cn/news/444439.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

C++:19---重載與模板、模板特例化

一、重載與模板 函數模板可以被另一個模板或一個普通非模板函數重載如果涉及函數模板,則函數匹配規則會有以下的約束:如果同樣好的函數中只有一個是非模板函數,則選擇此函數如果同樣好的函數中沒有非模板函數,而有多個函數模板,則其中一個模板比其他模板更特例化,則選擇此…

leetcode159. 至多包含兩個不同字符的最長子串

給定一個字符串 s &#xff0c;找出 至多 包含兩個不同字符的最長子串 t 。 示例 1: 輸入: "eceba" 輸出: 3 解釋: t 是 "ece"&#xff0c;長度為3。 示例 2: 輸入: "ccaabbb" 輸出: 5 解釋: t 是 "aabbb"&#xff0c;長度為5。 思…

C++:17---函數指針

一、格式 指針名前*號,并且將*和指針名用括號括起來例如: //指針名為pf,指向一個返回值為bool,參數為兩個const string&的函數 bool (*pf)(const string&, const string&); //這個不是函數指針,而是一個返回值為bool*的pf函數 bool *pf(const string&, co…

leetcode161. 相隔為 1 的編輯距離

給定兩個字符串 s 和 t&#xff0c;判斷他們的編輯距離是否為 1。 注意&#xff1a; 滿足編輯距離等于 1 有三種可能的情形&#xff1a; 往 s 中插入一個字符得到 t 從 s 中刪除一個字符得到 t 在 s 中替換一個字符得到 t 示例 1&#xff1a; 輸入: s "ab", t …

C語言-- 大端小端詳解

一、什么是大端和小端 所謂的大端模式,就是高位字節排放在內存的低地址端,低位字節排放在內存的高地址端。 所謂的小端模式,就是低位字節排放在內存的低地址端,高位字節排放在內存的高地址端。 簡單來說:大端——高尾端,小端——低尾端 舉個例子,比如數字 0x12 34 56 78…

leetcode164. 最大間距 借桶思想秒掉hard題

給定一個無序的數組&#xff0c;找出數組在排序之后&#xff0c;相鄰元素之間最大的差值。 如果數組元素個數小于 2&#xff0c;則返回 0。 示例 1: 輸入: [3,6,9,1] 輸出: 3 解釋: 排序后的數組是 [1,3,6,9], 其中相鄰元素 (3,6) 和 (6,9) 之間都存在最大差值 3。 示例 2: …

C++:18---函數模板(template)

一、模板的定義 template<typename T>以關鍵字template開頭,后面跟一個模板參數列表,列表里面用逗號將多個模板參數隔開定義的注意事項模板的編譯 當編譯器遇到一個模板定義時,并不生成代碼。只有當實例化處模板的一個特定版本時,編譯器才會生成代碼重點:通常,當我…

leetcode167. 兩數之和 II - 并沒有那么easy的easy題

給定一個已按照升序排列 的有序數組&#xff0c;找到兩個數使得它們相加之和等于目標數。 函數應該返回這兩個下標值 index1 和 index2&#xff0c;其中 index1 必須小于 index2。 說明: 返回的下標值&#xff08;index1 和 index2&#xff09;不是從零開始的。 你可以假設每…

C++:21---仿函數

什么是仿函數 所謂的仿函數(functor),是通過重載()運算符模擬函數形為的類。 因此,這里需要明確兩點: 1 仿函數不是函數,它是個類; 2 仿函數重載了()運算符,使得它的對你可以像函數那樣子調用(代碼的形式好像是在調用函數)。 假設有一個vector<string>,你的任務…

C++:20---類模板(template)

一、類模板與模板類 類模板:一個模板(是模板)模板類:調用類模板生成的類對象(是類實體),也稱為類模板的實例化類模板的定義: 與函數模板的定義是一樣的template <typename T> class Blob { public: Blob(); Blob(std::initializer_list<T> i); };模板類的使…

leetcode340. 至多包含 K 個不同字符的最長子串

給定一個字符串 s &#xff0c;找出 至多 包含 k 個不同字符的最長子串 T。 示例 1: 輸入: s "eceba", k 2 輸出: 3 解釋: 則 T 為 "ece"&#xff0c;所以長度為 3。 示例 2: 輸入: s "aa", k 1 輸出: 2 解釋: 則 T 為 "aa"&am…

Redis線上救命丸:01---誤操作AOF、RDB恢復數據

Redis的flushall/flushdb命令可以做數據清除&#xff0c;對于Redis的開發和運維人員有一定幫助&#xff0c;然而一旦誤操作&#xff0c;它的破壞性也是很明顯的。怎么才能快速恢復數據&#xff0c;讓損失達到最小呢&#xff1f;本文我們將結合之前學習的Redis相關知識進行分析&…

Log4j使用總結

一、介紹Log4j是Apache的一個開放源代碼項目&#xff0c;通過使用Log4j&#xff0c;我們可以控制日志信息輸送的目的地是控制臺、文件、GUI組件、甚至是套接口服務 器、NT的事件記錄器、UNIX Syslog守護進程等&#xff1b;我們也可以控制每一條日志的輸出格式&#xff1b;通過定…

C語言: GDB調試技術(一)

啟動GDB的方法有以下幾種: 1、gdb <program> program也就是你的執行文件,一般在當然目錄下。’ 例如我寫了一個簡單的helloword程序 #include <stdio.h> int main(){int a = 1;char* ch = "hello world";printf("%s\n",ch);return 0; }那么我…

C語言: ---windows下VS Debug調試

首先我先列出來常用的一些命令或者鍵盤控制: F5 開始調試,執行到斷點 Shift + F5 停止調試 F9 在光標所在行添加斷點 Shift + F9 QuickWatch Shift Ctrl F9 delete all 斷點 F10 單步執行 F11 進入調用的函數 Shift F11 跳出這次調用的函數 另外還可以用Disable all breakpoi…

leetcode350. 兩個數組的交集 II

給定兩個數組&#xff0c;編寫一個函數來計算它們的交集。 示例 1: 輸入: nums1 [1,2,2,1], nums2 [2,2] 輸出: [2,2] 示例 2: 輸入: nums1 [4,9,5], nums2 [9,4,9,8,4] 輸出: [4,9] 說明&#xff1a; 輸出結果中每個元素出現的次數&#xff0c;應與元素在兩個數組中出…

C語言: ---Linux下ulimit是什么鬼

其實ulimit的講解不屬于C或者C++ 語言范疇,他只是在我們日常開發或者線上linux運行環境不可缺少的工具。 比如我們要查看服務器崩潰的core文件,允許core文件產生,都需要ulimit -c命令調整。 比如我們設置的當前運行環境的棧空間過小,容易產生棧溢出,那么我們…

C語言: ---gdb查看內存和寄存器內容

gdb沒有CodeWarrior強大,但是也提供了查看寄存器的命令:(gdb) info register r1r1 0xbffffb40 3221224256(gdb) info registersr0 0x1000052c 268436780r1 0xbffffb40 3221224256r2 0x48026ea0 1208118944r3 …

leetcode52. N皇后 II 最強解法直接秒殺100%

n 皇后問題研究的是如何將 n 個皇后放置在 nn 的棋盤上&#xff0c;并且使皇后彼此之間不能相互攻擊。 上圖為 8 皇后問題的一種解法。 給定一個整數 n&#xff0c;返回 n 皇后不同的解決方案的數量。 示例: 輸入: 4 輸出: 2 解釋: 4 皇后問題存在如下兩個不同的解法。 [ […

C語言:---gdb多線程調試

1)恢復程序運行和單步調試 當程序被停住了,你可以用continue命令恢復程序的運行直到程序結束,或下一個斷點到來。也可以使用step或next命令單步跟蹤程序。 continue [ignore-count] c [ignore-count] fg [ignore-count] 恢復程序運行,直到程序結束,或是下一個斷點到來。ig…