1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        從零實(shí)現(xiàn)一個(gè)http服務(wù)器

        共 10227字,需瀏覽 21分鐘

         ·

        2021-10-21 06:36



        我始終覺(jué)得,天生的出身很重要,但后天的努力更加重要,所以如今的很多“科班”往往不如后天努力的“非科班”。所以,我們需要重新給“專(zhuān)業(yè)”和“專(zhuān)家”下一個(gè)定義:

        所謂專(zhuān)業(yè),就是別人不搞你搞,這就是你的“專(zhuān)業(yè)”;

        你和別人同時(shí)搞,你比別人搞的好,就是“專(zhuān)家”。

        說(shuō)到 http 協(xié)議和 http 請(qǐng)求,很多人都知道,但是他們真的“知道”嗎?

        我面試過(guò)很多求職者,一說(shuō)到 http 協(xié)議,他們能滔滔不絕,然后我問(wèn)他 http 協(xié)議的具體格式是啥樣子的?很多人不清楚,不清楚就不清楚吧,他甚至能將 http 協(xié)議的頭扯到 html 文檔頭部。

        當(dāng)我問(wèn) http GET 和 POST 方法的區(qū)別時(shí),GET 請(qǐng)求是什么形式一般人都可以答出來(lái),但是 POST 請(qǐng)求的數(shù)據(jù)放在哪里,服務(wù)器如何識(shí)別和解析這些 POST 數(shù)據(jù),很多人又說(shuō)不清道不明了。

        當(dāng)說(shuō)到 http 服務(wù)器時(shí),很多人離開(kāi)了 Apache、Nginx 這樣現(xiàn)成的 http server 之外,自己實(shí)現(xiàn)一個(gè) http 服務(wù)器無(wú)從下手,如果實(shí)際應(yīng)用場(chǎng)景有需要使用到一些簡(jiǎn)單 http 請(qǐng)求時(shí),使用 Apache、Nginx 這樣重量級(jí)的 http 服務(wù)器程序?qū)嵲趧趲焺?dòng)眾,你可以嘗試自己實(shí)現(xiàn)一個(gè)簡(jiǎn)單的。

        上面提到的問(wèn)題,如果您不能清晰地回答出來(lái),可以閱讀一下這篇文章,這篇文章在不僅介紹 http 協(xié)議的格式,同時(shí)帶領(lǐng)大家從零實(shí)現(xiàn)一個(gè)簡(jiǎn)單的 http 服務(wù)器程序。

        一、項(xiàng)目背景

        最近很多朋友希望我的 Flamingo 服務(wù)器支持 http 協(xié)議,我自己也想做一個(gè)微信小程序,小程序通過(guò) http 協(xié)議連接通過(guò)我的 Flamingo 服務(wù)器進(jìn)行聊天。

        Flamingo 是一個(gè)開(kāi)源的即時(shí)通訊軟件,目前除了服務(wù)器端,還有 PC 端、Android 端,后面會(huì)支持更多的終端。

        關(guān)于 Flamingo 的介紹和最新源碼:https://github.com/balloonwj/flamingo 更新日志:https://github.com/balloonwj/flamingo/issues/1

        下面是 Flamingo 的部分截圖:

        二、http 協(xié)議介紹

        1. http 協(xié)議是應(yīng)用層協(xié)議,一般建立在 TCP 協(xié)議的基礎(chǔ)之上(當(dāng)然你的實(shí)現(xiàn)非要基于 UDP 也是可以的),也就是說(shuō) http 協(xié)議的數(shù)據(jù)收發(fā)是通過(guò) TCP 協(xié)議的。

        2. http 協(xié)議分為 head 和 body 兩部分,但是我們一般說(shuō)的 html 文檔中的 ?和 ?部分是 html 文檔的 head 和 body,它們都是 http 協(xié)議的 body 部分。示意圖如下:

        那么 http 協(xié)議的頭到底長(zhǎng)啥樣子呢?我們來(lái)介紹一下 http 協(xié)議吧。

        http協(xié)議的格式如下:

        GET或POST?請(qǐng)求的URL路徑(一般是去掉域名的路徑)?HTTP協(xié)議版本號(hào)\r\n
        字段1名:?字段1值\r\n
        字段2名:?字段2值\r\n
        ????......
        字段n名?:?字段n值\r\n
        \r\n
        http協(xié)議包體內(nèi)容

        也就是說(shuō) http 協(xié)議由兩部分組成:包頭包體,包頭與包體之間使用一個(gè)\r\n分割,由于 http 協(xié)議包頭的每一行都是以\r\n結(jié)束,所以 http 協(xié)議包頭一般以\r\n\r\n(兩個(gè) \r\n )結(jié)束。

        舉個(gè)例子,比如我們?cè)跒g覽器中請(qǐng)求?http://www.hootina.org/index_2013.php?這個(gè)網(wǎng)址,這是一個(gè)典型的 GET 方法,瀏覽器組裝的 http 數(shù)據(jù)包格式如下:

        GET?/index_2013.php?HTTP/1.1\r\n
        Host:?www.hootina.org\r\n
        Connection:?keep-alive\r\n
        Upgrade-Insecure-Requests:?1\r\n
        User-Agent:?Mozilla/5.0?(Windows?NT?6.1;?Win64;?x64)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/65.0.3325.146?Safari/537.36\r\n
        Accept:?text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n
        Accept-Encoding:?gzip,?deflate\r\n
        Accept-Language:?zh-CN,zh;q=0.9,en;q=0.8\r\n
        \r\n

        上面這個(gè)請(qǐng)求只有包頭沒(méi)有包體,http 協(xié)議的包體不是必須的,GET 請(qǐng)求一般沒(méi)有包體。

        如果 GET 請(qǐng)求帶參數(shù),那么一般是附加在請(qǐng)求的 URL 后面,參數(shù)與參數(shù)之間使用 & 分割,例如請(qǐng)求?http://www.hootina.org/index_2013.php?param1=value1¶m2=value2¶m3=value3,我們看下這個(gè)請(qǐng)求組裝成的 http 協(xié)議包格式:

        GET?/index_2013.php?param1=value1¶m2=value2¶m3=value3?HTTP/1.1\r\n
        Host:?www.hootina.org\r\n
        Connection:?keep-alive\r\n
        Upgrade-Insecure-Requests:?1\r\n
        User-Agent:?Mozilla/5.0?(Windows?NT?6.1;?Win64;?x64)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/65.0.3325.146?Safari/537.36\r\n
        Accept:?text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8\r\n
        Accept-Encoding:?gzip,?deflate\r\n
        Accept-Language:?zh-CN,zh;q=0.9,en;q=0.8\r\n
        \r\n

        對(duì)比一下,你現(xiàn)在知道 http 協(xié)議的 GET 參數(shù)放在協(xié)議包的什么位置了吧。

        那么 POST 請(qǐng)求的數(shù)據(jù)放在什么位置呢?我們?cè)?12306 網(wǎng)站?https://kyfw.12306.cn/otn/login/init?中登陸輸入用戶(hù)名和密碼:

        然后發(fā)現(xiàn)瀏覽器以POST方式組裝了http協(xié)議包發(fā)送了我們的用戶(hù)名、密碼和其他一些信息,瀏覽器組裝的包格式如下:

        POST?/passport/web/login?HTTP/1.1\r\n
        Host:?kyfw.12306.cn\r\n
        Connection:?keep-alive\r\n
        Content-Length:?55\r\n
        Accept:?application/json,?text/javascript,?*/*;?q=0.01\r\n
        Origin:?https://kyfw.12306.cn\r\n
        X-Requested-With:?XMLHttpRequest\r\n
        User-Agent:?Mozilla/5.0?(Windows?NT?6.1;?Win64;?x64)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/65.0.3325.146?Safari/537.36\r\n
        Content-Type:?application/x-www-form-urlencoded;?charset=UTF-8\r\n
        Referer:?https://kyfw.12306.cn/otn/login/init\r\n
        Accept-Encoding:?gzip,?deflate,?br\r\n
        Accept-Language:?zh-CN,zh;q=0.9,en;q=0.8\r\n
        Cookie:?_passport_session=0b2cc5b86eb74bcc976bfa9dfef3e8a20712;?_passport_ct=18d19b0930954d76b8057c732ce4cdcat8137;?route=6f50b51faa11b987e576cdb301e545c4;?RAIL_EXPIRATION=1526718782244;?RAIL_DEVICEID=QuRAhOyIWv9lwWEhkq03x5Yl_livKZxx7gW6_-52oTZQda1c4zmVWxdw5Zk79xSDFHe9LJ57F8luYOFp_yahxDXQAOmEV8U1VgXavacuM2UPCFy3knfn42yTsJM3EYOy-hwpsP-jTb2OXevJj5acf40XsvsPDcM7;?BIGipServerpool_passport=300745226.50215.0000;?BIGipServerotn=1257243146.38945.0000;?BIGipServerpassport=1005060362.50215.0000\r\n
        \r\n
        username=balloonwj%40qq.com&password=iloveyou&appid=otn

        其中?username=balloonwj%40qq.com&password=iloveyou&appid=otn?就是我們的 POST 數(shù)據(jù),但是大家需要注意以下注意事項(xiàng),不要搞錯(cuò):

        1. 我的用戶(hù)名是?[email protected],到 POST 請(qǐng)求包體里面變成?balloonwj%40qq.com,其中%40@符號(hào)的 16 進(jìn)制轉(zhuǎn)碼形式。這個(gè)碼表可以參考這里:

        http://www.w3school.com.cn/tags/html_ref_urlencode.html

        2.這里有三個(gè)變量,分別是?username、passwordappid,他們之間使用 & 符號(hào)分割,但是請(qǐng)注意的是,這不意味著傳遞多個(gè) POST 變量時(shí)必須使用 & 符號(hào)分割,只不過(guò)這里是瀏覽器對(duì)表單數(shù)據(jù)(輸入用戶(hù)名和密碼的文本框是表單的一種)分割多個(gè)變量采用的方式。你可以根據(jù)你的需求自由定制,只要讓服務(wù)器知道你的解析方式即可。比如可以這么分割:

        方法一

        username=balloonwj%40qq.com|password=iloveyou|appid=otn

        方法二

        username:balloonwj%40qq.com\r\n
        password:iloveyou\r\n
        appid:otn\r\n

        方法三

        username,password,appid=balloonwj%40qq.com,iloveyou,otn

        不管怎么分割,只要你能自己按一定的規(guī)則解析出來(lái)就可以了。

        不知道你注意到?jīng)]有,上面的 POST 數(shù)據(jù)放在 http 包體中,服務(wù)器如何解析呢?可能你沒(méi)明白我的意思,看下圖:

        如上圖所示,由于 http 協(xié)議是基于 TCP 協(xié)議的,TCP 協(xié)議是流式協(xié)議,包頭部分可以通過(guò)多出的?\r\n?來(lái)分界,包體部分如何分界呢?這是協(xié)議本身要解決的問(wèn)題。

        目前一般有兩種方式:

        第一種方式就是在包頭中有個(gè)?Content-Length?字段,這個(gè)字段的值的大小標(biāo)識(shí)了 POST 數(shù)據(jù)的長(zhǎng)度,上圖中 55 就是數(shù)據(jù)?username=balloonwj%40qq.com&password=iloveyou&appid=otn?的長(zhǎng)度,服務(wù)器收到一個(gè)數(shù)據(jù)包后,先從包頭解析出這個(gè)字段的值,再根據(jù)這個(gè)值去讀取相應(yīng)長(zhǎng)度的作為 http 協(xié)議的包體數(shù)據(jù)。

        還有一個(gè)格式叫做 http chunk 技術(shù)(分塊),大致意思是將大包分成小包,具體的詳情有興趣的讀者可以自行搜索學(xué)習(xí)。

        三、http 客戶(hù)端實(shí)現(xiàn)

        如果您能掌握以上說(shuō)的 http 協(xié)議,你就可以自己通過(guò)代碼組裝 http 協(xié)議發(fā)送 http 請(qǐng)求了(也是各種開(kāi)源 http 庫(kù)的做法)。

        我們先簡(jiǎn)單地介紹一下如何模擬發(fā)送 http。

        舉個(gè)例子,我們要請(qǐng)求?http://www.hootina.org/index_2013.php,那么我們可以先通過(guò)域名得到 IP 地址,即通過(guò) socket API gethostbyname() 得到 www.hootina.org 的 IP 地址,由于 http 服務(wù)器默認(rèn)的端口號(hào)是 80,有了域名和 IP 地址之后,我們使用 socket API connect() 去連接服務(wù)器,然后根據(jù)上面介紹的格式組裝成 http 協(xié)議包,利用 socket API send() 函數(shù)發(fā)出去,如果服務(wù)器有應(yīng)答,我們可以使用 socket API recv() 去接受數(shù)據(jù),接下來(lái)就是解析數(shù)據(jù)(先解析包頭和包體)。

        四、http 服務(wù)器實(shí)現(xiàn)

        我們這里簡(jiǎn)化一些問(wèn)題,假設(shè)客戶(hù)端發(fā)送的請(qǐng)求都是 GET 請(qǐng)求,當(dāng)客戶(hù)端發(fā)來(lái) http 請(qǐng)求之后,我們拿到 http 包后就做相應(yīng)的處理。我們以為我們的 Flamingo 服務(wù)器實(shí)現(xiàn)一個(gè)支持 http 格式的注冊(cè)請(qǐng)求為例。假設(shè)用戶(hù)在瀏覽器里面輸入以下網(wǎng)址,就可以實(shí)現(xiàn)一個(gè)注冊(cè)功能:

        http://120.55.94.78:12345/register.do?p={"username":?"13917043329",?"nickname":?"balloon",?"password":?"123"}

        這里我們的 http 協(xié)議使用的是 12345 端口號(hào)而不是默認(rèn)的 80 端口。如何偵聽(tīng) 12345 端口,這個(gè)是非?;A(chǔ)的知識(shí)了,這里就不介紹了。

        當(dāng)我們收到數(shù)據(jù)以后:

        //代碼節(jié)選自:https://github.com/balloonwj/flamingo/blob/master/flamingoserver/chatserversrc/HttpSession.cpp:26行
        void?HttpSession::onRead(const?std::shared_ptr&?conn,?ByteBuffer*?pBuffer,?Timestamp?receivTime)
        {
        ????//LOGI?<"Recv?a?http?request?from?"?<peerAddress().toIpPort();
        ????
        ????string?inbuf;
        ????//先把所有數(shù)據(jù)都取出來(lái)
        ????inbuf.append(pBuffer->peek(),?pBuffer->readableBytes());
        ????//因?yàn)橐粋€(gè)http包頭的數(shù)據(jù)至少\r\n\r\n,所以大于4個(gè)字符
        ????//小于等于4個(gè)字符,說(shuō)明數(shù)據(jù)未收完,退出,等待網(wǎng)絡(luò)底層接著收取
        ????if?(inbuf.length()?<=?4)
        ????????return;

        ????//我們收到的GET請(qǐng)求數(shù)據(jù)包一般格式如下:
        ????/*
        ????GET?/register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22}?HTTP/1.1\r\n
        ????Host:?120.55.94.78:12345\r\n
        ????Connection:?keep-alive\r\n
        ????Upgrade-Insecure-Requests:?1\r\n
        ????User-Agent:?Mozilla/5.0?(Windows?NT?6.1;?Win64;?x64)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/65.0.3325.146?Safari/537.36\r\n
        ????Accept-Encoding:?gzip,?deflate\r\n
        ????Accept-Language:?zh-CN,?zh;?q=0.9,?en;?q=0.8\r\n
        ????\r\n
        ?????*/
        ????//檢查是否以\r\n\r\n結(jié)束,如果不是說(shuō)明包頭不完整,退出
        ????string?end?=?inbuf.substr(inbuf.length()?-?4);
        ????if?(end?!=?"\r\n\r\n")
        ????????return;

        ????//以\r\n分割每一行
        ????std::vector?lines;
        ????StringUtil::split(inbuf,?lines,?"\r\n");
        ????if?(lines.size()?????{
        ????????conn->forceClose();
        ????????return;
        ????}

        ????std::vector?chunk;
        ????StringUtil::split(lines[0],?chunk,?"?");
        ????//chunk中至少有三個(gè)字符串:GET+url+HTTP版本號(hào)
        ????if?(chunk.size()?????{
        ????????conn->forceClose();
        ????????return;
        ????}

        ????LOGI("url:?%s??from?%s",?chunk[1].c_str(),?conn->peerAddress().toIpPort().c_str());
        ????//inbuf?=?/register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22}
        ????std::vector?part;
        ????//通過(guò)?分割成前后兩端,前面是url,后面是參數(shù)
        ????StringUtil::split(chunk[1],?part,?"?");
        ????//chunk中至少有三個(gè)字符串:GET+url+HTTP版本號(hào)
        ????if?(part.size()?????{
        ????????conn->forceClose();
        ????????return;
        ????}

        ????string?url?=?part[0];
        ????string?param?=?part[1].substr(2);
        ????????
        ????if?(!process(conn,?url,?param))
        ????{
        ????????LOGE("handle?http?request?error,?from:?%s,?request:?%s",?conn->peerAddress().toIpPort().c_str(),?pBuffer->retrieveAllAsString().c_str());
        ????}

        ????//短連接,處理完關(guān)閉連接
        ????conn->forceClose();
        }

        代碼注釋都寫(xiě)的很清楚,我們先利用\r\n分割得到每一行,其中第一行的數(shù)據(jù)是:

        GET?/register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22}?HTTP/1.1

        其中%22是雙引號(hào)的 URL 轉(zhuǎn)碼形式,%20是空格的 URL 轉(zhuǎn)碼形式,然后我們根據(jù)空格分成三段,其中第二段就是我們的網(wǎng)址和參數(shù):

        /register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22}

        然后我們根據(jù)網(wǎng)址與參數(shù)之間的問(wèn)號(hào)將這個(gè)分成兩段:第一段是網(wǎng)址,第二段是參數(shù):

        //https://github.com/balloonwj/flamingo/blob/master/flamingoserver/chatserversrc/HttpSession.cpp:111行
        bool?HttpSession::process(const?std::shared_ptr&?conn,?const?std::string&?url,?const?std::string&?param)
        {
        ????if?(url.empty())
        ????????return?false;

        ????if?(url?==?"/register.do")
        ????{
        ????????onRegisterResponse(param,?conn);
        ????}
        ????else?if?(url?==?"/login.do")
        ????{
        ????????onLoginResponse(param,?conn);
        ????}
        ????else?if?(url?==?"/getfriendlist.do")
        ????{

        ????}
        ????else?if?(url?==?"/getgroupmembers.do")
        ????{

        ????}
        ????else
        ????????return?false;

        ????
        ????return?true;
        }

        然后我們根據(jù) URL匹配網(wǎng)址,如果是注冊(cè)請(qǐng)求,會(huì)走注冊(cè)處理邏輯:

        //https://github.com/balloonwj/flamingo/blob/master/flamingoserver/chatserversrc/HttpSession.cpp:158行
        void?HttpSession::onRegisterResponse(const?std::string&?data,?const?std::shared_ptr&?conn)
        {
        ????string?retData;
        ????string?decodeData;
        ????URLEncodeUtil::decode(data,?decodeData);
        ????BussinessLogic::registerUser(decodeData,?conn,?false,?retData);
        ????if?(!retData.empty())
        ????{
        ????????std::string?response;
        ????????URLEncodeUtil::encode(retData,?response);
        ????????makeupResponse(retData,?response);
        ????????conn->send(response);

        ????????LOGI("Response?to?client:?cmd=msg_type_register,?data:?%s,?client:?%s",?retData.c_str(),?conn->peerAddress().toIpPort().c_str());
        ????}
        }

        注冊(cè)結(jié)果放在 retData 中,為了發(fā)給客戶(hù)端,我們將結(jié)果中的特殊字符如雙引號(hào)轉(zhuǎn)碼,如返回結(jié)果是:

        {"code":0,?"msg":"ok"}

        會(huì)被轉(zhuǎn)碼成:

        {%22code%22:0,%20%22msg%22:%22ok%22}

        然后,將數(shù)據(jù)組裝成 http 協(xié)議發(fā)給客戶(hù)端,給客戶(hù)端的應(yīng)答協(xié)議與 http 請(qǐng)求協(xié)議有一點(diǎn)點(diǎn)差別,就是將請(qǐng)求的 URL 路徑換成所謂的 http 響應(yīng)碼,如 200 表示應(yīng)答正常返回、404 表示頁(yè)面不存在。

        應(yīng)答協(xié)議格式如下:

        GET或POST?響應(yīng)碼?HTTP協(xié)議版本號(hào)\r\n
        字段1名:?字段1值\r\n
        字段2名:?字段2值\r\n
        ?????......
        字段n名?:?字段n值\r\n
        \r\n
        http協(xié)議包體內(nèi)容

        舉個(gè)例子如:

        HTTP/1.1?200?OK\r\n
        Content-Type:?text/html\r\n
        Content-Length:42\r\n
        \r\n
        {%22code%22:%200,%20%22msg%22:%20%22ok%22}

        注意,包頭中的 Content-Length 長(zhǎng)度必須正好是包體{%22code%22:%200,%20%22msg%22:%20%22ok%22}?的長(zhǎng)度,這里是 42, 這也符合我們?yōu)g覽器的返回結(jié)果:

        當(dāng)然,需要注意的是,我們一般說(shuō) http 連接一般是短連接,這里我們也實(shí)現(xiàn)了這個(gè)功能(看上面的代碼:conn->forceClose();),不管一個(gè) http 請(qǐng)求是否成功,服務(wù)器處理后立馬就關(guān)閉連接。

        當(dāng)然,這里還有一些沒(méi)處理好的地方,如果你仔細(xì)觀察上面的代碼就會(huì)發(fā)現(xiàn)這個(gè)問(wèn)題,就是不滿(mǎn)足一個(gè) http 包頭時(shí)的處理,如果某個(gè)客戶(hù)端(不是使用瀏覽器)通過(guò)程序模擬了一個(gè)連接請(qǐng)求,但是遲遲不發(fā)含有?\r\n\r\n?的數(shù)據(jù),這路連接將會(huì)一直占用。我們可以判斷收到的數(shù)據(jù)長(zhǎng)度,防止別有用心的客戶(hù)端給我們的服務(wù)器亂發(fā)數(shù)據(jù)。我們假定,我們能處理的最大 URL 長(zhǎng)度是 2048,如果用戶(hù)發(fā)送的數(shù)據(jù)累積不含?\r\n\r\n,且長(zhǎng)度超過(guò) 2048 ,我們認(rèn)為連接非法,將連接斷開(kāi)。

        代碼修改成如下形式:

        //代碼節(jié)選自:https://github.com/balloonwj/flamingo/blob/master/flamingoserver/chatserversrc/HttpSession.cpp:26行
        void?HttpSession::onRead(const?std::shared_ptr&?conn,?ByteBuffer*?pBuffer,?Timestamp?receivTime)
        {
        ????//LOGI?<"Recv?a?http?request?from?"?<peerAddress().toIpPort();
        ????
        ????string?inbuf;
        ????//先把所有數(shù)據(jù)都取出來(lái)
        ????inbuf.append(pBuffer->peek(),?pBuffer->readableBytes());
        ????//因?yàn)橐粋€(gè)http包頭的數(shù)據(jù)至少\r\n\r\n,所以大于4個(gè)字符
        ????//小于等于4個(gè)字符,說(shuō)明數(shù)據(jù)未收完,退出,等待網(wǎng)絡(luò)底層接著收取
        ????if?(inbuf.length()?<=?4)
        ????????return;

        ????//我們收到的GET請(qǐng)求數(shù)據(jù)包一般格式如下:
        ????/*
        ????GET?/register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22}?HTTP/1.1\r\n
        ????Host:?120.55.94.78:12345\r\n
        ????Connection:?keep-alive\r\n
        ????Upgrade-Insecure-Requests:?1\r\n
        ????User-Agent:?Mozilla/5.0?(Windows?NT?6.1;?Win64;?x64)?AppleWebKit/537.36?(KHTML,?like?Gecko)?Chrome/65.0.3325.146?Safari/537.36\r\n
        ????Accept-Encoding:?gzip,?deflate\r\n
        ????Accept-Language:?zh-CN,?zh;?q=0.9,?en;?q=0.8\r\n
        ????\r\n
        ?????*/
        ????//檢查是否以\r\n\r\n結(jié)束,如果不是說(shuō)明包頭不完整,退出
        ????string?end?=?inbuf.substr(inbuf.length()?-?4);
        ????if?(end?!=?"\r\n\r\n")
        ????????return;
        ????//超過(guò)2048個(gè)字符,且不含\r\n\r\n,我們認(rèn)為是非法請(qǐng)求
        ????else?if?(inbuf.length()?>=?MAX_URL_LENGTH)
        ????{
        ????????conn->forceClose();
        ????????return;
        ????}

        ????//以\r\n分割每一行
        ????std::vector?lines;
        ????StringUtil::split(inbuf,?lines,?"\r\n");
        ????if?(lines.size()?????{
        ????????conn->forceClose();
        ????????return;
        ????}

        ????std::vector?chunk;
        ????StringUtil::split(lines[0],?chunk,?"?");
        ????//chunk中至少有三個(gè)字符串:GET+url+HTTP版本號(hào)
        ????if?(chunk.size()?????{
        ????????conn->forceClose();
        ????????return;
        ????}

        ????LOGI("url:?%s??from?%s",?chunk[1].c_str(),?conn->peerAddress().toIpPort().c_str());
        ????//inbuf?=?/register.do?p={%22username%22:%20%2213917043329%22,%20%22nickname%22:%20%22balloon%22,%20%22password%22:%20%22123%22}
        ????std::vector?part;
        ????//通過(guò)?分割成前后兩端,前面是url,后面是參數(shù)
        ????StringUtil::split(chunk[1],?part,?"?");
        ????//chunk中至少有三個(gè)字符串:GET+url+HTTP版本號(hào)
        ????if?(part.size()?????{
        ????????conn->forceClose();
        ????????return;
        ????}

        ????string?url?=?part[0];
        ????string?param?=?part[1].substr(2);
        ????????
        ????if?(!process(conn,?url,?param))
        ????{
        ????????LOGE("handle?http?request?error,?from:?%s,?request:?%s",?conn->peerAddress().toIpPort().c_str(),?pBuffer->retrieveAllAsString().c_str());
        ????}

        ????//短連接,處理完關(guān)閉連接
        ????conn->forceClose();
        }

        但這只能解決發(fā)送非法數(shù)據(jù)的情況,如果一個(gè)客戶(hù)端連上來(lái)不給我們發(fā)任何數(shù)據(jù),這段邏輯就無(wú)能為力了。如果不斷有客戶(hù)端這么做,會(huì)浪費(fèi)我們大量的連接資源,所以我們還需要一個(gè)定時(shí)器去定時(shí)檢測(cè)哪些 http 連接超過(guò)一定時(shí)間內(nèi)沒(méi)給我們發(fā)數(shù)據(jù),找到后將連接斷開(kāi)。這又涉及到服務(wù)器定時(shí)器如何設(shè)計(jì)了,關(guān)于這部分請(qǐng)參考我寫(xiě)的其他文章。

        限于作者經(jīng)驗(yàn)水平有限,文中難免有錯(cuò)亂之處,歡迎拍磚。另外,關(guān)于上面的代碼,可以去 github 上下載,地址是:

        https://github.com/balloonwj/flamingo



        本公眾號(hào)全部博文已整理成一個(gè)目錄,請(qǐng)?jiān)诠娞?hào)里回復(fù)「m」獲??!

        推薦閱讀:

        程序員的職業(yè)天花板

        Linux 系統(tǒng)開(kāi)機(jī)加電后發(fā)生了什么?

        想卸載微信了


        5T技術(shù)資源大放送!包括但不限于:C/C++,Linux,Python,Java,PHP,人工智能,單片機(jī),樹(shù)莓派,等等。在公眾號(hào)內(nèi)回復(fù)「1024」,即可免費(fèi)獲取?。?/span>


        瀏覽 31
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        評(píng)論
        圖片
        表情
        推薦
        點(diǎn)贊
        評(píng)論
        收藏
        分享

        手機(jī)掃一掃分享

        分享
        舉報(bào)
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            交H调教h女子学校 | 九一操逼 | 国产一及黄色 | 嗯~用力啊~嗯~c我~视频 | 色屁屁在线 | 逼特逼进入网站 | 欧美一区二区最爽乱婬视频免费看 | 日韩无码AV片 | 蜜桃网站在线观看 | 大奶无码|