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;
}

测试结果:

//服务器的终端打印
ubuntu@ubuntu:~/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!
//客户端的终端打印
ubuntu@ubuntu:~/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服务器的实现。那部分我也还没有完成,一起努力吧!

猜你喜欢

转载自blog.csdn.net/u014530704/article/details/78932967