日韩av黄I国产麻豆传媒I国产91av视频在线观看I日韩一区二区三区在线看I美女国产在线I麻豆视频国产在线观看I成人黄色短片

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) >

(二十二)深入浅出TCPIP之实战篇—用c++开发一个http服务器

發(fā)布時(shí)間:2023/12/13 68 豆豆
生活随笔 收集整理的這篇文章主要介紹了 (二十二)深入浅出TCPIP之实战篇—用c++开发一个http服务器 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

在當(dāng)前的網(wǎng)絡(luò)編程專欄前十幾篇文章里,我已經(jīng)說(shuō)明了TCPIP常用的一些原理,那么接下來(lái)我將逐步進(jìn)入到實(shí)戰(zhàn)編程階段:

本篇文章我將帶大家用C++做一個(gè)http服務(wù)器。既然想實(shí)現(xiàn)一個(gè)http服務(wù)器,首先必須要熟悉的就是http協(xié)議知識(shí),然后在選擇具體的模塊來(lái)完成實(shí)現(xiàn)。下面先了解一些http協(xié)議知識(shí),然后我們?cè)僖徊揭徊絹?lái)實(shí)現(xiàn)它。

http服務(wù)器

http協(xié)議知識(shí)

一、 網(wǎng)絡(luò)通信簡(jiǎn)介

傳輸層及其以下的機(jī)制由內(nèi)核提供,應(yīng)用層由用戶進(jìn)程提供,應(yīng)用程序?qū)νㄓ崝?shù)據(jù)的含義進(jìn)行解釋,而傳輸層及其以下處理通訊的細(xì)節(jié),將數(shù)據(jù)從一臺(tái)計(jì)算機(jī)通過(guò)一定的路徑發(fā)送到另一臺(tái)計(jì)算機(jī)。應(yīng)用層數(shù)據(jù)通過(guò)協(xié)議棧發(fā)到網(wǎng)絡(luò)上時(shí),每層協(xié)議都要加上一個(gè)數(shù)據(jù)首部(header),稱為封裝(Encapsulation)。

假設(shè)現(xiàn)在應(yīng)用層協(xié)議為http,那么其中的Data 可以看作是一個(gè)http請(qǐng)求或者應(yīng)答,Data包含真正的消息正文和app首部(即報(bào)頭等)。?

二、HTTP協(xié)議詳解之請(qǐng)求篇

http請(qǐng)求由三部分組成,分別是:請(qǐng)求行、消息報(bào)頭、請(qǐng)求正文

1、請(qǐng)求行以一個(gè)方法符號(hào)開(kāi)頭,以空格分開(kāi),后面跟著請(qǐng)求的URI和協(xié)議的版本,格式如下:Method Request-URI HTTP-Version CRLF 其中 Method表示請(qǐng)求方法;Request-URI是一個(gè)統(tǒng)一資源標(biāo)識(shí)符;HTTP-Version表示請(qǐng)求的HTTP協(xié)議版本;CRLF表示回車和換行(除了作為結(jié)尾的CRLF外,不允許出現(xiàn)單獨(dú)的CR或LF字符)。

請(qǐng)求方法(所有方法全為大寫)有多種,各個(gè)方法的解釋如下:GET 請(qǐng)求獲取Request-URI所標(biāo)識(shí)的資源 POST 在Request-URI所標(biāo)識(shí)的資源后附加新的數(shù)據(jù) HEAD 請(qǐng)求獲取由Request-URI所標(biāo)識(shí)的資源的響應(yīng)消息報(bào)頭 PUT 請(qǐng)求服務(wù)器存儲(chǔ)一個(gè)資源,并用Request-URI作為其標(biāo)識(shí) DELETE 請(qǐng)求服務(wù)器刪除Request-URI所標(biāo)識(shí)的資源 TRACE 請(qǐng)求服務(wù)器回送收到的請(qǐng)求信息,主要用于測(cè)試或診斷 CONNECT 保留將來(lái)使用 OPTIONS 請(qǐng)求查詢服務(wù)器的性能,或者查詢與資源相關(guān)的選項(xiàng)和需求

2、請(qǐng)求報(bào)頭后述 3、請(qǐng)求正文(略)

三、HTTP協(xié)議詳解之響應(yīng)篇

在接收和解釋請(qǐng)求消息后,服務(wù)器返回一個(gè)HTTP響應(yīng)消息。

HTTP響應(yīng)也是由三個(gè)部分組成,分別是:狀態(tài)行、消息報(bào)頭、響應(yīng)正文 1、狀態(tài)行格式如下:HTTP-Version Status-Code Reason-Phrase CRLF 其中,HTTP-Version表示服務(wù)器HTTP協(xié)議的版本;Status-Code表示服務(wù)器發(fā)回的響應(yīng)狀態(tài)代碼;Reason-Phrase表示狀態(tài)代碼的文本描述。

狀態(tài)代碼由三位數(shù)字組成,第一個(gè)數(shù)字定義了響應(yīng)的類別,且有五種可能取值:1xx:指示信息--表示請(qǐng)求已接收,繼續(xù)處理 2xx:成功--表示請(qǐng)求已被成功接收、理解、接受 3xx:重定向--要完成請(qǐng)求必須進(jìn)行更進(jìn)一步的操作 4xx:客戶端錯(cuò)誤--請(qǐng)求有語(yǔ)法錯(cuò)誤或請(qǐng)求無(wú)法實(shí)現(xiàn) 5xx:服務(wù)器端錯(cuò)誤--服務(wù)器未能實(shí)現(xiàn)合法的請(qǐng)求

2、響應(yīng)報(bào)頭后述

3、響應(yīng)正文就是服務(wù)器返回的資源的內(nèi)容

四、HTTP協(xié)議詳解之消息報(bào)頭篇

HTTP消息由客戶端到服務(wù)器的請(qǐng)求和服務(wù)器到客戶端的響應(yīng)組成。請(qǐng)求消息和響應(yīng)消息都是由開(kāi)始行(對(duì)于請(qǐng)求消息,開(kāi)始行就是請(qǐng)求行,對(duì)于響應(yīng)消息,開(kāi)始行就是狀態(tài)行),消息報(bào)頭(可選),空行(只有CRLF的行),消息正文(可選)組成。

HTTP消息報(bào)頭包括普通報(bào)頭、請(qǐng)求報(bào)頭、響應(yīng)報(bào)頭、實(shí)體報(bào)頭。每一個(gè)報(bào)頭域都是由名字+“:”+空格+值 組成,消息報(bào)頭域的名字是大小寫無(wú)關(guān)的。

1、普通報(bào)頭 在普通報(bào)頭中,有少數(shù)報(bào)頭域用于所有的請(qǐng)求和響應(yīng)消息,但并不用于被傳輸?shù)膶?shí)體,只用于傳輸?shù)南ⅰache-Control 用于指定緩存指令,緩存指令是單向的(響應(yīng)中出現(xiàn)的緩存指令在請(qǐng)求中未必會(huì)出現(xiàn)),且是獨(dú)立的(一個(gè)消息的緩存指令不會(huì)影響另一個(gè)消息處理的緩存機(jī)制),HTTP1.0使用的類似的報(bào)頭域?yàn)镻ragma。請(qǐng)求時(shí)的緩存指令包括:no-cache(用于指示請(qǐng)求或響應(yīng)消息不能緩存)、no-store、max-age、max-stale、min-fresh、only-if-cached; 響應(yīng)時(shí)的緩存指令包括:public、private、no-cache、no-store、no-transform、must-revalidate、proxy-revalidate、max-age、s-maxage.

Date普通報(bào)頭域表示消息產(chǎn)生的日期和時(shí)間

Connection普通報(bào)頭域允許發(fā)送指定連接的選項(xiàng)。

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

3、響應(yīng)報(bào)頭 響應(yīng)報(bào)頭允許服務(wù)器傳遞不能放在狀態(tài)行中的附加響應(yīng)信息,以及關(guān)于服務(wù)器的信息和對(duì)Request-URI所標(biāo)識(shí)的資源進(jìn)行下一步訪問(wèn)的信息。常用的響應(yīng)報(bào)頭 Location Server WWW-Authenticate

4、實(shí)體報(bào)頭 請(qǐng)求和響應(yīng)消息都可以傳送一個(gè)實(shí)體。一個(gè)實(shí)體由實(shí)體報(bào)頭域和實(shí)體正文組成,但并不是說(shuō)實(shí)體報(bào)頭域和實(shí)體正文要在一起發(fā)送,可以只發(fā)送實(shí)體報(bào)頭域。實(shí)體報(bào)頭定義了關(guān)于實(shí)體正文(eg:有無(wú)實(shí)體正文)和請(qǐng)求所標(biāo)識(shí)的資源的元信息。常用的實(shí)體報(bào)頭 Content-Encoding Content-Language Content-Length Content-Type Last-Modified Expires GMT

五、HTTP協(xié)議相關(guān)技術(shù)補(bǔ)充

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

2、協(xié)議分析的優(yōu)勢(shì)—HTTP分析器檢測(cè)網(wǎng)絡(luò)攻擊 以模塊化的方式對(duì)高層協(xié)議進(jìn)行分析處理,將是未來(lái)入侵檢測(cè)的方向。HTTP及其代理的常用端口80、3128和8080在network部分用port標(biāo)簽進(jìn)行了規(guī)定

3、HTTP協(xié)議Content Lenth限制漏洞導(dǎo)致拒絕服務(wù)攻擊 使用POST方法時(shí),可以設(shè)置ContentLenth來(lái)定義需要傳送的數(shù)據(jù)長(zhǎng)度,例如ContentLenth:999999999,在傳送完成前,內(nèi) 存不會(huì)釋放,攻擊者可以利用這個(gè)缺陷,連續(xù)向WEB服務(wù)器發(fā)送垃圾數(shù)據(jù)直至WEB服務(wù)器內(nèi)存耗盡。這種攻擊方法基本不會(huì)留下痕跡。

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

如何實(shí)現(xiàn)一個(gè)Web服務(wù)器:

1.本服務(wù)器采用基于線程池和epoll實(shí)現(xiàn)的Reactor模式的簡(jiǎn)單HTTP服務(wù)器。主進(jìn)程只管理監(jiān)聽(tīng)socket,連接socket都由進(jìn)程池中的worker進(jìn)行管理。當(dāng)有新的連接到來(lái)時(shí),主進(jìn)程會(huì)通過(guò)socketpair創(chuàng)建的套接字和worker進(jìn)程通信,通知子進(jìn)程接收新連接。子進(jìn)程正確接收連接之后,會(huì)把該套接字上的讀寫事件注冊(cè)到自己的epll內(nèi)核事件表中。之后該套接字上的任何I/O操作都由被選中的worker來(lái)處理,直到客戶關(guān)閉連接或超時(shí)。

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

2.每個(gè)子進(jìn)程都是一個(gè)reactor,采用epoll和非阻塞I/O實(shí)現(xiàn)事件循環(huán)。如下圖:

  • a. epoll負(fù)責(zé)監(jiān)聽(tīng)事件的發(fā)生,有事件到來(lái)將調(diào)用相應(yīng)的事件處理單元進(jìn)行處理

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

    • 2). 定時(shí)器事件。使用timefd,同樣通過(guò)監(jiān)聽(tīng)timefd上的可讀事件來(lái)統(tǒng)一事件源。將其設(shè)置為邊沿觸發(fā),不然timefd水平觸發(fā)將一直告知該事件。

    • a).忽略SIGPIPE信號(hào)(當(dāng)讀寫一個(gè)對(duì)端關(guān)閉的連接時(shí)),將為SIGINT,SIGTERM,SIGCHILD(對(duì)父進(jìn)程來(lái)說(shuō)標(biāo)識(shí)有子進(jìn)程狀態(tài)發(fā)生變化,一般是子進(jìn)程結(jié)束)設(shè)置信號(hào)處理函數(shù)。

    • a). 超時(shí)將通過(guò)連接池回收連接。

    • 1). 通過(guò)非阻塞I/o和事件循環(huán)來(lái)將阻塞進(jìn)程的方法分解。例如:每次recv新數(shù)據(jù)時(shí),如果recv返回EAGAIN錯(cuò)誤,都不會(huì)一直循環(huán)recv,而是將現(xiàn)有數(shù)據(jù)先處理,然后記錄當(dāng)前連接狀態(tài),然后將讀事件接著放到epoll隊(duì)列中監(jiān)聽(tīng)等待下一個(gè)數(shù)據(jù)到來(lái)。因?yàn)槊看味疾粫?huì)盡可能的將I/O上的數(shù)據(jù)讀取,所以我采用了水平觸發(fā)而不是邊沿觸發(fā)。send同理。

    • i. 對(duì)一個(gè)連接來(lái)說(shuō),主要監(jiān)聽(tīng)的就是讀就緒事件和寫就緒事件。

    • ii. 統(tǒng)一事件源:

  • b. 連接池和線程池的實(shí)現(xiàn):

    • i. 連接池:連接池采用一個(gè)數(shù)組實(shí)現(xiàn)。連接池在構(gòu)造時(shí)傳入最大連接數(shù),初始化為最大連接數(shù)個(gè)連接,且后續(xù)數(shù)目不可變。新連接到來(lái)時(shí)調(diào)用http_conn::init,當(dāng)客戶端有網(wǎng)絡(luò)數(shù)據(jù)發(fā)送的時(shí)候,將當(dāng)前連接傳入到線程池里,通過(guò)線程池來(lái)處理邏輯。

    • ii. 線程池:線程池的實(shí)現(xiàn)是通過(guò)連接類來(lái)完成的。連接類在第一次被初始化時(shí)即第一次被使用,將申請(qǐng)相應(yīng)的定時(shí)器,接收和發(fā)送緩存。之后將不會(huì)將申請(qǐng)的內(nèi)存銷毀,直到進(jìn)程結(jié)束。通過(guò)這樣來(lái)降低申請(qǐng)和釋放內(nèi)存的次數(shù)來(lái)減少內(nèi)存碎片以及節(jié)約時(shí)間。

  • c. 連接:
    每個(gè)連接都應(yīng)該有一個(gè)void http_conn::init( int sockfd, const sockaddr_in& addr )函數(shù),此函數(shù)會(huì)在第一次被調(diào)用時(shí)分配內(nèi)存,另外還有void http_conn::process() ,這個(gè)函數(shù)將根據(jù)操作類型,來(lái)決定要進(jìn)行的是讀process_read還是寫操作process_write。同時(shí)根據(jù)操作結(jié)果返回相應(yīng)的狀態(tài),來(lái)決定要給epoll添加什么事件。??

  • f. Http報(bào)文請(qǐng)求行和頭部解析:

    • i. 通過(guò)狀態(tài)機(jī)m_check_state 來(lái)實(shí)現(xiàn)HTTP報(bào)文的解析。因?yàn)橐粋€(gè)請(qǐng)求有可能不是在一個(gè)tcp包中到來(lái),所以需要記錄狀態(tài)機(jī)的狀態(tài),以及上次check到的位置。在解析完HTTP報(bào)文后,還需要保存解析的結(jié)果,然后根據(jù)解析結(jié)果,來(lái)產(chǎn)生相應(yīng)response。??

代碼結(jié)構(gòu)

HttpServer

基于線程池和epoll實(shí)現(xiàn)的Reactor模式的簡(jiǎn)單HTTP服務(wù)器

threadpool.h

基于C++實(shí)現(xiàn)的線程池類的封裝,采用模板來(lái)實(shí)現(xiàn)拓展性

locker.h

封裝了信號(hào)量、互斥鎖、條件變量的使用

http_conn.h

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

main.cpp

采用epoll實(shí)現(xiàn)了簡(jiǎn)單的Reactor模式,主線程復(fù)雜接受連接,線程池負(fù)責(zé)處理任務(wù)。】

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

運(yùn)行make

執(zhí)行 ./httpserver 0.0.0.0 80

代碼可在后臺(tái)回復(fù) “http服務(wù)器”,作者會(huì)提供一個(gè)url下載鏈接

總結(jié)

以上是生活随笔為你收集整理的(二十二)深入浅出TCPIP之实战篇—用c++开发一个http服务器的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。