1. 程式人生 > >http伺服器實現(二)

http伺服器實現(二)

前言

本文基於http伺服器實現(一)來完成http報文解析部分。
涉及到的內容有:

  1. http協議格式
  2. 狀態機變遷
  3. 字串解析
  4. 伺服器原始碼
  5. 客戶端原始碼和測試結果

一、http協議

這一節的重點是解析http報文,那麼首先我們必須知道http協議格式。針對協議欄位,本節程式並不涉及到每個欄位的含義,只是簡單的把這些欄位分隔開來,後續需要深入理解這些欄位的含義才能進一步實現伺服器的處理流程。這裡簡單介紹http報文格式。

一個http報文由請求行、請求頭部、空行、請求正文四部分組成,如下圖:
這裡寫圖片描述

抽象的東西一般比較難理解,這裡我用抓包工具抓了http的請求報文和響應報文。
http請求報文:

GET /adsid/integrator.js?domain=fragment.firefoxchina.cn HTTP/1.1
Host: adservice.google.com
User-Agent: Mozilla/5.0 (Windows NT 6.1; WOW64; rv:57.0) Gecko/20100101 Firefox/57.0
Accept: */*
Accept-Language: zh-CN,zh;q=0.8,zh-TW;q=0.7,zh-HK;q=0.5,en-US;q=0.3,en;q=0.2
Accept-Encoding: gzip, deflate, br
Referer: http://fragment.firefoxchina.cn/html/mp_google_banner_20171122v1.html
Connection: keep-alive Pragma: no-cache Cache-Control: no-cache

以上http報文,我們對照著前面的圖片,很容易可以看出請求行為:GET /adsid/integrator.js?domain=fragment.firefoxchina.cn HTTP/1.1請求行由請求方法、URL欄位和http協議版本欄位組成,它們之間用空格隔開。請求頭部由key/value鍵值對組成,每行一對,key和value之間用冒號”:”分隔。注意,必須在其後加上回車符和換行符”\r\n”標識。最後一個請求頭之後是一個空行,字元為回車符和換行符”\r\n”,用於標識請求頭的結束。空行下面則是請求正文。請求資料不在GET方法中使用,而是在POST中使用。上面GET的報文中無請求資料。
以下為http響應報文,可以看出和請求報文的格式差不多,由響應行、響應頭部、空行、響應資料組成。 因為在本節中不需要返回http響應報文,現階段暫不討論這些,等以後用到了會再說明。
http響應報文:

HTTP/1.1 200 OK
P3P: CP="This is not a P3P policy! See http://support.google.com/accounts/answer/151657 for more info."
Timing-Allow-Origin: *
Cache-Control: private, no-cache, no-store
Content-Type: application/javascript; charset=UTF-8
X-Content-Type-Options: nosniff
Content-Disposition: attachment; filename="f.txt"
Date: Fri, 29 Dec 2017 03:40:54 GMT
Server: cafe
X-XSS-Protection: 1; mode=block
Alt-Svc: hq="googleads.g.doubleclick.net:443"; ma=2592000; quic=51303431; quic=51303339; quic=51303338; quic=51303337; quic=51303335,quic="googleads.g.doubleclick.net:443"; ma=2592000; v="41,39,38,37,35",hq=":443"; ma=2592000; quic=51303431; quic=51303339; quic=51303338; quic=51303337; quic=51303335,quic=":443"; ma=2592000; v="41,39,38,37,35"
Content-Length: 108

processGoogleToken({"newToken":"NT","validLifetimeSecs":0,"freshLifetimeSecs":3600,"1p_jar":"","pucrd":""});

二、狀態機變遷

伺服器讀取HTTP頭部採用的是狀態機,下面來看看程式碼大致怎麼實現。

//解析http頭。引數buff為客戶端傳來的字串,len為字串的長度。成功返回0。
int header_parse(char *buff, int len)
{
    char *check = buff; //指向要解析的字串
    int status = READ_HEADER;//狀態機的標誌位
    int parse_pos = 0;//表示解析過的位元組數
    while (check < (buff + len)) {//一直解析到http請求頭結束
        switch (status) {
        case READ_HEADER:
            if (*check == '\r') {
                status = ONE_CR;
                line_end = check;
            } else if (*check == '\n') {
                status = ONE_LF;
                line_end = check;
            }
            break;

        case ONE_CR:
            if (*check == '\n')
                 status = ONE_LF;
            else if (*check != '\r')
                 status = READ_HEADER;
            break;

        case ONE_LF:
            /* if here, we've found the end (for sure) of a header */
            if (*check == '\r') /* could be end o headers */
                status = TWO_CR;
            else if (*check == '\n')
                status = BODY_READ;
            else
                status = READ_HEADER;
            break;

        case TWO_CR:
            if (*check == '\n')
                status = BODY_READ;
            else if (*check != '\r')
                status = READ_HEADER;
            break;

        default:
            break;
        }

        parse_pos++; //更新解析位元組數
        check++;    //更新解析位置
        //解析到"\r\n"後進入
        if (status == ONE_LF) {
            //以"\r\n"分隔開的認為是一行,這裡將進行請求頭一行的讀取和處理
        } else if (status == BODY_READ){
            //這裡將進行http請求正文的讀取和處理
            //處理部分省略。。。。。。
            PARSE_HEAD_OPTION = 0;//解析完請求頭部之後置0,為下一個客戶端做好準備。
            return 0;
        }
    } 
    return 0;
}

以上程式,是對HTTP頭的每個位元組逐一進行解析,狀態機流程如下:
一開始狀態標誌status為READ_HEADER

  1. 如果收到“\r”切到ONE_CR 態
  2. 如果收到“\n”則切為 ONE_LF,每次狀態為ONE_LF時,則對請求頭進行解析。

當status為ONE_CR時

  1. 如果收到“\n”則切為 ONE_LF 態
  2. 如果收到“\r”則切為初始態 READ_HEADER

當status為ONE_LF態時

  1. 如果收到“\r”則切為 TWO_CR 態
  2. 如果收到“\n”則收到兩個換行符,此時後面內容為HTTP BODY,切為BODY_READ態
  3. 如果收到其他字元,切回初始態READ_HEADER

當status為TWO_CR態時

  1. 如果收到“\n”則表示已經收到兩個“\r\n”,後面內容為BODY,則切為BODY_READ態
  2. 如果收到“\r”則切為初始態BODY_READ

每次狀態為ONE_LF時,將對http請求頭進行解析,主要是涉及到了字串的解析。下面將對字串解析進行說明。

三、字串解析

字串解析分為兩部分,一部分是HTTP請求行的解析,另一部分是HTTP請求頭部的解析。我們先來看請求行的解析,程式碼如下:

//函式主要用來解析HTTP請求行,引數buff指向HTTP請求行開始的位置,成功返回0,失敗返回-1
int process_logline(char *buff)
{
    static char *SIMPLE_HTTP_VERSION = "HTTP/0.9";
    int method;//用於獲取http請求行的方法,GET或HEAD或POST
    char request_uri[MAX_HEADER_LENGTH + 1]; // 用於獲取客戶端請求的uri
    char *http_version = SIMPLE_HTTP_VERSION;

    char *stop, *stop2;
    char *logline = buff;
    if (!memcmp(logline, "GET ", 4)){
        method = M_GET;
        printf("http method = GET\n");
    }
    else if (!memcmp(logline, "HEAD ", 5)){
        /* head is just get w/no body */
        method = M_HEAD;
        printf("http method = HEAD\n");
    }
    else if (!memcmp(logline, "POST ", 5)){
        method = M_POST;
        printf("http method = POST\n");
    }
    else {
        perror("malformed request\n");
        return -1;
    }
    PARSE_HEAD_OPTION = 1;//設定解析http頭選項的標誌位

    /* Guaranteed to find ' ' since we matched a method above */
    stop = logline + 3;
    if (*stop != ' ')
        ++stop;

    /* scan to start of non-whitespace */
    while (*(++stop) == ' ');

    stop2 = stop;

    /* scan to end of non-whitespace */
    while (*stop2 != '\0' && *stop2 != ' ')
        ++stop2;

    if (stop2 - stop > MAX_HEADER_LENGTH) {
        perror("URI too long");
        return -1;
    }
    memcpy(request_uri, stop, stop2 - stop);
    request_uri[stop2 - stop] = '\0';
    printf("request uri = %s\n",request_uri);
    if (*stop2 == ' ') {
        /* if found, we should get an HTTP/x.x */
        unsigned int p1, p2;

        /* scan to end of whitespace */
        ++stop2;
        while (*stop2 == ' ' && *stop2 != '\0')
            ++stop2;

        /* scan in HTTP/major.minor */
        if (sscanf(stop2, "HTTP/%u.%u", &p1, &p2) == 2) {
            /* HTTP/{0.9,1.0,1.1} */
            if (p1 == 1 && (p2 == 0 || p2 == 1)) {
                http_version = stop2;
                printf("http version = %s\n",http_version);
            } else if (p1 > 1 || (p1 != 0 && p2 > 1)) {
                goto BAD_VERSION;
            }
        } else {
            goto BAD_VERSION;
        }
    }

    return 0;

BAD_VERSION:
    perror("bogus HTTP version");
    return -1;
}

以上程式,依次解析出請求方法、URL欄位和http協議版本欄位。
為了比較形象的理解,我這裡舉個例子,比如函式引數buff指向的字串為:
GET /html/mp_google_banner_20171122v1.html HTTP/1.1

將在終端列印如下:

http method = GET
request uri = /html/mp_google_banner_20171122v1.html
http version = HTTP/1.1

接下來,看看HTTP請求頭部的解析,程式碼如下:

//格式化字串為大寫字母
char *to_upper(char *str)
{
    char *start = str;
    while (*str) {
        if (*str == '-')
            *str = '_';
        else
            *str = toupper(*str);

        str++;
    }
    return start;
}
//函式用來解析http請求頭,引數buff為指向請求頭的行首。
int process_option_line(char *buff)
{
    char *if_modified_since;    /* If-Modified-Since */
    char *content_type;
    char *content_length;
    char *keepalive;
    char *header_referer;
    char *header_user_agent;

    char c, *value, *line = buff;

    value = strchr(line, ':');
    if (value == NULL)
        return 0;//表示解析結束
    *value++ = '\0';            /* overwrite the : */
    to_upper(line);             /* header types are case-insensitive */
    while ((c = *value) && (c == ' ' || c == '\t'))
        value++;

    if (!memcmp(line, "IF_MODIFIED_SINCE", 18)){
        if_modified_since = value;
        printf("IF_MODIFIED_SINCE:%s\n",if_modified_since);
    }
    else if (!memcmp(line, "CONTENT_TYPE", 13)){
        content_type = value;
        printf("CONTENT_TYPE:%s\n",content_type);
    }
    else if (!memcmp(line, "CONTENT_LENGTH", 15)){
        content_length = value;
        printf("CONTENT_LENGTH:%s\n",content_length);
    }
    else if (!memcmp(line, "CONNECTION", 11) ) {         
        keepalive = value;
        printf("CONNECTION:%s\n",keepalive);
    }
    else if (!memcmp(line, "REFERER", 8)) {
        header_referer = value;
        printf("REFERER:%s\n",header_referer);
    } 
    else if (!memcmp(line, "USER_AGENT", 11)) {
        header_user_agent = value;
        printf("USER_AGENT:%s\n",header_user_agent);
    }
    return 0;
}

從以上可以看出,process_option_line函式非常簡單,先呼叫to_upper函式來格式化字串,然後呼叫strchr(line, ‘:’)函式來分離類似於字串CONNECTION:keep-alive。我們這裡分離出選項和選項的值之後,只是簡單地打印出來。這裡其實應該定義一個全域性的結構體,然後把這些選項值儲存到結構體裡面,因為在後續處理http請求會根據這些選項做出相對應的處理。在後續的文章中會實現這一部分。

四、伺服器程式實現

前面已經把該實現的基本實現了,現在貼出原始碼,正如前面所說,只是實現瞭解析http協議的部分,怎麼處理還沒完成。程式碼有點長,運用的是http伺服器實現(一)的框架。如果前面程式看懂了,這裡基本就能理解了,畢竟大部分重複了。

/*
 *  web-server2.c, an http server
 */
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netinet/in.h>
#include <errno.h>

#define BUFFER_SIZE 4096
#define MAX_QUE_CONN_NM 5
#define PORT 6000
//#define MAXSOCKFD     10
#define FILE_NAME_MAX 512
#define SOCKADDR sockaddr_in
#define S_FAMILY sin_family
#define SERVER_AF AF_INET

#define MAX_HEADER_LENGTH           1024
/****************** METHODS *****************/
#define M_GET       1
#define M_HEAD      2
#define M_PUT       3
#define M_POST      4
#define M_DELETE    5
#define M_LINK      6
#define M_UNLINK    7

/************** REQUEST STATUS (req->status) ***************/
#define READ_HEADER             0
#define ONE_CR                  1
#define ONE_LF                  2
#define TWO_CR                  3
#define BODY_READ               4
#define BODY_WRITE              5
#define WRITE                   6
#define PIPE_READ               7
#define PIPE_WRITE              8
#define DONE                    9
#define DEAD                   10

int PARSE_HEAD_OPTION;//解析http頭選項的標誌位,為1表示可以解析了
fd_set block_read_fdset;
int max_fd;
#define BOA_FD_SET(fd, where) { FD_SET(fd, where); \
    if (fd > max_fd) max_fd = fd; \
    }

void select_loop(int server_s);
int process_requests(int server_s);

int header_parse(char *buff, int len);
int process_logline(char *buff);
int process_option_line(char *buff);
char *to_upper(char *str);

int main(int argc,char* argv[])
{
    int sockfd;
    int sin_size = sizeof(struct sockaddr);
    struct sockaddr_in server_sockaddr, client_sockaddr;
    int i = 1;/* 使得重複使用本地地址與套接字進行繫結 */

    /*建立socket連線*/
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0))== -1)
    {
        perror("socket");
        exit(1);
    }
    printf("Socket id = %d\n",sockfd);

    /*設定sockaddr_in 結構體中相關引數*/
    server_sockaddr.sin_family = AF_INET;
    server_sockaddr.sin_port = htons(PORT);
    server_sockaddr.sin_addr.s_addr = INADDR_ANY;
    bzero(&(server_sockaddr.sin_zero), 8);

    setsockopt(sockfd, SOL_SOCKET, SO_REUSEADDR, &i, sizeof(i));

    /*繫結函式bind*/
    if (bind(sockfd, (struct sockaddr *)&server_sockaddr, sizeof(struct sockaddr))== -1)
    {
        perror("bind");
        exit(1);
    }
    printf("Bind success!\n");

    /*呼叫listen函式*/
    if (listen(sockfd, MAX_QUE_CONN_NM) == -1)
    {
        perror("listen");
        exit(1);
    }
    printf("Listening....\n");
    select_loop(sockfd);
    //recv_mul_file(sockfd);
    //close(sockfd);
    return 0;
}

void select_loop(int server_s)
{
    FD_ZERO(&block_read_fdset);

    /* preset max_fd */
    max_fd = server_s+1;

    while (1) {

        BOA_FD_SET(server_s, &block_read_fdset); /* server always set */
        //沒有可讀的檔案描述符,就阻塞。
        if (select(max_fd + 1, &block_read_fdset,NULL, NULL,NULL) == -1) {
            /* what is the appropriate thing to do here on EBADF */
            if (errno == EINTR)
                continue;   /* while(1) */
            else if (errno != EBADF) {
                perror("select");
            }
        }
        if (FD_ISSET(server_s, &block_read_fdset))
            process_requests(server_s);

    }
}

int process_requests(int server_s)
{
    int fd;                     /* socket */
    struct SOCKADDR remote_addr; /* address */
    int remote_addrlen = sizeof (struct SOCKADDR);
    size_t len;
    char buff[BUFFER_SIZE];
    bzero(buff,BUFFER_SIZE);
    //remote_addr.S_FAMILY = 0xdead;
    fd = accept(server_s, (struct sockaddr *) &remote_addr,
                &remote_addrlen);

    if (fd == -1) {
        if (errno != EAGAIN && errno != EWOULDBLOCK)
            /* abnormal error */
            perror("accept");
        return -1;
    }

    int bytes = read(fd, buff, BUFFER_SIZE);
    if (bytes < 0) {
        if (errno == EINTR)
            bytes = 0;
        else
            return -1;
    }
    printf("recv %d bytes from client:%s\n",bytes,buff);
    header_parse(buff,bytes);
    return 0;
}

int header_parse(char *buff, int len)
{
    char *check = buff;
    int status = READ_HEADER;
    char *line_end;//用於標記,改成指標
    char *header_line;//記錄http頭選項每一行開始的位置
    int parse_pos = 0;
    while (check < (buff + len)) {
        switch (status) {
        case READ_HEADER:
            if (*check == '\r') {
                status = ONE_CR;
                line_end = check;
            } else if (*check == '\n') {
                status = ONE_LF;
                line_end = check;
            }
            break;

        case ONE_CR:
            if (*check == '\n')
                 status = ONE_LF;
            else if (*check != '\r')
                 status = READ_HEADER;
            break;

        case ONE_LF:
            /* if here, we've found the end (for sure) of a header */
            if (*check == '\r') /* could be end o headers */
                status = TWO_CR;
            else if (*check == '\n')
                status = BODY_READ;
            else
                status = READ_HEADER;
            break;

        case TWO_CR:
            if (*check == '\n')
                status = BODY_READ;
            else if (*check != '\r')
                status = READ_HEADER;
            break;

        default:
            break;
        }

        parse_pos++;       /* update parse position */
        check++;
        //解析到每一行末後進入
        if (status == ONE_LF) {
            *line_end = '\0';

            /* terminate string that begins at req->header_line */

            if (PARSE_HEAD_OPTION) {//解析http頭選項,由key:value鍵值對組成
                if (process_option_line(header_line) == -1) {
                    perror("process_option_line error");
                    return -1;
                }
            } else {//解析http頭請求行
                if (process_logline(buff) == -1){
                    perror("process_logline error");
                    return -1;
                }
            }
            /* set header_line to point to beginning of new header */
            header_line = check;//記錄http頭選項每一行開始的位置
        } else if (status == BODY_READ){
            PARSE_HEAD_OPTION = 0;//解析完請求頭部之後置0,為下一個客戶端做好準備。
            printf("begin parse body!\n");
            return 0;
        }
    } 
    return 0;
}
char *to_upper(char *str)
{
    char *start = str;

    while (*str) {
        if (*str == '-')
            *str = '_';
        else
            *str = toupper(*str);

        str++;
    }

    return start;
}

int process_option_line(char *buff)
{
    char *if_modified_since;    /* If-Modified-Since */
    char *content_type;
    char *content_length;
    char *keepalive;
    char *header_referer;
    char *header_user_agent;

    char c, *value, *line = buff;

    value = strchr(line, ':');
    if (value == NULL)
        return 0;//表示解析結束
    *value++ = '\0';            /* overwrite the : */
    to_upper(line);             /* header types are case-insensitive */
    while ((c = *value) && (c == ' ' || c == '\t'))
        value++;

    if (!memcmp(line, "IF_MODIFIED_SINCE", 18)){
        if_modified_since = value;
        printf("IF_MODIFIED_SINCE:%s\n",if_modified_since);
    }
    else if (!memcmp(line, "CONTENT_TYPE", 13)){
        content_type = value;
        printf("CONTENT_TYPE:%s\n",content_type);
    }
    else if (!memcmp(line, "CONTENT_LENGTH", 15)){
        content_length = value;
        printf("CONTENT_LENGTH:%s\n",content_length);
    }
    else if (!memcmp(line, "CONNECTION", 11) ) {         
        keepalive = value;
        printf("CONNECTION:%s\n",keepalive);
    }
    else if (!memcmp(line, "REFERER", 8)) {
        header_referer = value;
        printf("REFERER:%s\n",header_referer);
    } 
    else if (!memcmp(line, "USER_AGENT", 11)) {
        header_user_agent = value;
        printf("USER_AGENT:%s\n",header_user_agent);
    }
    return 0;
}

int process_logline(char *buff)
{
    static char *SIMPLE_HTTP_VERSION = "HTTP/0.9";
    int method;//用於獲取http請求行的方法,GET或HEAD或POST
    char request_uri[MAX_HEADER_LENGTH + 1]; // 用於獲取客戶端請求的uri
    char *http_version = SIMPLE_HTTP_VERSION;//獲取http版本,未分配記憶體,是靜態變數,注意一下,可能會出錯

    char *stop, *stop2;
    char *logline = buff;
    if (!memcmp(logline, "GET ", 4)){
        method = M_GET;
        printf("http method = GET\n");
    }
    else if (!memcmp(logline, "HEAD ", 5)){
        /* head is just get w/no body */
        method = M_HEAD;
        printf("http method = HEAD\n");
    }
    else if (!memcmp(logline, "POST ", 5)){
        method = M_POST;
        printf("http method = POST\n");
    }
    else {
        //log_error_time();
        //fprintf(stderr, "malformed request: \"%s\"\n", req->logline);
        //send_r_not_implemented(req);
        perror("malformed request\n");
        return -1;
    }
    PARSE_HEAD_OPTION = 1;//設定解析http頭選項的標誌位

    /* Guaranteed to find ' ' since we matched a method above */
    stop = logline + 3;
    if (*stop != ' ')
        ++stop;

    /* scan to start of non-whitespace */
    while (*(++stop) == ' ');

    stop2 = stop;

    /* scan to end of non-whitespace */
    while (*stop2 != '\0' && *stop2 != ' ')
        ++stop2;

    if (stop2 - stop > MAX_HEADER_LENGTH) {
        //log_error_time();
        //fprintf(stderr, "URI too long %d: \"%s\"\n", MAX_HEADER_LENGTH,
        //        req->logline);
        //send_r_bad_request(req);
        perror("URI too long");
        return -1;
    }
    memcpy(request_uri, stop, stop2 - stop);
    request_uri[stop2 - stop] = '\0';
    printf("request uri = %s\n",request_uri);
    if (*stop2 == ' ') {
        /* if found, we should get an HTTP/x.x */
        unsigned int p1, p2;

        /* scan to end of whitespace */
        ++stop2;
        while (*stop2 == ' ' && *stop2 != '\0')
            ++stop2;

        /* scan in HTTP/major.minor */
        if (sscanf(stop2, "HTTP/%u.%u", &p1, &p2) == 2) {
            /* HTTP/{0.9,1.0,1.1} */
            if (p1 == 1 && (p2 == 0 || p2 == 1)) {
                http_version = stop2;
                printf("http version = %s\n",http_version);
            } else if (p1 > 1 || (p1 != 0 && p2 > 1)) {
                goto BAD_VERSION;
            }
        } else {
            goto BAD_VERSION;
        }
    }

    return 0;

BAD_VERSION:
    //log_error_time();
    //fprintf(stderr, "bogus HTTP version: \"%s\"\n", stop2);
    //send_r_bad_request(req);
    perror("bogus HTTP version");
    return -1;
}

五、客戶端測試程式

伺服器程式寫完了,再弄個客戶端程式來測試一下。程式碼很簡單,構造一個簡單的http報文,然後傳送給伺服器,不多說了,有點累贅,直接上程式碼。

/*client.c*/
#include <sys/types.h>
#include <sys/socket.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/ioctl.h>
#include <unistd.h>
#include <netdb.h>
#include <netinet/in.h>
#include <pthread.h>

#define PORT 6000
#define BUFFER_SIZE 4096
#define FILE_NAME_MAX 512

#define HTTP_head    "GET /html/mp_google_banner_20171122v1.html HTTP/1.1\r\n"
#define HTTP_option1 "Host: fragment.firefoxchina.cn\r\n"
#define HTTP_option2 "User-Agent: Firefox/57.0\r\n"
#define HTTP_option3 "Referer: http://home.firefoxchina.cn/\r\n"
#define HTTP_option4 "Connection: keep-alive\r\n\r\n"
//#define HTTP_body    "this is a test"

int main(int argc,char* argv[])
{
    int sockfd;
    struct hostent *host;
    struct sockaddr_in serv_addr;

    if(argc != 2)
    {
        fprintf(stderr,"Usage: ./client Hostname(or ip address) \ne.g. ./client 127.0.0.1 \n");
        exit(1);
    }

    //地址解析函式
    if ((host = gethostbyname(argv[1])) == NULL)
    {
        perror("gethostbyname");
        exit(1);
    }
    //建立socket
    if ((sockfd = socket(AF_INET,SOCK_STREAM,0)) == -1)
    {
        perror("socket");
        exit(1);
    }
    bzero(&serv_addr,sizeof(serv_addr)); 
    //設定sockaddr_in 結構體中相關引數
    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT); //將16位的主機字元順序轉換成網路字元順序
    serv_addr.sin_addr = *((struct in_addr *)host->h_addr); //獲取IP地址
    bzero(&(serv_addr.sin_zero), 8);  //填充0以保持struct sockaddr同樣大小

    //呼叫connect函式主動發起對伺服器端的連線
    if(connect(sockfd,(struct sockaddr *)&serv_addr, sizeof(serv_addr))== -1)
    {
        perror("connect");
        exit(1);
    }

    char buff[BUFFER_SIZE] = {0};
    strcpy(buff, HTTP_head);
    strcat(buff, HTTP_option1);
    strcat(buff, HTTP_option2);
    strcat(buff, HTTP_option3);
    strcat(buff, HTTP_option4);
    printf("buff:%s",buff);
    int count;
    count=send(sockfd,buff,strlen(buff),0);
    if(count<0)
    {
        perror("Send file informantion");
        exit(1);
    }
    printf("client send OK count = %d\n",count);
    //接收來自server的資料
    //char pbuf[BUFFER_SIZE] = {0};
    //int length=recv(sockfd,pbuf,100,0);
    //printf("data from pon is %s\n",pbuf);
    //close(sockfd);
    return 0;
}

測試結果:

//伺服器的終端列印
[email protected]:~/project/web-server$ ./web-server2
Socket id = 3
Bind success!
Listening....
recv 176 bytes from client:GET /html/mp_google_banner_20171122v1.html HTTP/1.1
Host: fragment.firefoxchina.cn
User-Agent: Firefox/57.0
Referer: http://home.firefoxchina.cn/
Connection: keep-alive


http method = GET
request uri = /html/mp_google_banner_20171122v1.html
http version = HTTP/1.1
USER_AGENT:Firefox/57.0
REFERER:http://home.firefoxchina.cn/
CONNECTION:keep-alive
begin parse body!
//客戶端的終端列印
[email protected]:~/project/web-server$ ./client2 127.0.0.1
buff:GET /html/mp_google_banner_20171122v1.html HTTP/1.1
Host: fragment.firefoxchina.cn
User-Agent: Firefox/57.0
Referer: http://home.firefoxchina.cn/
Connection: keep-alive

client send OK count = 176

在接下來的文章中,還會持續更新http伺服器的實現。那部分我也還沒有完成,一起努力吧!

相關推薦

http伺服器實現

前言 本文基於http伺服器實現(一)來完成http報文解析部分。 涉及到的內容有: http協議格式 狀態機變遷 字串解析 伺服器原始碼 客戶端原始碼和測試結果 一、http協議 這一節的重點是解析http報文,那麼首先我們必須知道http協議格

基於nginx-rtmp-module模組實現HTTP-FLV直播模組nginx-http-flv-module

      由於《基於nginx-rtmp-module模組實現的HTTP-FLV直播模組nginx-http-flv-module(一)》內容已經很長,所以後續的更新將記錄在這兒。非常感謝網友們的測試反饋和程式碼提交!專案地址

HTTP 代理原理及實現

提醒:本文最後更新於 1096 天前,文中所描述的資訊可能已發生改變,請謹慎使用。 在上篇《HTTP 代理原理及實現(一)》裡,我介紹了 HTTP 代理的兩種形式,並用 Node.js 實現了一個可用的普通 / 隧道代理。普通代理可以用來承載 HTTP 流量;隧道代理可以用來承載任何 TCP 流量,

http程式設計系列——java爬蟲實現刷個人部落格的訪問量

實現功能 這裡實現的功能是一個根據個人部落格主頁,搜尋出所有的個人博文連結,然後一個一個去訪問,從而增加訪問量。這裡我發現一個問題,csdn既沒有做介面ip訪問量的限制,訪問量統計時也沒有做同一ip相同時間段的重複訪問重複計數的處理。這也時這個程式能夠刷訪問量的原因。 思路

Android直播實現srs流媒體伺服器部署

但是作為android程式設計師,寫好了推流器和播放器,沒有伺服器來測試還是很頭疼的,這裡就介紹一下srs伺服器的最簡單的部署,如果有興趣深入研究的可以去看看官方的wiki,因為是國內開發人員開源的,提供中文文件,講解的很詳細 https://github.

Dji Mobile SDK 基礎實現

stat one 透傳 pub != exceptio rom tick ann Dji Mobile SDK 基礎實現(二) 本文簡要介紹如何通過調用DJI Mobile SDK,實現獲取和釋放無人機的控制權限、模擬遙控器按鈕控制無人機的飛行、獲取無人機的回傳視頻、獲取

實現自定義查詢的數據庫設計及實現

表名 table abr bigint sts 處理 update 關聯表 creat 上部分大概講了一下表設計,這部分講一下處理。 處理的結構 處理結構的內容比較多,分為幾個部分分別講解一下。首先講解一下尋找關系表。 尋找關系表 尋找關系表根據“表間關系登記表”進行處

KVM虛擬化的四種簡單網絡模型介紹及實現

str drive 51cto -c water -a return dfa 模型 接上篇,介紹NAT網絡模型和橋接模型。 三、NAT模型 NAT模型其實就是SNAT的實現,路由中虛擬機能將報文發送給外部主機,但是外部主機因找不到通往虛擬機的路由因而無法回應請求。但是外部

SpringBoot在Kotlin中的實現

文件中 open 代碼 rabl delete ons list any data 根據現在的開發模式和網上的一些資料,SpringBoot需要對業務和操作進行分層,通常分為controller、entity、service、respository等結構。下面以Kotlin

【原始碼剖析】tornado-memcached-sessions —— Tornado session 支援的實現

     客官您終於回頭了!讓我們本著探(zuo)索(si)精神把 session.py 看完吧...       首先看看需要的庫:       pickle 一個用於序列化反序列化的庫(聽

Java常用的八種排序演算法與程式碼實現:歸併排序法、快速排序法

注:這裡給出的程式碼方案都是通過遞迴完成的 --- 歸併排序(Merge Sort):   分而治之,遞迴實現   如果需要排序一個數組,我們先把陣列從中間分成前後兩部分,然後對前後兩部分進行分別排序,再將排好序的數組合並在一起,這樣整個陣列就有序了   歸併排序是穩定的排序演算法,時間

PhotonServer伺服器

目錄 7.nhibernate程式包的引入(我上傳的資源裡面有此版本的nhibernate程式包) 8.nhibernate資料庫連線配置 9.進行類和表的對映 10.建立會話session進行新增操作 11.利用NHibernate進行事務操作  12.建立NHi

資料結構實現:陣列棧C++版

資料結構實現(二):陣列棧(C++版) 1. 概念及基本框架 2. 基本操作程式實現 2.1 入棧操作 2.2 出棧操作 2.3 查詢操作 2.4 其他操作 3. 演算法複雜度分析 3.1 入棧

基於IE核心的多媒體檔案視覺化程式實現

文章目錄 使用命令列引數 命令列引數獲取和解析程式碼 幫助系統 執行測試 WSEE的功能還不只如此 現在的結果 上回說道,我們需要使用命令列引數方法將要瀏覽的檔名傳入wsee.exe 程式,然後由

圖解HTTP學習記錄

第2章 簡單的HTTP協議 HTTP協議規定,先從客戶端開始建立通訊,服務端在沒有接收到請求之前不會發送響應。 請求報文由請求方法、請求URI、協議版本、可選的請求首部欄位和內容實體構成的。 響應報文基本上由協議版本、狀態碼、用以解釋狀態碼的原因短語、可選的響應首部欄位

中小型園區網路的設計與實現

寫論文第二天 想要用心去寫一篇論文,首先要讀懂論文的要求   根據論文指導找出詳細的思路(論文的基本要求) 思路:①中小型區域網,500-1000臺計算機組成的一個網路。 ②網路型別是“園區網”,包括企業網、校園網等多種形式。園區網有一定的地理分佈範圍,不要簡單的一個辦

iOS研發助手DoraemonKit技術實現

一、前言 iOS研發助手DoraemonKit技術實現(一)中介紹了幾個常用工具集的技術實現,大家如果有疑問的話,可以在文章中進行留言,也希望大家接入試用,或者加入到DoraemonKit交流群一起交流。 效能問題極大程度的會影響到使用者的體驗,對於我們開發者和測試同學要隨時隨地保證我們app的質量,避免不好

JAVA高階基礎9---Set的典型實現:TreeSet

TreeSet TreeSet是基於TreeMap實現的。不是執行緒安全的。TreeSet可以使用元素的自然順序進行排序,或者定製排序 注:更多詳細方法請自行在 API 上查詢 排序方式       &nbs

spring-boot-admin原始碼分析及單機監控spring-boot-monitor的實現

SpringBootMonitor spring-boot-admin原始碼分析及單機監控spring-boot-monitor的實現(一) spring-boot-admin原始碼分析及單機監控spring-boot-monitor的實現(二)

基於java的微信小程式的實現登入,註冊,登出介面的實現

1.開發工具以及相關環境的配置 1.首先關於IDE,前端小程式端採用的是微信官方的微信開發者工具,後端使用的是idea(idea是真的智慧,再也不想回去eclipse了呢),關於前端的一些程式碼,主要是參照微信官方的API進行開發的,整體的檔案結構也和js,css,html也很相似。