1.服務器端初始化:html
建立socket => 設置端口複用 => 綁定socket與服務器地址 => 若是未指定端口,動態分配 => 監聽
int on = 1; unsigned int port = 4000; struct sockaddr_in name; int lfd = socket(PF_INET, SOCK_STREAM, 0); //建立socket memset(&name, 0, sizeof(name)); //初始化name name.sin_family = AF_INET; name.sin_port = htons(*port); name.sin_addr.s_addr = htonl(INADDR_ANY); setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof(on)) //端口複用 bind(lfd, (struct sockaddr *)&name, sizeof(name)) if (*port == 0) { //動態分配端口 socklen_t namelen = sizeof(name); getsockname(lfd, (struct sockaddr *)&name, &namelen); *port = ntohs(name.sin_port); } listen(httpd, 5);
2.服務器阻塞等待客戶端鏈接,一旦鏈接,開闢一個線程並執行對應響應函數:git
while(1) { client_sock = accept(server_sock, (struct sockaddr*)&client_name, &client_name_len); printf("received from %s at PORT %d\n", inet_ntop(AF_INET, &client_name.sin_addr, str, sizeof(str)), ntohs(client_name.sin_port)); pthread_create(&newthread , NULL, (void *)accept_request, (void *)(intptr_t)client_sock); }
1.獲取請求方法:github
上圖所示是一個http請求的報文格式,請求方法是指上圖中請求行中第一個字段。服務器
int i = 0; char buf[1024]; char method[255]; /*get_line返回獲取到的字節數*/ int numchars = get_line(client_sock, buf, sizeof(buf)); /*不爲空,以及未到達上限,將buf中Get或Post拷貝進method*/ while (!isspace((int)buf[i]) && (i < sizeof(method) - 1)) { method[i] = buf[i]; i++; } method[i] = '\0';
注意:若是既不是post也不是get請求,報錯退出;另外值得注意的是報文中的空格值爲"rn"。 socket
2.獲取url: 函數
上圖所示是一個常見的URL,而請求行中的url指的是上圖resource path,在請求頭部中會包含host字段。post
int j = i; i = 0; char url[255]; while (isspace((int)buf[j]) && (j < numchars)) j++; //跳過空格 /*取出報文頭的url,該url不包含host*/ while (!isspace((int)buf[j]) && (i < sizeof(url) - 1) && (j < numchars)) { url[i] = buf[j]; i++; j++; } url[i] = '\0';
3.設置cgi值(能夠理解爲一個標誌位):url
4.組合url,讓其指向服務器上的一個絕對路徑(path),若是path是一個目錄(文件夾),修改path指向默認的主頁地址,path = host+url。 spa
5.使用stat函數綁定path:線程
失敗,即路徑錯誤,將剩餘請求內容讀出並丟棄,返回404錯誤;
成功,若 cgi != 1 (get無參請求), 直接將path內容讀出並send回客戶端;若**cgi == 1, 執行cgi腳本,函數參數:client_sock, path, method, query(get請求的請求參數)。
1.什麼是cgi:
帶有參數的get請求,和post請求,服務器沒有辦法簡單的返回文件的內容, 服務器須要將對應的的頁面找出來,再send給客戶端。這個就須要cgi來幫忙,cgi能夠理解爲是服務器端可執行的小腳本。服務器收到這個請求以後,執行.cgi文件,這個文件是提早寫好了,專門來處理這樣的請求,而後獲得相應的網頁數據,再send給客戶端。
2.判斷是什麼請求:
get: 讀出剩餘報文請求頭內容並丟棄,直到遇到兩個換行符,後面再讀就是請求數據;
post: 讀報文請求頭部中的 content-length字段,判斷是否有有效內容,該字段的值爲post請求的數據長度。
//post請求,獲取請求主體數據長度 int numchars = get_line(client_sock, buf, sizeof(buf)); while ((numchars > 0) && strcmp("\n", buf)) { /*Content-Length:這個字符串一共長爲15位, 因此取出頭部一句後,將第16位設置結束符, 進行比較,第16位置爲結束*/ buf[15] = '\0'; if (strcasecmp(buf, "Content-Length:") == 0) content_length = atoi(&(buf[16])); numchars = get_line(client_sock, buf, sizeof(buf)); //剩餘頭內容讀出丟棄 } if (content_length == -1) { bad_request(client); return; }
3.建立兩個管道並fork:
pipe(cgi_output); //輸出 pipe(cgi_input); //輸入 pid = fork(); //建立進程
4.子進程:
char meth_env[255]; char query_env[255]; char length_env[255]; dup2(cgi_output[1], STDOUT); //複製輸出讀端到stdout dup2(cgi_input[0], STDIN); //複製輸入讀端到stdin close(cgi_output[0]); //輸出讀 close(cgi_input[1]); //輸入寫 //CGI環境變量 sprintf(meth_env, "REQUEST_METHOD=%s", method); putenv(meth_env); if (strcasecmp(method, "GET") == 0) { //get請求 sprintf(query_env, "QUERY_STRING=%s", query_string); putenv(query_env); } else { //post請求 sprintf(length_env, "CONTENT_LENGTH=%d", content_length); putenv(length_env); } execl(path, NULL); exit(0); //子進程退出
5.父進程:
close(cgi_output[1]); //輸出寫 close(cgi_input[0]); //輸入讀 if(strcasecmp(method, "POST") == 0) { //經過cgi_input[1](寫端)寫入到CGI的標準輸入 for (int i = 0; i < content_length; i++) { recv(client_sock, &c, 1, 0); write(cgi_input[1], &c, 1); } } //讀取CGI的標準輸出,發送到客戶端 while (read(cgi_output[0], &c, 1) > 0) send(client_sock, &c, 1, 0); close(cgi_output[0]); close(cgi_input[1]); waitpid(pid, &status, 0);
1.get_line:讀取一行數據
int get_line(int sock, char *buf, int size) { int n; int i = 0; char c = '\0'; while ((i < size - 1) && (c != '\n')) { //從sock中讀取一個字節 n = recv(sock, &c, 1, 0); if (n > 0) { // 將 \r\n 或 \r 轉換爲'\n' if (c == '\r') { // 讀到了'\r'就再預讀一個字節 n = recv(sock, &c, 1, MSG_PEEK); // 若是讀取到的是'\n',就讀取,不然c='\n' if ((n > 0) && (c == '\n')) recv(sock, &c, 1, 0); else c = '\n'; } // 讀取數據放入buf buf[i] = c; i++; } else c = '\n'; } buf[i] = '\0'; return(i); // 返回寫入buf的字節數 }