第二十四章(制作HTTP服務器端)學習筆記


2019-11-19

17:54:00

參考:https://github.com/riba2534/TCP-IP-NetworkNote/tree/master/ch24

 

 

24.1 HTTP 概要

本章將編寫 HTTP(HyperText Transfer Protocol,超文本傳輸協議)服務器端,即 Web 服務器端。

24.1.1 理解 Web 服務器端

web服務器端就是要基於 HTTP 協議,將網頁對應文件傳輸給客戶端的服務器端

 

 

24.1.2 HTTP

無狀態的 Stateless 協議

從上圖可以看出,服務器端相應客戶端請求后立即斷開連接。換言之,服務器端不會維持客戶端狀態。即使同一客戶端再次發送請求,服務器端也無法辨認出是原先那個,而會以相同方式處理新請求。因此,HTTP 又稱「無狀態的 Stateless 協議」

 

 

24.1.3 請求消息(Request Message)的結構

下面是客戶端向服務端發起請求消息的結構:

從圖中可以看出,請求消息可以分為請求頭、消息頭、消息體 3 個部分。其中,請求行含有請求方式(請求目的)信息。典型的請求方式有 GET 和 POST ,GET 主要用於請求數據,POST 主要用於傳輸數據。為了降低復雜度,我們實現只能響應 GET 請求的 Web 服務器端,下面解釋圖中的請求行信息。其中「GET/index.html HTTP/1.1」 具有如下含義:

請求(GET)index.html 文件,通常以 1.1 版本的 HTTP 協議進行通信。

請求行只能通過 1 行(line)發送,因此,服務器端很容易從 HTTP 請求中提取第一行,並分別分析請求行中的信息。

請求行下面的消息頭中包含發送請求的瀏覽器信息、用戶認證信息等關於 HTTP 消息的附加信息。最后的消息體中裝有客戶端向服務端傳輸的數據,為了裝入數據,需要以 POST 方式發送請求。但是我們的目標是實現 GET 方式的服務器端,所以可以忽略這部分內容。另外,消息體和消息頭與之間以空行隔開,因此不會發生邊界問題

24.1.4 響應消息(Response Message)的結構

下面是 Web 服務器端向客戶端傳遞的響應信息的結構。從圖中可以看出,該響應消息由狀態行、頭信息、消息體等 3 個部分組成。狀態行中有關於請求的狀態信息,這是與請求消息相比最為顯著地區別。

第一個字符串狀態行中含有關於客戶端請求的處理結果。例如,客戶端請求 index.html 文件時,表示 index.html 文件是否存在、服務端是否發生問題而無法響應等不同情況的信息寫入狀態行。圖中的「HTTP/1.1 200 OK」具有如下含義:

  • 200 OK : 成功處理了請求!
  • 404 Not Found : 請求的文件不存在!
  • 400 Bad Request : 請求方式錯誤,請檢查!

消息頭中含有傳輸的數據類型和長度等信息。圖中的消息頭含有如下信息:

服務端名為 SimpleWebServer ,傳輸的數據類型為 text/html。數據長度不超過 2048 個字節。

最后插入一個空行后,通過消息體發送客戶端請求的文件數據。以上就是實現 Web 服務端過程中必要的 HTTP 協議。

24.2 實現簡單的 Web 服務器端

24.2.1 實現基於 Windows 的多線程 Web 服務器端

暫略

24.2.2 實現基於 Linux 的多線程 Web 服務器端

webserv_linux.c

  1 #include <stdio.h>
  2 #include <stdlib.h>
  3 #include <unistd.h>
  4 #include <string.h>
  5 #include <arpa/inet.h>
  6 #include <sys/socket.h>
  7 #include <pthread.h>
  8 
  9 #define BUF_SIZE 1024
 10 #define SMALL_BUF 100
 11 
 12 void *request_handler(void *arg);
 13 void send_data(FILE *fp, char *ct, char *file_name);
 14 char *content_type(char *file);
 15 void send_error(FILE *fp);
 16 void error_handling(char *message);
 17 
 18 int main(int argc, char *argv[])
 19 {
 20     int serv_sock, clnt_sock;
 21     struct sockaddr_in serv_adr, clnt_adr;
 22     int clnt_adr_size;
 23     char buf[BUF_SIZE];
 24     pthread_t t_id;
 25     if (argc != 2)
 26     {
 27         printf("Usage : %s <port>\n", argv[0]);
 28         exit(1);
 29     }
 30 
 31     serv_sock = socket(PF_INET, SOCK_STREAM, 0);
 32     memset(&serv_adr, 0, sizeof(serv_adr));
 33     serv_adr.sin_family = AF_INET;
 34     serv_adr.sin_addr.s_addr = htonl(INADDR_ANY);
 35     serv_adr.sin_port = htons(atoi(argv[1]));
 36     if (bind(serv_sock, (struct sockaddr *)&serv_adr, sizeof(serv_adr)) == -1)
 37         error_handling("bind() error");
 38     if (listen(serv_sock, 20) == -1)
 39         error_handling("listen() error");
 40 
 41     while (1)
 42     {
 43         clnt_adr_size = sizeof(clnt_adr);
 44         clnt_sock = accept(serv_sock, (struct sockaddr *)&clnt_adr, &clnt_adr_size);
 45         printf("Connection Request : %s:%d\n",
 46                inet_ntoa(clnt_adr.sin_addr), ntohs(clnt_adr.sin_port));
 47         pthread_create(&t_id, NULL, request_handler, &clnt_sock);
 48         pthread_detach(t_id);
 49     }
 50     close(serv_sock);
 51     return 0;
 52 }
 53 
 54 void *request_handler(void *arg)
 55 {
 56     int clnt_sock = *((int *)arg);
 57     char req_line[SMALL_BUF];
 58     FILE *clnt_read;
 59     FILE *clnt_write;
 60 
 61     char method[10];
 62     char ct[15];
 63     char file_name[30];
 64 
 65     clnt_read = fdopen(clnt_sock, "r");
 66     clnt_write = fdopen(dup(clnt_sock), "w");
 67     fgets(req_line, SMALL_BUF, clnt_read);
 68     if (strstr(req_line, "HTTP/") == NULL)
 69     {
 70         send_error(clnt_write);
 71         fclose(clnt_read);
 72         fclose(clnt_write);
 73         return;
 74     }
 75     strcpy(method, strtok(req_line, " /"));
 76     strcpy(file_name, strtok(NULL, " /"));
 77     strcpy(ct, content_type(file_name));
 78     if (strcmp(method, "GET") != 0)
 79     {
 80         send_error(clnt_write);
 81         fclose(clnt_read);
 82         fclose(clnt_write);
 83         return;
 84     }
 85     fclose(clnt_read);
 86     send_data(clnt_write, ct, file_name);
 87 }
 88 void send_data(FILE *fp, char *ct, char *file_name)
 89 {
 90     char protocol[] = "HTTP/1.0 200 OK\r\n";
 91     char server[] = "Server:Linux Web Server \r\n";
 92     char cnt_len[] = "Content-length:2048\r\n";
 93     char cnt_type[SMALL_BUF];
 94     char buf[BUF_SIZE];
 95     FILE *send_file;
 96 
 97     sprintf(cnt_type, "Content-type:%s\r\n\r\n", ct);
 98     send_file = fopen(file_name, "r");
 99     if (send_file == NULL)
100     {
101         send_error(fp);
102         return;
103     }
104 
105     //傳輸頭信息
106     fputs(protocol, fp);
107     fputs(server, fp);
108     fputs(cnt_len, fp);
109     fputs(cnt_type, fp);
110 
111     //傳輸請求數據
112     while (fgets(buf, BUF_SIZE, send_file) != NULL)
113     {
114         fputs(buf, fp);
115         fflush(fp);
116     }
117     fflush(fp);
118     fclose(fp);
119 }
120 char *content_type(char *file)
121 {
122     char extension[SMALL_BUF];
123     char file_name[SMALL_BUF];
124     strcpy(file_name, file);
125     strtok(file_name, ".");
126     strcpy(extension, strtok(NULL, "."));
127 
128     if (!strcmp(extension, "html") || !strcmp(extension, "htm"))
129         return "text/html";
130     else
131         return "text/plain";
132 }
133 void send_error(FILE *fp)
134 {
135     char protocol[] = "HTTP/1.0 400 Bad Request\r\n";
136     char server[] = "Server:Linux Web Server \r\n";
137     char cnt_len[] = "Content-length:2048\r\n";
138     char cnt_type[] = "Content-type:text/html\r\n\r\n";
139     char content[] = "<html><head><title>NETWORK</title></head>"
140                      "<body><font size=+5><br>發生錯誤! 查看請求文件名和請求方式!"
141                      "</font></body></html>";
142     fputs(protocol, fp);
143     fputs(server, fp);
144     fputs(cnt_len, fp);
145     fputs(cnt_type, fp);
146     fflush(fp);
147 }
148 void error_handling(char *message)
149 {
150     fputs(message, stderr);
151     fputc('\n', stderr);
152     exit(1);
153 }

 

 經過測試,這個簡單的 HTTP 服務器可以正常的顯示出頁面。

 

24.3 習題

以下答案僅代表本人個人觀點,可能不是正確答案。

  1. 下列關於 Web 服務器端和 Web 瀏覽器端的說法錯誤的是?

    答:以下加粗選項代表正確。

    1. Web 瀏覽器並不是通過自身創建的套接字連接服務端的客戶端
    2. Web 服務器端通過 TCP 套接字提供服務,因為它將保持較長的客戶端連接並交換數據
    3. 超文本與普通文本的最大區別是其具有可跳轉的特性
    4. Web 瀏覽器可視為向瀏覽器提供請求文件的文件傳輸服務器端
    5. 除 Web 瀏覽器外,其他客戶端都無法訪問 Web 服務器端。
  2. 下列關於 HTTP 協議的描述錯誤的是?

    答:以下加粗選項代表正確。

    1. HTTP 協議是無狀態的 Stateless 協議,不僅可以通過 TCP 實現,還可以通過 UDP 來實現
    2. HTTP 協議是無狀態的 Stateless 協議,因為其在 1 次請求和響應過程完成后立即斷開連接。因此,如果同一服務器端和客戶端需要 3 次請求及響應,則意味着需要經過 3 次套接字的創建過程。
    3. 服務端向客戶端傳遞的狀態碼中含有請求處理結果的信息。
    4. HTTP 協議是基於因特網的協議,因此,為了同時向大量客戶端提供服務,HTTP 協議被設計為 Stateless 協議。

 

 

 

 

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM