Linux网络编程11——简单的web服务器

news2024/11/15 21:40:47

学习视频链接

02-web大练习的概述_bilibili_哔哩哔哩_bilibilihttps://www.bilibili.com/video/BV1iJ411S7UA/?p=132&spm_id_from=pageDriver&vd_source=0471cde1c644648fafd07b54e303c905

目录

一、项目展示

二、HTTP 协议基础

2.1 HTTP协议基础。

2.2 请求消息(Request)

2.3 响应消息 (Response)

三、简单的代码

3.1 代码

3.2 后续需要增加的内容

3.3 获取需要的数据

3.4 错误处理函数

3.5 正则表达式

3.6 判断文件是否存在

3.7 应答回复客户端

3.8 文件类型区分

         3.9 细节处理

3.10 文件夹处理

3.11 汉字字符编码和解码 


学习目标:实现一个简单的 web 服务器 myhttpd 能够给浏览器提供服务,供用户借助浏览器访问服务器主机中的文件

一、项目展示

启动服务器

访问路径

访问文件

输入了错误的地址,访问不到需要的文件,就会展示错误页面

二、HTTP 协议基础

2.1 HTTP协议基础。

HTTP,超文本传输协议 (HyperText Transfer Protocol)。互联网应用最为广泛的一种网络应用层协议。它可以减少网络传输,使浏览器更加高效。通常 HTTP 消息包括客户机向服务器的请求消息和服务器向客户机的响应消息

2.2 请求消息(Request)

浏览器 -> 发给 -> 服务器。主旨内容包含 4 部分:

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

请求头:说明服务器要使用的附加信息

空行:必须!即使没有请求数据

请求数据:也叫主体,可以添加任意的其他数据

2.3 响应消息 (Response)

服务器 -> 发给 -> 浏览器。主旨内容包含 4 部分:

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

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

空行:必须!

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

三、简单的代码

3.1 代码

#include <stdio.h>  
#include <string.h>  
#include <stdlib.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <sys/wait.h>  
#include <sys/types.h>  
#include <sys/epoll.h>  
#include <unistd.h>  
#include <fcntl.h>  
  
#define MAXSIZE 2048  
 
  
int init_listen_fd(int port, int epfd)  
{  
    // 创建监听的套接字 lfd  
    int lfd = socket(AF_INET, SOCK_STREAM, 0);  
    if (lfd == -1) {      
        perror("socket error");  
        exit(1);  
    }  
    // 创建服务器地址结构 IP+port  
    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");  
        exit(1);  
    }  
    // 设置监听上限  
    ret = listen(lfd, 128);  
    if (ret == -1) {   
        perror("listen error");  
        exit(1);  
    }  
      
    // 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 add lfd error");  
        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");  
        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");  
        exit(1);  
    }  
}  
  
void do_read(int cfd, int epfd)  
{  
    // read cfd 小 -- 大 write 回  
    // 读取一行http协议, 拆分, 获取 get 文件名 协议号  
}  
  
void epoll_run(int port)  
{  
    int i = 0;  
    struct epoll_event all_events[MAXSIZE];  
  
    // 创建一个epoll监听树根  
    int epfd = epoll_create(MAXSIZE);  
    if (epfd == -1) {   
        perror("epoll_create error");  
        exit(1);  
    }  
      
    // 创建lfd,并添加至监听树  
    int lfd = init_listen_fd(port, epfd);  
     
    while (1) {  
        // 监听节点对应事件  
        int ret = epoll_wait(epfd, all_events, MAXSIZE, -1);  
        if (ret == -1) {        
            perror("epoll_wait error");  
            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;  
} 

3.2 后续需要增加的内容

3.3 获取需要的数据

nt 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') {
				//MSG_PEEK 使得recv以拷贝的方式从缓冲区中读取数据(否则读取之后,缓冲区中的数据就没了)
				//试探性的获取缓冲区中的数据量
				n = recv(cfd, &c, 1, MSG_PEEK);
				//缓冲区中有数据并且结尾是 \n  ,则读取数据
				if ((n > 0) && (c == '\n')) {
					recv(cfd, &c, 1, 0);
				}
				else {
					c = '\n';
				}
			}
			buf[i] = c;
			i++;
		}
		else {
			c = '\n';
		}
	}
	buf[i] = '\0';
 
	//recv失败
	if (n == -1) {
		i = -1;
	}
 
	return i;
}

3.4 错误处理函数

// 断开连接
void disconnect(int cfd, int epfd)
{
    int ret = epoll_ctl(epfd, EPOLL_CTL_DEL, cfd, NULL);
    if(ret != 0) {
        perror("epoll_ctl error");
        exit(1);
    }
    close(cfd);
}
  
void do_read(int cfd, int epfd)  
{  
    // 读取一行http协议, 拆分, 获取 get 文件名 协议号
    char line[1024] = { 0 };
    int len = get_line(cfd, line, sizeof(line));  // 确定读出 GET /hello.c HTTP/1.1
    if (len == 0) {
        printf("服务器检测到客户端关闭\n");
        disconnect(cfd, epfd);
    }
    else {
        // 现在要按照空格分割得到 /hello.c
    }

}  

3.5 正则表达式

void do_read(int cfd, int epfd)  
{  
    // 读取一行http协议, 拆分, 获取 get 文件名 协议号
    char line[1024] = { 0 };
    int len = get_line(cfd, line, sizeof(line));  // 确定读出 GET /hello.c HTTP/1.1
    if (len == 0) {
        printf("服务器检测到客户端关闭\n");
        disconnect(cfd, epfd);
    }
    else {
        // 现在要按照空格分割得到 /hello.c
        char method[16], path[256], protocol[16];

        sscanf(line, "%[^ ] %[^ ] %[^ ]", method, path, protocol);

        printf("method=%s, path=%s, protocol=%s\n", method, path, protocol);
    }

} 

现在测试代码

正则表达式字符类

正则表达式数量限定 

3.6 判断文件是否存在

// 处理http请求,判断文件是否存在,回发
void http_request(const char *file)
{
    struct stat sbuf;

    // 判断文件是否存在
    int ret = stat(file, &sbuf);
    if (ret != 0) {
        // 回发浏览器 404 错误页面
        perror("stat");
        exit(1);
    }

    if(S_ISREG(sbuf.st_mode)) {  // 是一个普通文件
        printf("It's a file\n");
    }
}
  
void do_read(int cfd, int epfd)  
{  
    // 读取一行http协议, 拆分, 获取 get 文件名 协议号
    char line[1024] = { 0 };
    int len = get_line(cfd, line, sizeof(line));  // 确定读出 GET /hello.c HTTP/1.1
    if (len == 0) {
        printf("服务器检测到客户端关闭\n");
        disconnect(cfd, epfd);
    }
    else {
        // 现在要按照空格分割得到 /hello.c
        char method[16], path[256], protocol[16];
        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));
            printf("-- len = %d\n", len);
            if (len == '\n') {
                break;
            }
            if(len == -1) {
                break;
            }
        }

        if(strncasecmp(method, "GET", 3) == 0)
        {
            char *file = path + 1;  // 取出客户端要访问的文件名
            http_request(file);
        }
    }
}  

3.7 应答回复客户端

#include <stdio.h>  
#include <string.h>  
#include <stdlib.h>  
#include <netinet/in.h>  
#include <arpa/inet.h>  
#include <sys/wait.h>  
#include <sys/types.h>  
#include <sys/epoll.h>
#include <sys/stat.h>
#include <unistd.h>  
#include <fcntl.h>  
  
#define MAXSIZE 2048  
 
  
int init_listen_fd(int port, int epfd)  
{  
    // 创建监听的套接字 lfd  
    int lfd = socket(AF_INET, SOCK_STREAM, 0);  
    if (lfd == -1) {      
        perror("socket error");  
        exit(1);  
    }  
    // 创建服务器地址结构 IP+port  
    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");  
        exit(1);  
    }  
    // 设置监听上限  
    ret = listen(lfd, 128);  
    if (ret == -1) {   
        perror("listen error");  
        exit(1);  
    }  
      
    // 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 add lfd error");  
        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");  
        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");  
        exit(1);  
    }  
}

// 通过文件名获取文件的类型
const char *get_file_type(const char *name)
{
    char *dot;   
    
    // 自右向左查找‘.’字符, 如不存在返回NULL  
    dot = strrchr(name, '.');     
    if (dot == NULL)  
        return "text/plain; charset=utf-8";  
    if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)  
        return "text/html; charset=utf-8";
    if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)  
        return "image/jpeg";  
    if (strcmp(dot, ".gif") == 0)  
        return "image/gif";  
    if (strcmp(dot, ".png") == 0)  
        return "image/png";  
    if (strcmp(dot, ".css") == 0)  
        return "text/css";  
    if (strcmp(dot, ".au") == 0)  
        return "audio/basic";  
    if (strcmp(dot, ".wav" ) == 0)  
        return "audio/wav";  
    if (strcmp(dot, ".avi") == 0)  
        return "video/x-msvideo";  
    if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)  
        return "video/quicktime";  
    if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)  
        return "video/mpeg";  
    if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)  
        return "model/vrml";  
    if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)  
        return "audio/midi";  
    if (strcmp(dot, ".mp3") == 0)  
        return "audio/mpeg";  
    if (strcmp(dot, ".ogg") == 0)  
        return "application/ogg";  
    if (strcmp(dot, ".pac") == 0)  
        return "application/x-ns-proxy-autoconfig";  

    return "text/plain; charset=utf-8";  
}

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') {
				//MSG_PEEK 使得recv以拷贝的方式从缓冲区中读取数据(否则读取之后,缓冲区中的数据就没了)
				//试探性的获取缓冲区中的数据量
				n = recv(cfd, &c, 1, MSG_PEEK);
				//缓冲区中有数据并且结尾是 \n  ,则读取数据
				if ((n > 0) && (c == '\n')) {
					recv(cfd, &c, 1, 0);
				}
				else {
					c = '\n';
				}
			}
			buf[i] = c;
			i++;
		}
		else {
			c = '\n';
		}
	}
	buf[i] = '\0';
 
	//recv失败
	if (n == -1) {
		i = -1;
	}
 
	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");
        exit(1);
    }
    close(cfd);
}

// 客户端的fd,错误号,错误描述,回发文件类型,文件长度
void send_respond(int cfd, int no, char *disp, char *type, int len)
{
    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];

    // 打开的服务器本地文件 —— cfd 能访问客户端的 socket
    int fd = open(file, O_RDONLY);
    if (fd == -1) {
        // 404 错误页面
        perror("open error");
        exit(1);
    }

    int ret;
    while ((n = read(fd, buf, sizeof(buf))) > 0) {
        ret = send(cfd, buf, n, 0);
        if (ret == -1) {
            if (ret == -1) {  
                perror("send error");     
                exit(1);
            }  
        }
    }

    close(fd);
}

// 处理http请求,判断文件是否存在,回发
void http_request(int cfd, const char *file)
{
    struct stat sbuf;

    // 判断文件是否存在
    int ret = stat(file, &sbuf);
    if (ret != 0) {
        // 回发浏览器 404 错误页面
        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);
        char *type = get_file_type(file);
        send_respond(cfd, 200, "OK", type, 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));  // 确定读出 GET /hello.c HTTP/1.1
    if (len == 0) {
        printf("服务器检测到客户端关闭\n");
        disconnect(cfd, epfd);
    }
    else {
        // 现在要按照空格分割得到 /hello.c
        char method[16], path[256], protocol[16];
        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') {
                break;
            }
            if(len == -1) {
                break;
            }
        }

        if(strncasecmp(method, "GET", 3) == 0)
        {
            char *file = path + 1;  // 取出客户端要访问的文件名
            http_request(cfd, file);
        }
    }
}  
  
void epoll_run(int port)  
{  
    int i = 0;  
    struct epoll_event all_events[MAXSIZE];  
  
    // 创建一个epoll监听树根  
    int epfd = epoll_create(MAXSIZE);  
    if (epfd == -1) {   
        perror("epoll_create error");  
        exit(1);  
    }  
      
    // 创建lfd,并添加至监听树  
    int lfd = init_listen_fd(port, epfd);  
     
    while (1) {  
        // 监听节点对应事件  
        int ret = epoll_wait(epfd, all_events, MAXSIZE, -1);  
        if (ret == -1) {        
            perror("epoll_wait error");  
            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;  
} 

单个文件请求成功

mp3 格式,他的头文件是这样的

3.8 文件类型区分

// 通过文件名获取文件的类型
const char *get_file_type(const char *name)
{
    char *dot;   
    
    // 自右向左查找‘.’字符, 如不存在返回NULL  
    dot = strrchr(name, '.');     
    if (dot == NULL)  
        return "text/plain; charset=utf-8";  
    if (strcmp(dot, ".html") == 0 || strcmp(dot, ".htm") == 0)  
        return "text/html; charset=utf-8";
    if (strcmp(dot, ".jpg") == 0 || strcmp(dot, ".jpeg") == 0)  
        return "image/jpeg";  
    if (strcmp(dot, ".gif") == 0)  
        return "image/gif";  
    if (strcmp(dot, ".png") == 0)  
        return "image/png";  
    if (strcmp(dot, ".css") == 0)  
        return "text/css";  
    if (strcmp(dot, ".au") == 0)  
        return "audio/basic";  
    if (strcmp(dot, ".wav" ) == 0)  
        return "audio/wav";  
    if (strcmp(dot, ".avi") == 0)  
        return "video/x-msvideo";  
    if (strcmp(dot, ".mov") == 0 || strcmp(dot, ".qt") == 0)  
        return "video/quicktime";  
    if (strcmp(dot, ".mpeg") == 0 || strcmp(dot, ".mpe") == 0)  
        return "video/mpeg";  
    if (strcmp(dot, ".vrml") == 0 || strcmp(dot, ".wrl") == 0)  
        return "model/vrml";  
    if (strcmp(dot, ".midi") == 0 || strcmp(dot, ".mid") == 0)  
        return "audio/midi";  
    if (strcmp(dot, ".mp3") == 0)  
        return "audio/mpeg";  
    if (strcmp(dot, ".ogg") == 0)  
        return "application/ogg";  
    if (strcmp(dot, ".pac") == 0)  
        return "application/x-ns-proxy-autoconfig";  

    return "text/plain; charset=utf-8";  
}

// 处理http请求,判断文件是否存在,回发
void http_request(int cfd, const char *file)
{
    struct stat sbuf;

    // 判断文件是否存在
    int ret = stat(file, &sbuf);
    if (ret != 0) {
        // 回发浏览器 404 错误页面
        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);
        char *type = get_file_type(file);
        send_respond(cfd, 200, "OK", type, sbuf.st_size);
        // 回发 给客户端请求数据内容
        send_file(cfd, file);
    }
}

3.9 细节处理

1、没有找到文件的页面

也可以自己在文件夹中写一个错误页面,然后没找到的发发送写过的错误页面

2、和客户端第二次请求 ico 文件处理

如上图,只需要在文件夹中放一个需要的 ico 文件,浏览器就能获取需要的网页图标

3、get 处理完成后删除服务器和客户端的连接

3.10 文件夹处理

拼接一个 html 页面

3.11 汉字字符编码和解码 

 

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

个人博客系统

目录一、项目简介二、项目开发流程2.1 准备工作2.2 实现Vue层2.2 实现Model层2.4 实现Controller层一、项目简介 基于servlet&#xff0c;采用前后端分离的方式&#xff0c;实现个人博客系统&#xff0c;功能包括&#xff1a;登录、注销、编辑、删除博客、发布博客等. 二、项…

用了10年开源工具,换了Smartbi后,3分钟搞定一份报表

大约在20年前&#xff0c;中国企业开始应用国外BI软件&#xff0c;报表工具可以说是BI 1.0时代的代表产物。在BI软件盛行之初&#xff0c;大部分软件都有开源的&#xff0c;从系统到数据库到各类工具、应用&#xff0c;当时大部分企业使用的BI软件包括报表工具&#xff0c;也都…

外汇天眼:外汇占款是什么意思? 与外汇储备之间的差额是由哪些原因造成的?

外汇占款就是指受资国中央银行回收外汇财产而相对投放的本币。 因为人民币是是非非随意换取代币&#xff0c;外资企业导入后需换取成人民币才可以进到商品流通应用&#xff0c;国家以便外资企业兑换外币要投入很多的资产提升了贷币的需要量&#xff0c;产生了外汇占款。 外汇占…

线程池源码解析 3.excute() 方法

线程池源码解析—excute()方法 execute() execute 方法是线程池的核心方法&#xff0c;所有的方法&#xff0c;包括包装的 FutureTask&#xff0c;都是调用这个方法。 大致流程 这里只是总结了一遍大致的流程&#xff0c;一些细节问题见下面的流程图或者参考源码。 当提交任…

【毕业设计】大数据电商销售预测分析 - python 数据分析

文章目录1 前言2 开始分析2.1 数据特征2.2 各项投入与销售额之间的关系2.3 建立销售额的预测模型3 最后1 前言 &#x1f525; Hi&#xff0c;大家好&#xff0c;这里是丹成学长的毕设系列文章&#xff01; &#x1f525; 对毕设有任何疑问都可以问学长哦! 这两年开始&#x…

Java8新特性 CompletableFuture

Java8新特性 CompletableFuture 什么是CompletableFuture&#xff1f; CompletableFuture类的设计灵感来自于 Google Guava 的 ListenableFuture 类&#xff0c;它实现了 Future 和 CompletionStage 接口并且新增了许多方法&#xff0c;它支持 lambda表达式&#xff0c;通过回…

【IDEA插件】这5款IDEA插件,堪称代码BUG检查神器!

随着业务的发展&#xff0c;系统会越来越庞大&#xff0c;原本简单稳定的功能&#xff0c;可能在不断迭代后复杂度上升&#xff0c;潜在的风险也随之暴露&#xff0c;导致最终服务不稳定&#xff0c;造成业务价值的损失。而为了减少这种情况&#xff0c;其中一种比较好的方式就…

5.盒子阴影(重点)

提示&#xff1a;css3中新增了盒子阴影&#xff0c;我们可以使用box-shadow属性为盒子添加阴影。 1、语法&#xff1a; div{ box-shadow:"h-shadow"或者“v-shadow” } 解释&#xff1a; h-shadow 必须&#xff0c;水平阴影位置&#xff0c;允许负值。 v-shado…

UE4 回合游戏项目 18- 退出战斗

在上一篇&#xff08;UE4 回合游戏项目 17- 进入指定区域触发战斗事件&#xff09;基础上完成击败敌人从而退出战斗的功能。 效果&#xff1a; 步骤&#xff1a; 1.打开“battleScenario”蓝图&#xff0c;添加一个自定义事件&#xff0c;命名为“离开战斗” ​ 2.删除所有…

[附源码]Python计算机毕业设计_社区无接触快递栈

项目运行 环境配置&#xff1a; Pychram社区版 python3.7.7 Mysql5.7 HBuilderXlist pipNavicat11Djangonodejs。 项目技术&#xff1a; django python Vue 等等组成&#xff0c;B/S模式 pychram管理等等。 环境需要 1.运行环境&#xff1a;最好是python3.7.7&#xff0c;…

手撕二叉搜索树

目录 一、概念 二、常见操作 2.1 查找操作 2.2 插入操作 2.3 删除操作 三、模型应用 3.1 K模型 3.2 KV模型 3.3 代码完整实现 四、 性能分析 一、概念 二叉搜索树(BST,Binary Search Tree),也称二叉排序树或二叉查找树 它或者是一棵空树&#xff0c;或者是具有以下…

Spring整合Mybatis和Junit小案例(9)

Spring整合Mybatis和Junit环境准备步骤1&#xff1a;准备数据库步骤2&#xff1a;创建项目导入jar包步骤3&#xff1a;根据数据库的表创建模型类步骤4&#xff1a;创建Dao接口步骤5&#xff1a;创建Service接口和实现类步骤6&#xff1a;添加jdbc.properties文件步骤7&#xff…

5种常用格式的数据输出,手把手教你用Pandas实现

导读:任何原始格式的数据载入DataFrame后,都可以使用类似DataFrame.to_csv()的方法输出到相应格式的文件或者目标系统里。本文将介绍一些常用的数据输出目标格式。 01 CSV DataFrame.to_csv方法可以将DataFrame导出为CSV格式的文件,需要传入一个CSV文件名。 df.to_csv(done.…

在 SPRING Boot JPA 中调用带有本机查询中的参数的存储过程

配置pom.xml <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"http://maven.apache.org/POM/4.…

惊了!10万字的Spark全文!

Hello&#xff0c;大家好&#xff0c;这里是857技术社区&#xff0c;我是社区创始人之一&#xff0c;以后会持续给大家更新大数据各组件的合集内容&#xff0c;路过给个关注吧!!! 今天给大家分享一篇小白易读懂的 Spark万字概念长文&#xff0c;本篇文章追求的是力求精简、通俗…

Linux(基于Centos7)(一)

文章目录一、任务介绍二、基本操作命令三、目录操作命令四、文件操作命令五、查看系统信息六、其他常用命令一、任务介绍 Linux服务器配置与管理&#xff08;基于Centos7.2&#xff09;任务目标&#xff08;一&#xff09; 实施该工单的任务目标如下&#xff1a; 知识目标 1、…

RNA剪接增强免疫检查点抑制疗效

什么是 RNA 剪接&#xff1f;真核生物基因包含一系列外显子和内含子&#xff0c;内含子必须在转录过程中被移除以便成熟的 mRNA 被翻译成蛋白质&#xff0c;RNA 剪接则是这一过程中至关重要的一步。RNA 剪接包含两类剪接事件。组成型剪接 (constitutive splicing): RNA 剪接的一…

【蓝桥杯Web】第十四届蓝桥杯(Web 应用开发)模拟赛 1 期-职业院校组 | 精品题解

&#x1f9d1;‍&#x1f4bc; 个人简介&#xff1a;一个不甘平庸的平凡人&#x1f36c; &#x1f5a5;️ Nodejs专栏&#xff1a;Node.js从入门到精通 &#x1f5a5;️ TS知识总结&#xff1a;十万字TS知识点总结 &#x1f449; 你的一键三连是我更新的最大动力❤️&#xff0…

企业级Java EE架构设计精深实践

内容简介 本书全面、深入介绍了企业级Java EE设计的相关内容&#xff0c;内容涵盖了Java EE架构设计的常见问题。 本书每一章讲解一个Java EE领域的具体问题&#xff0c;采用问题背景、需求分析、解决思路、架构设计、实践示例和章节总结的顺序组织内容&#xff0c;旨在通过分…

生成树(STP)

1.详细说明STP的工作原理 在二层交换网络中&#xff0c;逻辑的阻塞部分的接口&#xff0c;实现从跟交换机到所有节点唯一的路径称为最佳路径&#xff0c;生成一个没有环路的拓扑。当最佳路径出现故障时&#xff0c;个别被阻塞的接口将打开&#xff0c;形成备份链路。 2. STP的…