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

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

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

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

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

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

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

生成一個響應(yīng)報文

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

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

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

      __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此時并不是標(biāo)準(zhǔn)的格式字符串,但這個變量里面有我們需要的資源

          /* 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ù),其實此處可以將這些語句合并起來寫,而不是分別調(diào)用,十分浪費。但這么寫比較清晰

    其中在生成時間的時候,我使用的是預(yù)定義好的靜態(tài)字符串?dāng)?shù)組來幫助我,可很好的猜到這些date_xxx數(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ù)器上的實體資源,那就找到它 */
          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;
      }
  • 上面有幾個函數(shù)調(diào)用open, mmap, munmap,學(xué)過Linux系統(tǒng)編程的人肯定知道,這是共享內(nèi)存的一種最簡單高效的方式。
  • 看不太懂的可以去查詢APUE或者網(wǎng)上資源很多,這是很重要的一個知識點。大致的功能就是將一個文件打開,并映射到內(nèi)存中,這個內(nèi)存可以在多個進(jìn)程間共享MAP_SHARED也可以不共享MAP_PRIVATE,這樣我們就能像數(shù)組一樣對其進(jìn)行讀取操作了。

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

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

發(fā)送報文

  • 那這個就簡單很多了,直接貼上代碼

      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;
                  }
                  /* 在這個地方就是前面所說的那個EPIPE錯誤 */
                  else /* if (EPIPE == errno) */
                      /* 對端關(guān)閉了連接 */
                      return HANDLE_WRITE_FAILURE;
              }
              else if (0 == count)
                  return HANDLE_WRITE_FAILURE;
              nbyte -= count;
          }
          return HANDLE_WRITE_SUCCESS;
      }
  • 就是這么簡單,因為實在是沒有其他工作可以做了
    • 嘗試發(fā)送所有,直到發(fā)送完全部數(shù)據(jù),或者發(fā)送緩沖區(qū)不夠,那就等待下次發(fā)送,這個通過epoll很容易就實現(xiàn)了。
    • 如果發(fā)現(xiàn)對面的不在了,直接關(guān)閉就好啦。

附加

小結(jié)

  • 其實也是拖拖拉拉地在不斷地寫這些東西
  • 也還是因為時間不多的原因,一直想抽一個連貫的時間,結(jié)果一拖就是半年,所以做事一定要當(dāng)機(jī)立斷,當(dāng)然要經(jīng)過腦子??雌饋硗γ?/li>
  • 寫到這里,算是給自己的求學(xué)之路一個挺好的交代,因為至少將自己知道的都寫了出來,對我也好,對其他人也好,至少挺安心的。
  • 無論如何都要感謝一下互聯(lián)網(wǎng),學(xué)校圖書館的館藏和薦購權(quán)限。
  • 不知道我這些東西有多少能幫助到看的人,但我知道一定會有影響,也一定有不好的地方,但是我不怕,就怕沒人和我說我錯在哪里。
  • 接下來我想做的事就是用剩下的一年里去互聯(lián)網(wǎng),IT的各個大領(lǐng)域?qū)嵙?xí),見見世面,心中還是有鴻鵠之志的。
  • 這本書也就到此為止了

題外話

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