HTTP server implemented under Linux

Project function:

(1) Can receive the GET request from the client;

(2) Be able to parse the client's request message and find the corresponding resources according to the client's requirements;

(2) Be able to reply to the http response message;

(3) Be able to read the files stored in the server and return them to the requesting client to realize the release of static resources;

(4) Use I/O multiplexing to improve the concurrency of processing requests;

(5) The server side supports error handling, such as replying to a 404 error when the resource to be accessed does not exist.

1. http protocol

1. Client request message

The client has get requests and post requests

Process: browser -> send to -> server, the client (browser) sends an HTTP request to the server. The request message includes the following format: request line (request line), request header (header), blank line and request data consists of four parts

step:

  • Request line : Indicates the request type, the resource to be accessed, and the http version used

  • Request header : Indicates the additional information to be used by the server, each line requires \r\n to indicate the end of a certain attribute

  • Empty line : required!, even if there is no request data, it is actually \r\n

  • Request data : also called the main body, you can add any other data, which is the data required by the client and sent by the server

Note: When reading the http request header continuously, if you encounter two consecutive carriage returns and line feeds, it means that the request header is over

Sample content requested by the browser

浏览器请求:
GET /demo.html HTTP/1.1
Host: 47.100.162.191
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/63.0.3239.26 Safari/537.36 Core/1.63.6788.400 QQBrowser/10.3.2767.400
Upgrade-Insecure-Requests: 1
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9
Cookie:cna=BT0+EoVi1FACAXH3Nv5I7h6k;isg=BIOD99I03BNYvZDfE2FJUOsMB0ftUBZcBFi4E7VgYOJZdKOWPcvRinAl6kSfVG8y

2. Server response message

The server gets the status message of the browser

Process: server -> send to -> browser

step:

  • Status line : including http protocol version number, status code, status information

  • Message header : Indicates some additional information to be used by the client

  • Empty line : required!

  • Response body : the text information returned by the server to the client

Sample content of server response

服务器响应:
HTTP/1.0 200 OK
Server: Martin Server
Content-Type: text/html
Connection: Close
Content-Length: 526

<html lang="zh-CN">
<head>
<meta content="text/html; charset=utf-8" http-equiv="Content-Type">
<title>This is a test</title>

Some response codes and code descriptions

response code

Code Description

The requested content exists on the server and can respond to the client

200

OK

There is an exception in the client's request, and there is a problem with the method

501

Method Not Implemented

After the server receives the request, it cannot respond due to self-generated problems

500

Internal Server Error

The requested content does not exist

404

NOT FOUND

There is a problem with the request format sent by the client, etc.

400

BAD REQUEST

3. Some functions and structures used

1. stat and fstat functions

Function prototype: int stat(const char *pathname, struct stat *statbuf);

   int fstat(int fd, struct stat *statbuf);

int stat(const char *pathname, struct stat *statbuf);
    作用:获取文件信息
    包含在头文件:include <sys/types.h> #include <sys/stat.h> #include <unistd.h>
    返回值:成功返回0,失败返回-1
    参数解释:
        参数pathname:表示文件路径
        参数statbuf:struct stat类型的结构体

int fstat(int fd, struct stat *statbuf);
    作用:由文件描述符获取文件的状态
    包含的头文件:#include <sys/stat.h>   #include <unistd.h>
    参数解释:
        参数fd:表示是已经打开的文件描述符
        参数statbuf:是struct stat类型的结构体
    返回值:执行成功返回0,失败返回-1

   结构体原型:
struct stat
{
    dev_t     st_dev;     /* ID of device containing file */文件使用的设备号
    ino_t     st_ino;     /* inode number */    索引节点号 
    mode_t    st_mode;    /* protection */  文件对应的模式,文件,目录等
    nlink_t   st_nlink;   /* number of hard links */    文件的硬连接数  
    uid_t     st_uid;     /* user ID of owner */    所有者用户识别号
    gid_t     st_gid;     /* group ID of owner */   组识别号  
    dev_t     st_rdev;    /* device ID (if special file) */ 设备文件的设备号
    off_t     st_size;    /* total size, in bytes */ 以字节为单位的文件容量   
    blksize_t st_blksize; /* blocksize for file system I/O */ 包含该文件的磁盘块的大小   
    blkcnt_t  st_blocks;  /* number of 512B blocks allocated */ 该文件所占的磁盘块  
    time_t    st_atime;   /* time of last access */ 最后一次访问该文件的时间   
    time_t    st_mtime;   /* time of last modification */ /最后一次修改该文件的时间   
    time_t    st_ctime;   /* time of last status change */ 最后一次改变该文件状态的时间   
};

    st_mode包含了三部分的信息:
        1. 15-12位保存了文件类型
        2. 11-9位保存了执行文件时设置的信息
        3. 8-0位保存文件访问权限

    在c库中定义的S_ISDIR函数:S_ISDIR(statbuf.st_mode)
        作用:判断一个路径是否是一个目录
        返回值:不是路径返回非零,是路径返回0

2. isspace function

Function prototype: int isspace(int c);

int isspace(int c);  包含在头文件:#include  <ctype.h>
    作用:判断字符c是否为空白符(空白符指空格、水平制表、垂直制表、换页、回车和换行符。)
    返回值:成功返回非0,失败返回0

3.strncasecmp function

Function prototype: int strncasecmp (const char *s1, const char *s2, size_t count);

int strncasecmp (const char *s1, const char *s2, size_t count);
    作用:判断字符串指定长度的字符是否相等,忽略大小写
    包含的头文件:#include<string.h>
    返回值:
        小于0:表示s1大于s2
        等于0:表示s1等于s2
        大于0:表示s1大于s2

4. strchr function

Function prototype: char *strstr( const char *string, const char *strCharSet );

char *strstr( const char *string, const char *strCharSet );
    作用:用于判断字符串strCharSet是否是string的子串
    返回值:如果是子串,则返回字串strCharSet在字符串string中第一个出现的位置到结尾的字符串,
            否则返回NULL,返回值是一个指针,返回的是子串在原字符串中第一个出现的位置,如果
            没有找到返回NULL

5. snprintf function

Function prototype: int snprintf(char *str, size_t size, const char *format, ...)

Function: If the length of the formatted string < size, copy the entire string to str, and add a string terminator ('\0'); if the length of the formatted string> = size, only the (size-1) characters will be copied to str, and a string terminator ('\0') will be added after it, and the return value will be the length of the string to be written.

6. fprintf function

Function prototype: intfprintf( FILE *stream, constchar *format, ... );

   包含的头文件:#include <stdio.h>
   int fprintf( FILE *stream, const char *format, ... );
        作用:根据指定的format(格式)发送信息(参数)到由stream(流)指定的文件.
                因此fprintf()可以使得信息输出到指定的文件

        fprintf()和printf()一样工作. 
        printf是打印输出到屏幕,fprintf是打印输出到文件。 
        fprintf()的返回值是输出的字符数,发生错误时返回一个负值. 

7. fileno function

Function prototype: int fileno(FILE *stream);

int fileno(FILE *stream);  包含在头文件 <stdio.h>
    作用:把文件流指针转换成文件描述符
    参数stream:是指定的文件路径
    返回值:成功返回的是stream对应的文件描述符id,失败返回-1

8.pthread_create function

函数原型:int pthread_create(pthread_t *thread, const pthread_attr_t *attr,

void *(*start_routine) (void *), void *arg);

int pthread_create(pthread_t *restrict tidp,
                   const pthread_attr_t *restrict attr,
                   void *(*start_rtn)(void), 
                   void *restrict arg);
    作用:创建一个线程
    包含的头文件:<pthread.h>
    在linux下编译时,如果程序使用到pthread.h头文件中的函数,需要加  -lpthread 选项
        例如:gcc Minihttp.c -o minihttp -lpthread
    参数解释:
        第一个参数为指向线程标识符的指针。
        第二个参数用来设置线程属性。
        第三个参数是线程运行函数的起始地址。
        最后一个参数是运行函数的参数

4.实现一个简单http服务器的步骤

1.创建用于连接的服务器socket套接字

2.处理客户端连接请求的消息,把请求行和请求头部的内容读取出来,并且判断客户端实现的是get请求还是post请求

3.服务器响应客户端的请求,如果客户端申请的是一个网页,那就需要在服务器编写出符合http协议的请求行和请求头,并且发送给客户端,再把客户端请求的内容发送给浏览器;如果客户端申请的不是网页,那就不需要发送请求头和请求行,直接发送内容即可。

5.实现了简单的http服务器的代码

1.使用了epoll实现的http服务器

#include<stdio.h>
#include<unistd.h>
#include<ctype.h>
#include<string.h>
#include<stdlib.h>
#include<sys/socket.h>
#include<sys/types.h>
#include<sys/epoll.h>
#include<arpa/inet.h>
#include<sys/stat.h>
#include<sys/un.h>
#include<stddef.h>
#include<fcntl.h>

int init_listen_fd(int port, int epfd)
{
    //创建监听的套接字
    int lfd = socket(AF_INET, SOCK_STREAM, 0);
    if (lfd == -1)
    {
        perror("socket error\n");
        exit(1);
    }
    struct sockaddr_in srv_addr;
    bzero(&srv_addr, sizeof(srv_addr));
    srv_addr.sin_family = AF_INET;
    srv_addr.sin_port = htons(port);
    srv_addr.sin_addr.s_addr = htonl(INADDR_ANY);

    //端口复用
    int opt = -1;
    setsockopt(lfd, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt));

    //给lfd绑定结构体
    int ret = bind(lfd, (struct sockaddr*)&srv_addr, sizeof(srv_addr));
    if (ret == -1)
    {
        perror("bind error\n");
        exit(1);
    }
    //设置监听上限
    ret = listen(lfd, 128);
    if (ret == -1)
    {
        perror("listen error\n");
    }

    //将lfd 添加到epoll树上
    struct epoll_event ev;
    ev.events = EPOLLIN;
    ev.data.fd = lfd;

    ret = epoll_ctl(epfd, EPOLL_CTL_ADD, lfd, &ev);
    if (ret == -1)
    {
        perror("epoll_ctl error\n");
        exit(1);
    }
    return lfd;
}

void do_accept(int lfd, int epfd)
{
    struct sockaddr_in clt_addr;
    socklen_t clt_addr_len = sizeof(clt_addr);

    int cfd = accept(lfd, (struct sockaddr*)&clt_addr, &clt_addr_len);
    if (cfd == -1)
    {
        perror("accept error\n");
        exit(1);
    }
    //打印客户端IP+PORT
    char client_ip[64] = { 0 };
    printf("New Client IP:%s, Port:%d, cfd = %d\n",
        inet_ntop(AF_INET, &clt_addr.sin_addr.s_addr, client_ip, sizeof(client_ip)), ntohs(clt_addr.sin_port), cfd);
    //设置cfd非阻塞
    int flag = fcntl(cfd, F_GETFL);
    flag |= O_NONBLOCK;
    fcntl(cfd, F_SETFL, flag);

    //将新节点cfd挂上epoll上
    struct epoll_event ev;
    ev.data.fd = cfd;

    //边缘非阻塞模式
    ev.events = EPOLLIN | EPOLLET;
    int ret = epoll_ctl(epfd, EPOLL_CTL_ADD, cfd, &ev);
    if (ret == -1)
    {
        perror("epoll_ctl add cfd error\n");
        exit(1);
    }
}

//获取一行\r\n结尾的数据
int get_line(int cfd, char* buf, int size)
{
    int i = 0;
    char c = '\0';
    int n;
    while ((i < size - 1) && c != '\n')
    {
        n = recv(cfd, &c, 1, 0);
        if (n > 0)
        {
            if (c == '\r')
            {
                n = recv(cfd, &c, 1, MSG_PEEK);
                if ((n > 0) && (c == '\n'))
                {
                    recv(cfd, &c, 1, 0);
                }
                else
                {
                    c = '\n';
                }
            }
            buf[i] = c;
            i++;
        }
        else
        {
            c = '\n';
        }
    }
    buf[i] = '\0';
    if (n == -1)
    {
        i = n;
    }
    return i;
}

//断开链接
void disconnect(int cfd, int epfd)
{
    int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
    if (ret != 0)
    {
        perror("epoll_ctl error\n");
        exit(1);
    }
    close(cfd);
}

void send_respond(int cfd, //客户端的socket
    int no, //错误号
    char* disp, //错误描述
    char* type, //回发文件类型
    int len) //文件长度
{
    //拼写出http协议
    char buf[1024] = { 0 };
    sprintf(buf, "HTTP/1.1 %d %s\r\n", no, disp);
    sprintf(buf+strlen(buf), "%s\r\n", type);
    sprintf(buf + strlen(buf), "Content_Length:%d\r\n", len);
    send(cfd, buf, strlen(buf), 0);
    send(cfd, "\r\n", 2, 0);
}

//把本地文件内容发生给浏览器
void send_file(int cfd, const char* file)
{
    int n = 0;
    char buf[1024];
    int fd = open(file, O_RDONLY);//打开的服务器文件
    if (fd == -1)
    {
        //404错误页面
        perror("oepn error");
        exit(1);
    }
    while ((n = read(fd, buf, sizeof(buf))) > 0)//获取文件中的数据
    {
        send(cfd, buf, n, 0);//
    }

    close(fd);
}

//处理http请求,判断文件是否存在,存在则把内容发给浏览器
void http_request(int cfd, const char* file)
{
    struct stat sbuf;
    //判断文件是否存在
    int ret = stat(file, &sbuf);
    if (ret != 0)
    {
        perror("stat");
        exit(1);
    }
    if (S_ISREG(sbuf.st_mode))//判断是一个普通文件
    {
        // 回发 http协议应答
        send_respond(cfd, 200, "OK", "Content-Type: text/plain; charset=iso-8859-1", sbuf.st_size);
        //回发 给客户端请求数据内容
        send_file(cfd, file);
    }
}

void do_read(int cfd, int epfd)
{
    //读取一行http协议,拆分,获取get文件名 协议号
    char line[1024] = { 0 };
    int len = get_line(cfd, line, sizeof(line));//读取 请求协议首行 
    if (len == 0)
    {
        printf("服务器,检查到客户端关闭.....\n");
        disconnect(cfd, epfd);
    }
    else
    {
        char method[16] = { 0 };
        char path[256] = { 0 };
        char protocol[16] = { 0 };
        sscanf(line, "%[^ ] %[^ ] %[^ ]", method, path, protocol);//分割首行
        printf("method=%s, path=%s, protocol=%s\n", method, path, protocol);
        while (1)
        {
            char buf[1024] = { 0 };
            len = get_line(cfd, buf, sizeof(buf));//把缓冲区的剩下内容读走
            if (len == '\n' || len == -1)
            {
                break;
            }
        }
        if (strncasecmp(method, "GET", 3) == 0)//比较字符串,参数3表示比较几个
        {
            char* file = path + 1; //取出 客户端要访问的文件名
            http_request(cfd, file);//读出文件内容
        }
    }
}

void epoll_run(int port)
{
    int i = 0;
    struct epoll_event all_events[128];
    //创建一个epoll监听树根
    int epfd = epoll_create(128);
    if (epfd == -1)
    {
        perror("epoll_create error\n");
        exit(1);
    }
    //创建lfd,并添加到监听树根
    int lfd = init_listen_fd(port, epfd);
    while (1)
    {
        //监听节点对应事件
        int ret = epoll_wait(epfd, all_events, 128, -1);
        if (ret == -1)
        {
            perror("epoll_wait error\n");
            exit(1);
        }
        for (i = 0; i < ret; i++)
        {
            //只处理读事件,其他事件不处理
            struct epoll_event* pev = &all_events[i];
            //不是读事件
            if (!(pev->events & EPOLLIN))
            {
                continue;
            }
            if (pev->data.fd == lfd)
            {
                //接受链接请求
                do_accept(lfd, epfd);
            }
            else
            {
                //读事件处理
                do_read(pev->data.fd, epfd);
            }
        }
    }
}

int main(int argc, char* argv[])
{
    //命令行参数获取 端口 和 server提供的目录
    if (argc < 3)
    {
        printf("./server port path\n");
    }
    //获取端口号
    int port = atoi(argv[1]);
    //改变进程工作目录
    int ret = chdir(argv[2]);
    if (ret != 0)
    {
        perror("chdir error");
        exit(1);
    }
    //启动epoll监听
    epoll_run(port);

    return 0;
}
    

2.使用线程实现的http服务器

#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <ctype.h>
#include <stdlib.h>
#include <arpa/inet.h>
#include <errno.h>
#include <sys/stat.h>

static int debug = 1;

int get_line(int sock, char* buf, int size);//获取客户端请求的内容
void* do_http_request(void* pclient_sock);//作为线程的回调函数
void do_http_response(int client_sock, const char* path);//响应客户端请求
int  headers(int client_sock, FILE* resource);//给客户端发送请求头数据
void cat(int client_sock, FILE* resource);//给客户端发送html数据
void unimplemented(int client_sock);//请求没有被实现
void not_found(int client_sock);//如果没有找到就执行这个
void inner_error(int client_sock);//服务器内部出错
void bad_request(int client_sock);//请求出错


int main() 
{
    int sock;//代表信箱
    struct sockaddr_in server_addr;


    //1.美女创建信箱
    sock = socket(AF_INET, SOCK_STREAM, 0);

    //2.清空标签,写上地址和端口号
    bzero(&server_addr, sizeof(server_addr));
    server_addr.sin_family = AF_INET;//选择协议族IPV4
    server_addr.sin_addr.s_addr = htonl(INADDR_ANY);//监听本地所有IP地址
    server_addr.sin_port = htons(8888);//绑定端口号

    //实现标签贴到收信得信箱上
    int ret = bind(sock, (struct sockaddr*)&server_addr, sizeof(server_addr));
    if(ret == -1)
    {
        perror("bind error");
        exit(1);
    }

    //把信箱挂置到传达室,这样,就可以接收信件了
    ret = listen(sock, 128);
    if(ret == -1)
    {
        perror("bind error");
        exit(1);
    }
    //设置端口复用
    int opt = 1;
    setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (void*)&opt, sizeof(opt));
    //万事俱备,只等来信
    printf("等待客户端连接\n");
    while (1) 
    {
        struct sockaddr_in client;
        bzero(&client, sizeof(client));
        int client_sock, len, i;
        char client_ip[128];
        char buf[256];
        pthread_t id;//线程id
        int* pclient_sock = NULL;

        socklen_t  client_addr_len;
        client_addr_len = sizeof(client);
        client_sock = accept(sock, (struct sockaddr*)&client, &client_addr_len);

        //打印客服端IP地址和端口号
        printf("client ip: %s\t port : %d\n",
            inet_ntop(AF_INET, &client.sin_addr.s_addr, client_ip, sizeof(client_ip)),
            ntohs(client.sin_port));
        /*处理http 请求,读取客户端发送的数据*/
        pclient_sock = (int*)malloc(sizeof(int));//分配空间
        *pclient_sock = client_sock;
        //使用线程处理请求
        pthread_create(&id, NULL, do_http_request, (void*)pclient_sock);//并行
    }
    close(sock);
    return 0;
}

//1.读取请求行
void* do_http_request(void* pclient_sock) 
{
    int len = 0;
    char buf[256];
    char method[64];
    char url[256];
    char path[256];
    int client_sock = *(int*)pclient_sock;//解参数的引用
    struct stat  st;
    /*读取客户端发送的http 请求*/
    //1.读取请求行
    len = get_line(client_sock, buf, sizeof(buf));

    if (len > 0) 
    {//读到了请求行
        int i = 0, j = 0;
        while (!isspace(buf[j]) && (i < sizeof(method) - 1)) 
        {
            method[i] = buf[j];
            i++;
            j++;
        }
        method[i] = '\0';
        if (debug) printf("request method: %s\n", method);

        if (strncasecmp(method, "GET", i) == 0) 
        { //只处理get请求
            if (debug) printf("method = GET\n");

            //获取url
            while (isspace(buf[j++]));//跳过白空格
            i = 0;

            while (!isspace(buf[j]) && (i < sizeof(url) - 1)) {
                url[i] = buf[j];
                i++;
                j++;
            }
            url[i] = '\0';
            if (debug) printf("url: %s\n", url);
            //继续读取http 头部
            do {
                len = get_line(client_sock, buf, sizeof(buf));
                if (debug) printf("read: %s\n", buf);

            } while (len > 0);
            //***定位服务器本地的html文件***
            //处理url 中的?
            {
                char* pos = strchr(url, '?');
                if (pos) {
                    *pos = '\0';
                    printf("real url: %s\n", url);
                }
            }

            sprintf(path, "./html_docs/%s", url);//拼接出要获取内容在服务器中的路径
            if (debug) printf("path: %s\n", path);

            //执行http 响应
            //判断文件是否存在,如果存在就响应200 OK,同时发送相应的html 文件,如果不存在,就响应 404 NOT FOUND.
            if (stat(path, &st) == -1) {//文件不存在或是出错
                fprintf(stderr, "stat %s failed. reason: %s\n", path, strerror(errno));
                not_found(client_sock);
            }
            else {//文件存在

                if (S_ISDIR(st.st_mode)) {//S_SIDIR判断一个路径是否是目录
                    strcat(path, "/index.html");
                }

                do_http_response(client_sock, path);
            }
        }
        else {//非get请求, 读取http 头部,并响应客户端 501     Method Not Implemented
            fprintf(stderr, "warning! other request [%s]\n", method);
            do {
                len = get_line(client_sock, buf, sizeof(buf));
                if (debug) printf("read: %s\n", buf);

            } while (len > 0);
            unimplemented(client_sock);   //请求未实现
        }
    }
    else 
    {//请求格式有问题,出错处理
        bad_request(client_sock);   //在响应时再实现
    }

    close(client_sock);//关闭sock
    if (pclient_sock) free(pclient_sock);//释放动态分配的内存

    return NULL;
}

void do_http_response(int client_sock, const char* path) {
    int ret = 0;
    FILE* resource = NULL;

    resource = fopen(path, "r");

    if (resource == NULL) {
        not_found(client_sock);
        return;
    }

    //1.发送http 头部
    ret = headers(client_sock, resource);//

    //2.发送http body .
    if (!ret) {
        cat(client_sock, resource);//把文件内容一行一行读取
    }

    fclose(resource);
}

/****************************
 *返回关于响应文件信息的http 头部
 *输入:
 *     client_sock - 客服端socket 句柄
 *     resource    - 文件的句柄
 *返回值: 成功返回0 ,失败返回-1
******************************/
int  headers(int client_sock, FILE* resource) {
    struct stat st;
    int fileid = 0;
    char tmp[64];
    char buf[1024] = { 0 };
    strcpy(buf, "HTTP/1.0 200 OK\r\n");
    strcat(buf, "Server: Martin Server\r\n");
    strcat(buf, "Content-Type: text/html\r\n");
    strcat(buf, "Connection: Close\r\n");

    fileid = fileno(resource);//获取到指定文件的文件描述符

    if (fstat(fileid, &st) == -1) {//获取文件状态失败
        inner_error(client_sock);
        return -1;
    }

    snprintf(tmp, 64, "Content-Length: %ld\r\n\r\n", st.st_size);//会返回拼接出来的字符串
    strcat(buf, tmp);

    if (debug) fprintf(stdout, "header: %s\n", buf);//输出头部

    if (send(client_sock, buf, strlen(buf), 0) < 0) {//给浏览器发送http请求的数据
        fprintf(stderr, "send failed. data: %s, reason: %s\n", buf, strerror(errno));
        return -1;
    }

    return 0;
}

//返回值: -1 表示读取出错, 等于0表示读到一个空行, 大于0 表示成功读取一行
int get_line(int sock, char* buf, int size) 
{
    int count = 0;//表示已经读取的字符
    char ch = '\0';//表示字符串结束符
    int len = 0;//当前读取的字符


    while ((count < size - 1) && ch != '\n') 
    {
        len = read(sock, &ch, 1);//获取读取到的字节数 

        //读取到一个字符的清空
        if (len == 1) {
            if (ch == '\r') {//如果是回车符,就继续
                continue;
            }
            else if (ch == '\n') {//如果是换行符那就直接结束
                //buf[count] = '\0';
                break;
            }

            //这里处理一般的字符
            buf[count] = ch;
            count++;

        }
        else if (len == -1) {//读取出错
            perror("read failed");
            count = -1;
            break;
        }
        else {// read 返回0,客户端关闭sock 连接.
            fprintf(stderr, "client close.\n");
            count = -1;
            break;
        }
    }

    if (count >= 0) buf[count] = '\0';//结束符

    return count;//返回读取的数量
}

void not_found(int client_sock) {
    const char* reply = "HTTP/1.0 404 NOT FOUND\r\n\
                            Content-Type: text/html\r\n\
                            \r\n\
                            <HTML lang=\"zh-CN\">\r\n\
                            <meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
                            <HEAD>\r\n\
                            <TITLE>NOT FOUND</TITLE>\r\n\
                            </HEAD>\r\n\
                            <BODY>\r\n\
                                <P>文件不存在!\r\n\
                                <P>The server could not fulfill your request because the resource specified is unavailable or nonexistent.\r\n\
                            </BODY>\r\n\
                            </HTML>";

    int len = write(client_sock, reply, strlen(reply));
    if (debug) fprintf(stdout, reply);

    if (len <= 0) {
        fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
    }


}

void unimplemented(int client_sock) 
{
    const char* reply = "HTTP/1.0 501 Method Not Implemented\r\n\
                        Content-Type: text/html\r\n\
                        \r\n\
                        <HTML>\r\n\
                        <HEAD>\r\n\
                        <TITLE>Method Not Implemented</TITLE>\r\n\
                        </HEAD>\r\n\
                        <BODY>\r\n\
                            <P>HTTP request method not supported.\r\n\
                        </BODY>\r\n\
                        </HTML>";

    int len = write(client_sock, reply, strlen(reply));
    if (debug) fprintf(stdout, reply);

    if (len <= 0) 
    {
        fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
    }
}

void cat(int client_sock, FILE* resource) 
{
    char buf[1024];
    fgets(buf, sizeof(buf), resource);//读取一行
    while (!feof(resource)) 
    {
        int len = write(client_sock, buf, strlen(buf));//获取发送给客户端的字符串长度
        if (len < 0) {//发送body 的过程中出现问题,怎么办?1.重试? 2.
            fprintf(stderr, "send body error. reason: %s\n", strerror(errno));
            break;
        }
        if (debug) fprintf(stdout, "%s", buf);
        fgets(buf, sizeof(buf), resource);
    }
}

void inner_error(int client_sock)
{
    const char* reply = "HTTP/1.0 500 Internal Sever Error\r\n\
                            Content-Type: text/html\r\n\
                            \r\n\
                            <HTML lang=\"zh-CN\">\r\n\
                            <meta content=\"text/html; charset=utf-8\" http-equiv=\"Content-Type\">\r\n\
                            <HEAD>\r\n\
                            <TITLE>Inner Error</TITLE>\r\n\
                            </HEAD>\r\n\
                            <BODY>\r\n\
                                <P>服务器内部出错.\r\n\
                            </BODY>\r\n\
                            </HTML>";

    int len = write(client_sock, reply, strlen(reply));
    if (debug) fprintf(stdout, reply);

    if (len <= 0)
    {
        fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
    }
}

void bad_request(int client_sock)
{
    const char* reply = "HTTP/1.0 400 BAD REQUEST\r\n\
                        Content-Type: text/html\r\n\
                        \r\n\
                        <HTML>\r\n\
                        <HEAD>\r\n\
                        <TITLE>BAD REQUEST</TITLE>\r\n\
                        </HEAD>\r\n\
                        <BODY>\r\n\
                            <P>Your browser sent a bad request!\r\n\
                        </BODY>\r\n\
                        </HTML>";

    int len = write(client_sock, reply, strlen(reply));
    if (len <= 0) {
        fprintf(stderr, "send reply failed. reason: %s\n", strerror(errno));
    }
}


3.访问的注意事项

1.访问的格式

2.连接状态

Guess you like

Origin blog.csdn.net/weixin_62859191/article/details/129485073