Servidor HTTP implementado bajo Linux

función del proyecto:

(1) Puede recibir la solicitud GET del cliente;

(2) Ser capaz de analizar el mensaje de solicitud del cliente y encontrar los recursos correspondientes de acuerdo con los requisitos del cliente;

(2) Ser capaz de responder al mensaje de respuesta http;

(3) Ser capaz de leer los archivos almacenados en el servidor y devolverlos al cliente solicitante para realizar la liberación de recursos estáticos;

(4) Usar multiplexación de E/S para mejorar la concurrencia de solicitudes de procesamiento;

(5) El lado del servidor admite el manejo de errores, como responder a un error 404 cuando el recurso al que se accede no existe.

1. protocolo http

1. Mensaje de solicitud del cliente

El cliente tiene solicitudes de obtención y de publicación.

Proceso: navegador -> enviar a -> servidor, el cliente (navegador) envía una solicitud HTTP al servidor. El mensaje de solicitud incluye el siguiente formato: línea de solicitud (línea de solicitud), encabezado de solicitud (encabezado), línea en blanco y datos de solicitud consta de cuatro partes

paso:

  • Línea de solicitud : indica el tipo de solicitud, el recurso al que se accede y la versión http utilizada

  • Encabezado de solicitud : Indica la información adicional que utilizará el servidor, cada línea requiere \r\n para indicar el final de un determinado atributo

  • Línea vacía : ¡obligatorio!, incluso si no hay datos de solicitud, en realidad es \r\n

  • Solicitar datos : también llamado cuerpo principal, puede agregar cualquier otro dato, que es el dato requerido por el cliente y enviado por el servidor

Nota: Al leer el encabezado de la solicitud http continuamente, si encuentra dos retornos de carro y saltos de línea consecutivos, significa que el encabezado de la solicitud ha terminado.

Ejemplo de contenido solicitado por el navegador

浏览器请求:
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. Mensaje de respuesta del servidor

El servidor recibe el mensaje de estado del navegador.

Proceso: servidor -> enviar a -> navegador

paso:

  • Línea de estado : incluido el número de versión del protocolo http, código de estado, información de estado

  • Encabezado del mensaje : Indica alguna información adicional para ser utilizada por el cliente

  • Línea vacía : requerido!

  • Cuerpo de respuesta : la información de texto devuelta por el servidor al cliente

Ejemplo de contenido de la respuesta del servidor

服务器响应:
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>

Algunos códigos de respuesta y descripciones de códigos

código de respuesta

Código Descripción

El contenido solicitado existe en el servidor y puede responder al cliente

200

DE ACUERDO

Hay una excepción en la solicitud del cliente y hay un problema con el método.

501

Método no Aplicado

Después de que el servidor recibe la solicitud, no puede responder debido a problemas autogenerados

500

Error Interno del Servidor

El contenido solicitado no existe

404

EXTRAVIADO

Hay un problema con el formato de solicitud enviado por el cliente, etc.

400

SOLICITUD INCORRECTA

3. Algunas funciones y estructuras utilizadas

1. funciones stat y fstat

Prototipo de función: 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. función isspace

Prototipo de función: int isspace(int c);

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

función 3.strncasecmp

Prototipo de función: 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. función strchr

Prototipo de función: char *strstr( const char *string, const char *strCharSet );

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

5. función snprintf

Prototipo de función: int snprintf(char *str, size_t size, const char *format, ...)

Función: si la longitud de la cadena formateada < tamaño, copie toda la cadena en str y agregue un terminador de cadena ('\0'); si la longitud de la cadena formateada> = tamaño, solo los caracteres (tamaño-1) se copiará en str, y se agregará un terminador de cadena ('\0') después, y el valor de retorno será la longitud de la cadena que se escribirá.

6. función fprintf

Prototipo de función: intfprintf( ARCHIVO *flujo, constchar *formato, ... );

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

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

7. función fileno

Prototipo de función: int fileno(FILE *stream);

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

función 8.pthread_create

函数原型: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.连接状态

Supongo que te gusta

Origin blog.csdn.net/weixin_62859191/article/details/129485073
Recomendado
Clasificación