Linux下实现的 HTTP 服务器

项目功能:

(1)能接收客户端的GET请求;

(2)能够解析客户端的请求报文,根据客户端要求找到相应的资源;

(2)能够回复http应答报文;

(3)能够读取服务器中存储的文件,并返回给请求客户端,实现对外发布静态资源;

(4)使用I/O复用来提高处理请求的并发度;

(5)服务器端支持错误处理,如要访问的资源不存在时回复404错误等。

1.http协议

1.客户端请求消息

客户端有get请求和post请求

过程:浏览器->发给->服务器,客户端(浏览器)发送一个HTTP请求到服务器的请求消息包括以下格式:请求行(request line)、请求头部(header)、空行和请求数据四个部分组成

步骤:

  • 请求行:说明请求类型,要访问的资源,以及使用的 http 版本

  • 请求头:说明服务器要使用的附加信息,每一行都需要 \r\n 表示某一个属性结束

  • 空 行:必须!,即使没有请求数据,其实就是  \r\n

  • 请求数据:也叫主体,可以添加任意的其他数据,是客户端需要的数据,由服务器发送

注意:在连续读取http请求头部时,如果碰到两个连续的回车换行,即表示请求头部结束

浏览器请求的内容样例

浏览器请求:
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.服务器响应消息

服务器获取浏览器的状态消息

过程:服务器->发给->浏览器

步骤:

  • 状态行: 包括 http 协议版本号,状态码,状态信息

  • 消息报头: 说明客户端要使用的一些附加信息

  • 空 行:必须!

  • 响应正文:服务器返回给客户端的文本信息

服务器响应的内容样例

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

一些响应代号及代号描述

响应代号

代号描述

服务器上存在请求的内容,并可以响应给客户端

200

OK

客户端的请求有异常,方法有问题

501

Method Not Implemented

服务器收到请求后,因为自生的问题没法响应

500

Internal Server Error

请求的内容不存在

404

NOT FOUND

客户端发送的请求格式有问题等

400

BAD REQUEST

3.使用到的一些函数及结构体

1.stat和fstat函数

函数原型: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函数

函数原型:int isspace(int c);

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

3.strncasecmp函数

函数原型: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函数

函数原型:char *strstr( const char *string, const char *strCharSet );

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

5.snprintf函数

函数原型:int snprintf(char *str, size_t size, const char *format, ...)

作用:如果格式化后的字符串长度 < size,则将此字符串全部复制到str中,并给其后添加一个字符串结束符('\0');如果格式化后的字符串长度 >= size,则只将其中的(size-1)个字符复制到str中,并给其后添加一个字符串结束符('\0'),返回值为欲写入的字符串长度。

6.fprintf函数

函数原型: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函数

函数原型:int fileno(FILE *stream);

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

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.连接状态

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mfbz.cn/a/7844.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

数据结构和算法学习记录——设计循环队列(数组实现循环队列)核心思路、题解过程、完整题解

目录 题目描述 题目示例 核心思路 链表实现 数组实现 重点 题解过程 结构体类型定义 创建一个循环队列并初始化 判断循环队列为空或为满 入队列函数 出队列函数 取队头数据 取队尾数据 销毁循环队列 完整题解 题目来源&#xff1a;力扣 题目描述 设计你的…

Sentinel滑动时间窗限流算法原理及源码解析(下)

文章目录对统计数据如何使用获取之前统计好的数据对统计数据如何使用 流控快速失败 获取之前统计好的数据

SpringBoot 项目的创建与启动

✅作者简介&#xff1a;2022年博客新星 第八。热爱国学的Java后端开发者&#xff0c;修心和技术同步精进。 &#x1f34e;个人主页&#xff1a;Java Fans的博客 &#x1f34a;个人信条&#xff1a;不迁怒&#xff0c;不贰过。小知识&#xff0c;大智慧。 &#x1f49e;当前专栏…

iosApplePay

1、Apple Pay 接入工程 - 简书 // 设置币种、国家码及merchant标识符等基本信息 PKPaymentRequest *payRequest [[PKPaymentRequest alloc]init]; payRequest.countryCode "CN"; //国家代码 payRequest.currencyCode "CNY"; //RMB的币种代码 …

“被裁员之前,没得到任何风声,措手不及...” 一个在职6年测试工程师内心独白

前言 一个码农&#xff08;软件测试工程师&#xff09;的自白 小张&#xff1a; 我们用工作五年的积蓄&#xff0c;在这个一线城市买了房子&#xff0c;买了车子&#xff0c;然后领证。我也在6年前进入了一个很多人梦寐以求新的公司 码农的新生活开始了。在这家公司里&…

ChatGPT如何为企业提供帮助?

数字化转型是指利用技术来改变企业的运营方式并为客户提供价值&#xff0c;这不仅仅是关于如何采用新的技术或工具。要想取得成功&#xff0c;就需要从根本上改变文化和心态。 ChatGPT如何为企业提供帮助?从数据分析到知识管理再到客户服务等等&#xff0c;人工智能聊天机器人…

光伏发电系统模拟及其发电预测开源python工具pvlib

1. 太阳辐照量模拟 pysolar是一个用于计算太阳位置和辐照量的Python库。它是基于python语言编写的&#xff0c;可以方便地在各种python项目中使用。pysolar主要用于计算太阳的位置、太阳高度角、太阳方位角、日出和日落时间等信息。这些信息可以用于太阳能电池板和太阳能集热器…

【设计模式】创建型-抽象工厂模式

文章目录一、抽象工厂模式1.1、产品族、产品等级1.2、抽象工厂模式中的角色1.3、实例一、抽象工厂模式 在工厂方法模式中&#xff0c;每一个具体的工厂子类只能生成一种具体的产品&#xff0c;如果想要生产另外一种产品&#xff0c;就需要重新定义一个抽象工厂类&#xff0c;这…

泡泡玛特“失速”,盲盒经济迎来拐点?

配图来自Canva可画​ 前些年泡泡玛特的飞速增长&#xff0c;曾经在行业内外引起了广泛的反响&#xff0c;其主打的盲盒经济也曾风靡一时、被众多行业效仿。不过&#xff0c;这种情况在疫情肆虐的2022年似乎受到了一些影响&#xff0c;这在其财报中就有所体现。 3月29日&#…

Python 小型项目大全 61~65

六十一、ROT13 密码 原文&#xff1a;http://inventwithpython.com/bigbookpython/project61.html ROT13 密码是最简单的加密算法之一&#xff0c;代表“旋转 13 个空格”密码将字母A到Z表示为数字 0 到 25&#xff0c;加密后的字母距离明文字母 13 个空格&#xff1a; A变成N&…

【Android】之【自定义View实践】

这里以一个进度条的加载为例子&#xff0c;先看效果&#xff08;运行效果是动态变化的&#xff09; 一、自定义属性 首先在res->values目录下新建attrs资源文件&#xff0c;如下图&#xff1a; 内容如下&#xff1a; <?xml version"1.0" encoding"utf…

SpringBoot基础学习之(九)添加员工的信息

本次项目所有能够使用的静态资源可以免费进行下载 静态资源 在本篇代码DAO层将通过Java文件去实现&#xff0c;在这里就不连接数据&#xff0c;然后通过jdbc将数据库内容的内容显示出来 案例&#xff1a;员工管理系统 上一篇博文的主要的内容是展示员工的信息&#xff0c;本篇…

Oracle JDK 和 OpenJDK 有什么区别?

可能在看这个问题之前很多人和我一样并没有接触和使用过 OpenJDK 。那么 Oracle JDK 和 OpenJDK 之间是否存在重大差异&#xff1f;下面我通过收集到的一些资料&#xff0c;为你解答这个被很多人忽视的问题。 首先&#xff0c;2006 年 SUN 公司将 Java 开源&#xff0c;也就有…

JAVA——网络编程基本概念

&#x1f389;&#x1f389;&#x1f389;点进来你就是我的人了 博主主页&#xff1a;&#x1f648;&#x1f648;&#x1f648;戳一戳,欢迎大佬指点!人生格言&#xff1a;当你的才华撑不起你的野心的时候,你就应该静下心来学习! 欢迎志同道合的朋友一起加油喔&#x1f9be;&am…

腾讯云轻量服务器和云服务器区别对比(超详细)

腾讯云轻量服务器和云服务器有什么区别&#xff1f;为什么轻量应用服务器费用更低&#xff1f;是因为轻量服务器CPU内存性能比云服务器CVM性能差吗&#xff1f;轻量应用服务器适合中小企业或个人开发者搭建企业官网、博客论坛、微信小程序或开发测试环境&#xff0c;云服务器CV…

orcad library builder 建库及报错问题

目录 一.安装orcad library builder 二.orcad library builder 使用 1.建立一个orcad 原理图库测试下 尝试理解tcl那段的意思 xml文件导入建orcad库 折腾了2个多小时&#xff0c;居然没有直接方案搞定&#xff0c;简单记录下&#xff0c;后面遇到该问题的兄弟可参考借鉴&am…

Java集合框架之collection

1. 什么是集合 1.1 概念 对象的容器&#xff0c;实现类对对象常用的操作。 1.2 和数组的区别 数组长度固定&#xff0c;集合长度不固定。数组可以存储基本类型和引用类型&#xff0c;集合只能存储引用类型。 1.3 位置 java.util.*; 2. Collection体系 2.1 Collection父接…

网络编程 1

前言 小亭子正在努力的学习编程&#xff0c;接下来将开启javaEE的学习~~ 分享的文章都是学习的笔记和感悟&#xff0c;如有不妥之处希望大佬们批评指正~~ 同时如果本文对你有帮助的话&#xff0c;烦请点赞关注支持一波, 感激不尽~~ 目录 网络编程 什么是网络编程&#xff1f;…

ASP网上视频点播系统的设计与实现

在线视频服务系统的功能模块划分如下图&#xff08;2-2&#xff09;所示&#xff1a; 电影分类浏览 用户可以通过电影的类别进行浏览。显示近期热门电影&#xff0c;近期点机排行。用户能很方便的找到自己感兴趣的电影进行观看。 电影搜索 如果用户有很明确的目的&#xff0c;…

JUC结构

JUC是java.util.concurrent包的简称在Java5.0添加&#xff0c;目的就是为了更好的支持高并发任务。让开发者进行多线程编程时减少竞争条件和死锁的问题&#xff01;进程与线程的区别&#xff1a;进程 : 一个运行中的程序的集合; 一个进程往往可以包含多个线程,至少包含一个线程…
最新文章