在线观看不卡亚洲电影_亚洲妓女99综合网_91青青青亚洲娱乐在线观看_日韩无码高清综合久久

鍍金池/ 教程/ C/ 0x17-套接字編程-HTTP服務(wù)器(5)
0x0E-單線程備份(下)
0x11-套接字編程-1
0x05-C語(yǔ)言指針:(Volume-1)
0x13-套接字編程-HTTP服務(wù)器(1)
0x0C-開(kāi)始行動(dòng)
C 語(yǔ)言進(jìn)階
第一部分
0x05-C語(yǔ)言指針(Volume-2)
0x08-C語(yǔ)言效率(下)
0x07-C語(yǔ)言效率(上)
0x04 C代碼規(guī)范
0x0F-多線程備份
0x05-C語(yǔ)言變量
第四部分
0x16-套接字編程-HTTP服務(wù)器(4)
0x0D-單線程備份(上)
總結(jié)
0x01-C語(yǔ)言序言
0x15-套接字編程-HTTP服務(wù)器(3)
0x14-套接字編程-HTTP服務(wù)器(2)
0x17-套接字編程-HTTP服務(wù)器(5)
第三部分
我的C語(yǔ)言
0x06-C語(yǔ)言預(yù)處理器
0x09-未曾領(lǐng)略的新風(fēng)景
0x0A-C線程和Glib的視角
第二部分
0x10-網(wǎng)絡(luò)的世界
0x12-套接字編程-2
0x03-C代碼
0x0B-C語(yǔ)言錯(cuò)誤處理

0x17-套接字編程-HTTP服務(wù)器(5)

0x17-套接字編程-HTTP服務(wù)器(5)

  • 讓我們停下來(lái),回想一下之前的內(nèi)容

    1. 首先讀取配置文件,并憑此打開(kāi)服務(wù)器套接字
    2. 確定一切完備的情況下(listen),開(kāi)啟事務(wù)循環(huán)handle_loop
    3. 準(zhǔn)備好各項(xiàng)資源prepare_worker,開(kāi)啟兩種線程就真正開(kāi)始工作了
  • string_t 不打算詳細(xì)講解,因?yàn)椴⒉皇鞘裁春玫脑O(shè)計(jì),但是只需要將接口,改成C風(fēng)格的就不錯(cuò),但是有一個(gè)致命的缺點(diǎn),就是這不是二進(jìn)制字符串
    • 什么意思?就是這是一個(gè)C風(fēng)格的字符串,無(wú)法很好的存儲(chǔ)二進(jìn)制數(shù)據(jù),例如無(wú)法存儲(chǔ)\0這個(gè)字符,實(shí)際上要設(shè)計(jì)就需要重新設(shè)計(jì)。
    • 但這個(gè)小程序綽綽有余,因?yàn)橹皇亲鳛橐粋€(gè)靜態(tài)資源HTTP服務(wù)器在使用。
    • 在本章最后,會(huì)將源代碼地址貼上,僅供參考,寫的不夠嚴(yán)謹(jǐn),但還是有意義的練習(xí)。

2016-08-28 修復(fù)上述問(wèn)題,具體可以參看源碼,現(xiàn)在支持二進(jìn)制數(shù)據(jù)

萬(wàn)事開(kāi)頭難,當(dāng)你在鍵盤上打下第一句代碼的時(shí)候你就成功了。看永遠(yuǎn)都只能是談?wù)劚?,雖說(shuō)談兵也需要技術(shù)

生成一個(gè)響應(yīng)報(bào)文

  • 實(shí)際上客戶端對(duì)你怎么處理這些數(shù)據(jù)一點(diǎn)都不感興趣,他們感興趣的不就是你的響應(yīng)報(bào)文是什么嗎
  • 所以說(shuō)到了這一步就要看看這個(gè)報(bào)文的組成,但這并不是我們的重點(diǎn),簡(jiǎn)單講一下哪些屬性比較重要。
  • 還記得開(kāi)頭的時(shí)候,給出了一個(gè)報(bào)文實(shí)例,實(shí)際上最明了的莫過(guò)于在瀏覽器中摁F12后自己查看交互報(bào)文,再專業(yè)一些使用Wireshark這類專業(yè)抓包軟件也未嘗不可,以瀏覽器為例:
    • 這是個(gè)人博客上的一個(gè)背景圖的請(qǐng)求交互,重點(diǎn)看Response Headers
    • 這么一長(zhǎng)串,實(shí)際上真正必不可少的還是那么兩行
      • HTTP/1.1 200 OK
      • Content-Length: 377710
    • 前者告訴你這個(gè)球球的結(jié)果,后者告訴你請(qǐng)求的結(jié)果的內(nèi)容在哪里,即在報(bào)文中空行后多少個(gè)字節(jié)都是請(qǐng)求的結(jié)果。
  • 那在C語(yǔ)言中,或者說(shuō)在任何語(yǔ)言中,都沒(méi)什么特別好的辦法,就是用字符串構(gòu)造報(bào)文了。作為一個(gè)標(biāo)準(zhǔn)庫(kù)比較貧瘠的語(yǔ)言,這就要我們多做一點(diǎn)工作,這也是為什么要自己寫一個(gè)字符串結(jié)構(gòu)體的原因所在。

    • 當(dāng)然如果你為了兼容二進(jìn)制數(shù)據(jù),那么甚至連標(biāo)準(zhǔn)庫(kù)中的字符串函數(shù)都不能使用了,包括Linux提供的擴(kuò)展gnu99字符串函數(shù),原因是因?yàn)镃-Style字符串是以\0作為結(jié)束符的。
  • 現(xiàn)在我們規(guī)定一下,我們這個(gè)服務(wù)器的響應(yīng)報(bào)文會(huì)包含的部分

    1. 狀態(tài)行是必要的 HTTP/VER STATUS_CODE STATUS_MESSAGE\r\n
    2. 服務(wù)器時(shí)間 Date: xxx\r\n
      • 用的是UTC格式,實(shí)際上此處也可以有點(diǎn)小講究,后面提一下
    3. 資源類型 Content-Type: xxx\r\n
    4. 資源長(zhǎng)度 Content-Length: xxx\r\
    5. 連接狀態(tài) Connection: xxx\r\n
    6. 空行\r\n
    7. 資源
  • 在進(jìn)入生成報(bào)文的環(huán)節(jié)中,其實(shí)還有很多工作要做,例如判斷是什么請(qǐng)求方法是否是惡意請(qǐng)求, 獲取資源的各種信息 等,直接進(jìn)入最核心的階段make_response中的write_to_buf
  • 也就是構(gòu)造報(bào)文階段

      __thread char local_write_buf[CONN_BUF_SIZE] = {0};
      static int write_to_buf(conn_client * restrict client, // connection client message
                      const char * const * restrict status,  int rsource_size) {
      #define STATUS_CODE 0
      #define STATUS_TITLE 1
      #define STATUS_CONTENT 2
          char *   write_buf = &local_write_buf[0]; /* Local write buffer */
          string_t resource  = client->conn_res.requ_res_path; /* Resource that peer request */
          string_t w_buf     = client->w_buf;     /* Real data buffer */
          int w_count = 0;
          struct tm * utc;   /* Get GMT time Format */
          time_t      now;
          time(&now);
          utc = gmtime(&now);/* Same As before */

    utc此時(shí)并不是標(biāo)準(zhǔn)的格式字符串,但這個(gè)變量里面有我們需要的資源

          /* Construct the HTTP head */
          w_count += snprintf(write_buf+w_count, CONN_BUF_SIZE-w_count, "%s %s %s\r\n",
                      http_ver[client->conn_res.request_http_v],
                      status[STATUS_CODE], status[STATUS_TITLE]);
          w_count += snprintf(write_buf+w_count, CONN_BUF_SIZE-w_count, "Date: %s, %02d %s %d %02d:%02d:%02d GMT\r\n",
                      date_week[utc->tm_wday], utc->tm_mday,
                      date_month[utc->tm_mon], 1900+utc->tm_year,
                      utc->tm_hour, utc->tm_min, utc->tm_sec);
          w_count += snprintf(write_buf+w_count, CONN_BUF_SIZE-w_count, "Content-Type: %s\r\n", content_type[client->conn_res.content_type]);
          w_count += snprintf(write_buf+w_count, CONN_BUF_SIZE-w_count, "Content-Length: %u\r\n", 0 == rsource_size
                                                                                          ? (unsigned int)strlen(status[2]):(unsigned int)rsource_size);
          w_count += snprintf(write_buf+w_count, CONN_BUF_SIZE-w_count, "Connection: close\r\n");
          w_count += snprintf(write_buf+w_count, CONN_BUF_SIZE-w_count, "\r\n");
          write_buf[w_count] = '\0';

    從上往下依次是剛才我在上面介紹的順序,使用的是snprintf函數(shù),其實(shí)此處可以將這些語(yǔ)句合并起來(lái)寫,而不是分別調(diào)用,十分浪費(fèi)。但這么寫比較清晰

    其中在生成時(shí)間的時(shí)候,我使用的是預(yù)定義好的靜態(tài)字符串?dāng)?shù)組來(lái)幫助我,可很好的猜到這些date_xxx數(shù)組里放的都是些什么,無(wú)非就是一些時(shí)間的縮寫。

          /* 寫入緩沖區(qū) */
          append_string(w_buf, STRING(write_buf));
          client->w_buf_offset = w_count;
    
          /* If Server do not wanna to sent local file */
          if (0 == rsource_size) {  /* GET Method */
              append_string(w_buf, STRING(status[STATUS_CONTENT]));
              snprintf(write_buf+w_count, CONN_BUF_SIZE-w_count, status[2]);
              return 0;
          } else if (-1 == rsource_size) { /* HEAD Method */
              return 0;
          }
          /* 如果需要服務(wù)器上的實(shí)體資源,那就找到它 */
          int fd = open(resource->str, O_RDONLY);
          if (fd < 0) {
              return -1; /* Write again */
          }
          /* 將資源文件映射到內(nèi)存里,這樣就能很好的操作 */
          char *file_map = mmap(NULL, (size_t)rsource_size, PROT_READ, MAP_PRIVATE, fd, 0);
          if (NULL == file_map) {
              assert(file_map != NULL);
      }
          close(fd);
          /* 存入緩沖區(qū) */
          append_string(w_buf, file_map, rsource_size);
          client->w_buf_offset += rsource_size;
          munmap(file_map, (unsigned int)rsource_size);
          return 0;
      }
  • 上面有幾個(gè)函數(shù)調(diào)用open, mmap, munmap,學(xué)過(guò)Linux系統(tǒng)編程的人肯定知道,這是共享內(nèi)存的一種最簡(jiǎn)單高效的方式。
  • 看不太懂的可以去查詢APUE或者網(wǎng)上資源很多,這是很重要的一個(gè)知識(shí)點(diǎn)。大致的功能就是將一個(gè)文件打開(kāi),并映射到內(nèi)存中,這個(gè)內(nèi)存可以在多個(gè)進(jìn)程間共享MAP_SHARED也可以不共享MAP_PRIVATE,這樣我們就能像數(shù)組一樣對(duì)其進(jìn)行讀取操作了。

  • 至于make_response_page的代碼就不貼源碼了,因?yàn)榇a幾乎都是在做檢測(cè)的工作,例如安全之類的事情,以及方法分配,只需要掃一眼就能夠很清楚的理解了。

  • 在構(gòu)造完成報(bào)文之后,下一步自然就是發(fā)送它了,那我們又回到了worker_thread中去

發(fā)送報(bào)文

  • 那這個(gè)就簡(jiǎn)單很多了,直接貼上代碼

      HANDLE_STATUS handle_write(conn_client * client) {
          /* String Version */
          char*    w_buf    = client->w_buf->str;
          int      w_offset = client->w_buf_offset;
          int nbyte = w_offset;
          int count = 0;
          int fd = client->file_dsp;
          while (nbyte > 0) {
              w_buf += count;
              count = write(fd, w_buf, nbyte);
              if (count < 0) {
                  if (EAGAIN == errno || EWOULDBLOCK == errno) {
                      /* 如果發(fā)送緩沖區(qū)不夠容納所有的,那就下次再發(fā) */
                      memcpy(client->w_buf->str, w_buf, strlen(w_buf));
                      client->w_buf_offset = nbyte;
                      return HANDLE_WRITE_AGAIN;
                  }
                  /* 在這個(gè)地方就是前面所說(shuō)的那個(gè)EPIPE錯(cuò)誤 */
                  else /* if (EPIPE == errno) */
                      /* 對(duì)端關(guān)閉了連接 */
                      return HANDLE_WRITE_FAILURE;
              }
              else if (0 == count)
                  return HANDLE_WRITE_FAILURE;
              nbyte -= count;
          }
          return HANDLE_WRITE_SUCCESS;
      }
  • 就是這么簡(jiǎn)單,因?yàn)閷?shí)在是沒(méi)有其他工作可以做了
    • 嘗試發(fā)送所有,直到發(fā)送完全部數(shù)據(jù),或者發(fā)送緩沖區(qū)不夠,那就等待下次發(fā)送,這個(gè)通過(guò)epoll很容易就實(shí)現(xiàn)了。
    • 如果發(fā)現(xiàn)對(duì)面的不在了,直接關(guān)閉就好啦。

附加

小結(jié)

  • 其實(shí)也是拖拖拉拉地在不斷地寫這些東西
  • 也還是因?yàn)闀r(shí)間不多的原因,一直想抽一個(gè)連貫的時(shí)間,結(jié)果一拖就是半年,所以做事一定要當(dāng)機(jī)立斷,當(dāng)然要經(jīng)過(guò)腦子。看起來(lái)挺矛盾
  • 寫到這里,算是給自己的求學(xué)之路一個(gè)挺好的交代,因?yàn)橹辽賹⒆约褐赖亩紝懥顺鰜?lái),對(duì)我也好,對(duì)其他人也好,至少挺安心的。
  • 無(wú)論如何都要感謝一下互聯(lián)網(wǎng),學(xué)校圖書(shū)館的館藏和薦購(gòu)權(quán)限。
  • 不知道我這些東西有多少能幫助到看的人,但我知道一定會(huì)有影響,也一定有不好的地方,但是我不怕,就怕沒(méi)人和我說(shuō)我錯(cuò)在哪里。
  • 接下來(lái)我想做的事就是用剩下的一年里去互聯(lián)網(wǎng),IT的各個(gè)大領(lǐng)域?qū)嵙?xí),見(jiàn)見(jiàn)世面,心中還是有鴻鵠之志的。
  • 這本書(shū)也就到此為止了

題外話

  • 實(shí)際上也是構(gòu)思了三個(gè)月左右,我打算附加一章,用來(lái)實(shí)現(xiàn)一個(gè)數(shù)據(jù)庫(kù)系統(tǒng),在上一節(jié)也提到過(guò)。
  • 大致的想法是實(shí)現(xiàn) : SQL編譯器,數(shù)據(jù)庫(kù)存儲(chǔ)引擎,數(shù)據(jù)庫(kù)管理系統(tǒng)。至于事務(wù)的話,看看吧。覺(jué)得如果我寫下來(lái)就一定會(huì)和大家分享。謝謝給我支持的那些人。