HTTP協議(下):HTTP協議實現通信

1. 概述

服務器的開發不容易,尤其是開發高性能、穩定性好服務器,更加不容易,因此人們嘗試更好簡單的方式來開發軟件。

在服務器方面,使用Web服務器,採用HTTP協議來代替底層的socket,是常見的選擇。採用HTTP協議更加除了能得到穩定的服務器支持外,更加可以兼容各種客戶端(手機、PC、瀏覽器)等等。這樣實現了一個服務器之後,多個客戶端可以通用。

2.通信過程

這裏寫圖片描述

HTTP 協議採用請求/響應模型。客戶端向服務器發送一個請求報文,服務器以一個狀態作爲響應。

HTTP 請求/響應的步驟:

  • 客戶端連接到web服務器:HTTP 客戶端與web服務器建立一個 TCP 連接;
  • 客戶端向服務器發起 HTTP 請求:通過已建立的TCP 連接,客戶端向服務器發送一個請求報文;
  • 服務器接收 HTTP 請求並返回 HTTP 響應:服務器解析請求,定位請求資源,服務器將資源副本寫到 TCP 連接,由客戶端讀取;
  • 釋放 TCP 連接:若connection 模式爲close,則服務器主動關閉TCP 連接,客戶端被動關閉連接,釋放TCP 連接;若connection 模式爲keepalive,則該連接會保持一段時間,在該時間內可以繼續接收請求;
  • 客戶端瀏覽器解析HTML內容:客戶端將服務器響應的 html 文本解析並顯示。

3. 測試代碼

#include <stdio.h>  
#include <stdlib.h>  
#include <string.h>                         
#include <unistd.h>  
#include <fcntl.h>
#include <sys/socket.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>

int main()
{
     // 創建通信端點:套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);  
    if(sockfd < 0)  
    {  
        perror("socket");  
        return -1;  
    }  

    // 設置本地地址結構體  
    struct sockaddr_in my_addr;  
    bzero(&my_addr, sizeof(my_addr));   // 清空      
    my_addr.sin_family = AF_INET;       // ipv4  
    my_addr.sin_port   = htons(8000);   // 端口  
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip  

    // 綁定  
    int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));  
    if( err_log != 0)  
    {  
        perror("binding");  
        close(sockfd);        
        return -1;  
    }  

    err_log = listen(sockfd, 10); // 監聽,監聽套接字改爲被動  
    if(err_log != 0)  
    {  
        perror("listen");  
        close(sockfd);        
        return -1;    
    }     

    printf("listen client @port=%d...\n", 8000); 

    int connfd;  
    connfd = accept(sockfd, NULL, NULL);    // 等待連接  

    char recv_buf[8*1024] = {0};
    read(connfd, recv_buf, sizeof(recv_buf));
    printf("%s", recv_buf);

    //獲取客戶端需要的網頁內容
    char filename[200] = {0};
    sscanf(recv_buf, "GET /%[^ ]", filename); //獲取文件名字
    printf("filename = %s\n", filename);

    int fd;
    fd = open(filename, O_RDONLY);//只讀方式打開
    if(fd < 0)//打開文件失敗
    {
        //HTTP 響應報文由狀態行、響應頭部、空行、響應包體4個部分組成
        char err[]= "HTTP/1.1 404 Not Found\r\n"    //狀態行
                    "Content-Type: text/html\r\n"   //響應頭部
                    "\r\n"                          //空行
                    "<HTML><BODY>File not found</BODY></HTML>";  //響應包體           

        perror("open"); 
        send(connfd, err, strlen(err), 0);//發送失敗的響應報文頭

        close(connfd);
        return -1;
    }   

    //HTTP 響應報文由狀態行、響應頭部、空行、響應包體4個部分組成
    char head[] = "HTTP/1.1 200 OK\r\n"     //狀態行
              "Content-Type: text/html\r\n" //響應頭部
              "\r\n";                       //空行
    send(connfd, head, strlen(head), 0); //發送成功的響應報文頭

    //發送響應包體
    int len;
    char file_buf[4 * 1024];
    //循環讀取併發送文件,讀多少,發多少
    while((len = read(fd, file_buf, sizeof(file_buf))) > 0) 
    {
        send(connfd, file_buf, len, 0);
    }

    close(fd);
    close(connfd); 

    while(1)
    {
        NULL;
    }

    return 0;
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78
  • 79
  • 80
  • 81
  • 82
  • 83
  • 84
  • 85
  • 86
  • 87
  • 88
  • 89
  • 90
  • 91
  • 92
  • 93
  • 94
  • 95
  • 96
  • 97
  • 98
  • 99

終端運行服務器程序: 

瀏覽器請求服務,得到相應內容: 

4. 簡單版Web服務器

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>

/************************************************************************
函數名稱:   int main(int argc, char *argv[])
函數功能:   通過進程創建webserver
函數參數:   int argc, char *argv[]
函數返回:   無
************************************************************************/
int main(int argc, char *argv[])
{
    unsigned short port = 8000;   //設置默認端口號
    if(argc > 1)
    {
        port = atoi(argv[1]);   //將參數2賦值給端口號變量
    }

    //創建TCP套接字
    int sockfd = socket(AF_INET, SOCK_STREAM, 0);
    if( sockfd < 0)
    {
        perror("socket");   
        exit(-1);
    }

    //服務器套接字地址變量賦值
    struct sockaddr_in my_addr;
    bzero(&my_addr, sizeof(my_addr));
    my_addr.sin_family = AF_INET;   //IPV4族
    my_addr.sin_port   = htons(port); //將端口號轉換成網絡字節序
    my_addr.sin_addr.s_addr = htonl(INADDR_ANY); //本機IP地址

    //綁定TCP套接字
    if( bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr)) != 0)
    {
        perror("bind");
        close(sockfd);      
        exit(-1);
    }

    //監聽
    if( listen(sockfd, 10) != 0)
    {
        perror("listen");
        close(sockfd);      
        exit(-1);
    }

    printf("Listenning at port=%d\n",port);   //打印端口號信息
    printf("Usage: http://127.0.0.1:%d/html/index.html\n", port);

    while(1)
    {
        char cli_ip[INET_ADDRSTRLEN] = {0};  //存放客戶端點分十進制IP地址
        struct sockaddr_in client_addr;
        socklen_t cliaddr_len = sizeof(client_addr);

        //等待客戶端連接
        int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
        printf("connfd=%d\n",connfd); //打印已連接套接字
        if(connfd > 0)
        {
            if(fork() == 0)  //創建進程並判斷返回值
            {
                close(sockfd);
                //子進程執行
                int  fd = 0;
                int  len = 0;
                char buf[1024] = "";
                char filename[50] = "";

                //將網絡字節序轉換成點分十進制形式存放在cli_ip中
                inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
                printf("connected form %s\n\r", cli_ip);   //打印點分十進制形式的客戶端IP地址
                recv(connfd, buf, sizeof(buf), 0);   //接收客戶端發送的請求內容
                sscanf(buf, "GET /%[^ ]", filename);   //解析客戶端發送請求字符串
                printf("filename=*%s*\n", filename);

                fd = open(filename, O_RDONLY);   //以只讀方式打開文件
                if( fd < 0)   //如果打開文件失敗
                {
                    //HTTP失敗頭部
                    char err[]= "HTTP/1.1 404 Not Found\r\n"
                                "Content-Type: text/html\r\n"
                                "\r\n"  
                                "<HTML><BODY>File not found</BODY></HTML>";

                    perror("open error");                   
                    send(connfd, err, strlen(err), 0);
                    close(connfd);  //關閉已連接套接字
                    exit(0);    //子進程退出
                }

                //打開文件成功後
                //接收成功時返回的頭部
                char head[]="HTTP/1.1 200 OK\r\n"
                            "Content-Type: text/html\r\n"
                            "\r\n";
                send(connfd, head, strlen(head), 0);  //發送HTTP請求成功頭部

                while( (len = read(fd, buf, sizeof(buf))) > 0)   //循環讀取文件內容
                {
                    send(connfd, buf, len, 0);       //將讀得的數據發送給客戶端
                }

                close(fd);   //成功後關閉文件
                close(connfd);   //關閉已連接套接字
                exit(0);     //子進程退出
            }
        }   

        close(connfd);   //父進程關閉連接套接字
    }
    close(sockfd);
    printf("exit main!\n");

    return 0;
}
發佈了51 篇原創文章 · 獲贊 17 · 訪問量 6萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章