WebServer 解析HTTP 响应报文
一、基础API部分,介绍stat、mmap、iovec、writev、va_list
1.1 stat
作用:获取文件信息
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>// 获取文件属性,存储在statbuf中
int stat(const char *pathname, struct stat *statbuf);struct stat {mode_t st_mode; /* 文件类型和权限 */off_t st_size; /* 文件大小,字节数 */
};
- 返回值:成功返回0,失败返回-1;
- 参数:文件路径(名),struct stat 类型的结构体
struct stat 结构体详解:
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 */ 最后一次改变该文件状态的时间
};
stat结构体中的st_mode 则定义了下列数种情况:
S_IFMT 0170000 文件类型的位遮罩S_IFSOCK 0140000 套接字S_IFLNK 0120000 符号连接S_IFREG 0100000 一般文件S_IFBLK 0060000 区块装置S_IFDIR 0040000 目录S_IFCHR 0020000 字符装置S_IFIFO 0010000 先进先出
S_ISUID 04000 文件的(set user-id on execution)位S_ISGID 02000 文件的(set group-id on execution)位S_ISVTX 01000 文件的sticky位
S_IRUSR(S_IREAD) 00400 文件所有者具可读取权限S_IWUSR(S_IWRITE)00200 文件所有者具可写入权限S_IXUSR(S_IEXEC) 00100 文件所有者具可执行权限
S_IRGRP 00040 用户组具可读取权限S_IWGRP 00020 用户组具可写入权限S_IXGRP 00010 用户组具可执行权限
S_IROTH 00004 其他用户具可读取权限S_IWOTH 00002 其他用户具可写入权限S_IXOTH 00001 其他用户具可执行权限
上述的文件类型在POSIX中定义了检查这些类型的宏定义:S_ISLNK (st_mode) 判断是否为符号连接S_ISREG (st_mode) 是否为一般文件S_ISDIR (st_mode) 是否为目录S_ISCHR (st_mode) 是否为字符装置文件S_ISBLK (s3e) 是否为先进先出S_ISSOCK (st_mode) 是否为socket若一目录具有sticky位(S_ISVTX),则表示在此目录下的文件只能被该文件所有者、此目录所有者或root来删除或改名,在linux中,最典型的就是这个/tmp目录啦。
1.2 mmap
用于将一个文件或其他对象映射到内存,提高文件的访问速度
void* mmap(void* start,size_t length,int port,int flags,int fd,off_t offset);
int munmap(void* start,size_t length);
- start : 映射区的开始地址,设置为0时表示由系统决定映射区的起始地址
- length : 映射区的长度
- prot : 期望的内存保护标志,不能与文件的打开模式冲突
- PROT_READ 表示页内容可以读取
- flags : 指定映射对象的类型,映射选项和映射页是否可以共享
- MAP_PRIVATE 建立一个写入时拷贝的私有映射,内存区域的写入不会影响到原文件
- fd : 有效的文件描述符,一般是由open()函数返回
- off_toffset : 被映射对象内容的起点
我的往期文章:
了解Linux 的 mmap --- 笔记_呵呵哒( ̄▽ ̄)"的博客-CSDN博客https://heheda.blog.csdn.net/article/details/132119077
1.3 iovec
定义了一个向量元素,一般把这个结构用作一个多元素的数组
struct iovec {void *iov_base; /* starting address of buffer */size_t iov_len; /* size of buffer */
};
- iov_base 指向数据的地址
- iov_len 表示数据的长度
- iovec是一个结构体,里面有两个元素,指针成员iov_base指向一个缓冲区,这个缓冲区是存放的是writev将要发送的数据
- 成员iov_len表示实际写入的长度
1.4 writev
writev函数用于在一次函数调用中写多个非连续缓冲区,有时也将这该函数称为聚集写
#include <sys/uio.h>
ssize_t writev(int filedes,const struct iovec* iov,int iovcnt);
- filedes 表示文件描述符
- iov 为前述io向量机制结构体 iovec
- iovcnt 为结构体的个数
若成功则返回已写的字节数,若出错则返回 -1
- writev 以顺序 iov[0],iov[1] 至 iov[iovcnt - 1] 从缓冲区中聚集输出数据
- writev 返回输出的字节总数。通常,它应等于所有缓冲区长度之和
特别注意:循环调用writev时,需要重新处理iovec中的指针和长度,该函数不会对这两个成员做任何处理
writev()函数是Linux系统中的一个系统调用,其作用是将多个缓冲区的数据一起写入到文件描述符中(各个缓冲区的数据可以是不连续的)。
1.5 va_list
- va_list 是C语言中用于解决变参问题的一组宏,它定义于标准库 stdarg.h 中
- 通过命令 $man va_arg 也可以从手册中查询到 va_list 的用法
#include <stdarg.h>void va_start(va_list ap, last);
type va_arg(va_list ap, type);
void va_end(va_list ap);
void va_copy(va_list dest, va_list src);
对可变参数列表的处理过程:
- 用 va_list 定义一个可变参数列表
- 用 va_start 获取函数可变参数列表
- 用 va_arg 循环处理可变参数列表中的各个可变参数
- 用 va_end 结束对可变参数列表的处理
#include <stdio.h>
#include <stdarg.h>static void show_numbers(int num,...) {va_list va;/* 初始化va,让va指向num后面的参数*/va_start(va,num);while (num--) {/* 通过while循环依次获取遍历后面的参数 */printf("%d\n",va_arg(va,int));}va_end(va);
}int main(void) {show_numbers(3,110,111,112);return 0;
}
heheda@heheda:~/Linux/webserver$ gcc va_list.cpp
heheda@heheda:~/Linux/webserver$ ./a.out
110
111
112
1.5.1 va_start
void va_start(va_list ap, last_arg);
- ap: 这是一个 va_list 类型的对象,它用来存储通过 va_arg 获取额外参数时所必需的信息
- 这个函数的作用是初始化
ap
变量,它与 va_arg 和 va_end 函数一起使用 - last_arg 是最后一个传递给函数的已知的固定参数,即省略号之前的参数
#include <stdarg.h>
#include <stdio.h> // 采用可变参数,第一个参数用于标识参数数量
int sum(int num_args,...) {int val = 0;va_list ap;int i;va_start(ap,num_args);for(i=0;i<num_args;i++) {val += va_arg(ap,int);}va_end(ap);return val;
}void test1() {printf("10、20 和 30 的和 = %d\n", sum(3, 10, 20, 30) );printf("4、20、25 和 30 的和 = %d\n", sum(4, 4, 20, 25, 30) );
}int main() {test1();return 0;
}
heheda@heheda:~/Linux/webserver$ gcc va_list.cpp
heheda@heheda:~/Linux/webserver$ ./a.out
10、20 和 30 的和 = 60
4、20、25 和 30 的和 = 79
1.5.2 vsnprintf
作用:使用vsnprintf()
用于向一个字符串缓冲区打印格式化字符串,且可以限定打印的格式化字符串的最大长度
#include <stdarg.h>
int vsnprintf(char *str, size_t size, const char *format, va_list ap);- 功能:将可变参数格式化输出到一个字符数组- 参数:- str:把生成的格式化的字符串存放在这里- size:str可接受的最大字符数- format:指定输出格式的字符串,它决定了你需要提供的可变参数的类型、个数和顺序- ap:va_list变量- 返回值:- 成功:返回最终生成字符串的长度- 失败:返回负值
- 第一个参数:目标缓冲区(字符数组)
- 第二个参数,限定最多打印到缓冲区的字符数量为
n-1
个(留位置给\0
) - 第三个参数,打印的格式(如
%d:%s
) - 第四个参数,可变参数arg,需要用
va_start
初始化
1.5.3 fprintf
int fprintf(FILE *stream, const char *format, ...);
#include <cassert>
#include <cstring>
#include <stdlib.h>
#include <stdarg.h>
#include <stdio.h>
#include <cerrno>
#include <ctime>#define DEBUG 0
#define NOTICE 1
#define WARNING 2
#define FATAL 3const char* log_level[] = {"DEBUG","NOTICE","WARNING","FATAL"};// 采用可变参数列表
void logging(int level,const char* format,...) {assert(level >= DEBUG && level <= FATAL);char* name = getenv("USER"); // 获取环境变量中的用户(执行命令的用户)char logInfo[1024];// 获取可变参数列表va_list ap;// ap -> char*va_start(ap,format);vsnprintf(logInfo,sizeof(logInfo),format,ap);va_end(ap);// ap = NULL;// 根据日志等级选择打印到 stderr/stdoutFILE *out = (level == FATAL) ? stderr:stdout;// 格式化打印到文件中fprintf(out, "%s | %u | %s | %s\n", \log_level[level], \(unsigned int)time(nullptr),\name == nullptr ? "unknow":name,\logInfo);
}int main() {logging(DEBUG, "socket create success: %d", 114514);logging(FATAL, "socket:%s:%d", strerror(errno), 11234);return 0;
}
heheda@heheda:~/Linux/webserver$ g++ 利用可变参数实现log类.cpp
heheda@heheda:~/Linux/webserver$ ./a.out
DEBUG | 1694175430 | heheda | socket create success: 114514
FATAL | 1694175430 | heheda | socket:Success:11234
二、 HTTP请求和响应步骤
2.1 HTTP 响应报文格式
响应行\r\n
响应头\r\n
空行\r\n
响应体\r\n提示: 每项信息之间都要有一个\r\n进行分割
2.2 流程图
浏览器端发出HTTP请求报文,服务器端接收该报文并调用 process_read 对其进行解析,根据解析结果 HTTP_CODE,进入相应的逻辑和模块
- 服务器子线程完成报文的解析与响应;
- 主线程监测读写事件,调用 read_once 和 http_conn::write 完成数据的读取与发送
2.3 HTTP_CODE 含义
2.4 do_request
do_request 函数的返回值是对请求的文件分析后的结果,一部分是语法错误导致的 BAD_REQUEST,一部分是 do_request 的返回结果(NO_RESOURCE、FORBIDDEN_REQUEST、FILE_REQUEST)
do_request 函数:
- 将网络将网站根目录 和 url 文件拼接
- 接着通过 stat 判断该文件属性
- 为了提高访问速度,可通过mmap进行映射,将普通文件映射到内存逻辑地址
// 网站的根目录
const char* doc_root = "/home/nowcoder/webserver/resources";// 当得到一个完整、正确的HTTP请求时,我们就分析目标文件的属性,
// 如果目标文件存在、对所有用户可读,且不是目录,则使用mmap将其
// 映射到内存地址m_file_address处,并告诉调用者获取文件成功
http_conn::HTTP_CODE http_conn::do_request()
{// "/home/nowcoder/webserver/resources"// 将初始化的m_real_file赋值为网站根目录strcpy( m_real_file, doc_root );int len = strlen( doc_root );strncpy( m_real_file + len, m_url, FILENAME_LEN - len - 1 );// 获取m_real_file文件的相关的状态信息,-1失败,0成功// 通过stat获取请求资源文件信息,成功则将信息更新到m_file_stat结构体// 失败返回NO_RESOURCE状态,表示资源不存在if ( stat( m_real_file, &m_file_stat ) < 0 ) {return NO_RESOURCE;}// 判断文件的权限,是否可读,不可读则返回FORBINDDEN_REQUEST状态if ( ! ( m_file_stat.st_mode & S_IROTH ) ) {return FORBIDDEN_REQUEST;}// 判断文件类型,如果是目录,则返回BAD_REQUEST,表示请求报文有误if ( S_ISDIR( m_file_stat.st_mode ) ) {return BAD_REQUEST;}// 以只读方式获取文件描述符,通过mmap将该文件映射到内存中int fd = open( m_real_file, O_RDONLY );// 创建内存映射// 通过mmap将该文件映射到内存中m_file_address = ( char* )mmap( 0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );// 避免文件描述符的浪费和占用close( fd );// 表示请求文件存在,且可以访问return FILE_REQUEST;
}
unmap 取消映射
// 对内存映射区执行munmap操作
void http_conn::unmap() {if( m_file_address ){munmap( m_file_address, m_file_stat.st_size );m_file_address = 0;}
}
2.5 process_write
根据 do_request 的返回状态,服务器子线程调用 process_write 向 m_write_buf 中写入响应报文
- add_status_line函数,添加状态行: http/1.1 状态码 状态消息
- add_headers函数添加消息报头,内部调用add_content_length 和 add_linger函数
- content-length 记录响应报文长度,用于浏览器端判断服务器是否发送完的数据
- connection 记录连接状态,用于告诉浏览器端保持长连接
- add_blank_line添加空行
add_status_line、add_headers、add_content_length、add_linger、add_blank_line这5个函数,均是内部调用 add_response 函数更新 m_write_idx 指针和缓冲区 m_write_buf 中的内容
2.5.1 add_response
// 往写缓冲中写入待发送的数据
bool http_conn::add_response( const char* format, ... ) {// 如果写入内容超出m_write_buf大小则报错if( m_write_idx >= WRITE_BUFFER_SIZE ) {return false;}// 定义可变参数列表va_list arg_list;// 将变量arg_list初始化为传入参数va_start( arg_list, format );// 将数据format从可变参数列表写入缓冲区写,返回写入数据的长度int len = vsnprintf( m_write_buf + m_write_idx, WRITE_BUFFER_SIZE - 1 - m_write_idx, format, arg_list );// 如果写入的数据长度超过缓冲区剩余空间,则报错if( len >= ( WRITE_BUFFER_SIZE - 1 - m_write_idx ) ) {va_end( arg_list );return false;}// 更新m_write_idx位置m_write_idx += len;// 清空可变参列表va_end( arg_list );return true;
}
2.5.2 add_status_line
// 添加状态行
bool http_conn::add_status_line( int status, const char* title ) {return add_response( "%s %d %s\r\n", "HTTP/1.1", status, title );
}
2.5.3 add_headers
// 添加消息报头,具体的添加文本长度
bool http_conn::add_headers(int content_len) {add_content_length(content_len);add_content_type();add_linger();add_blank_line();
}
2.5.3 add_headers
// 添加消息报头,具体的添加文本长度
bool http_conn::add_headers(int content_len) {add_content_length(content_len);add_content_type();add_linger();add_blank_line();
}
2.5.4 add_content_length
// 添加Content-Length,表示响应报文的长度
bool http_conn::add_content_length(int content_len) {return add_response( "Content-Length: %d\r\n", content_len );
}
2.5.5 add_linger
// 添加连接状态,通知浏览器端是保持连接还是关闭
bool http_conn::add_linger()
{return add_response( "Connection: %s\r\n", ( m_linger == true ) ? "keep-alive" : "close" );
}
2.5.6 add_blank_line
// 添加空行
bool http_conn::add_blank_line()
{return add_response( "%s", "\r\n" );
}
2.5.7 add_content
// 添加文本content
bool http_conn::add_content( const char* content )
{return add_response( "%s", content );
}
2.5.8 add_content_type
// 添加文本类型,这里是html
bool http_conn::add_content_type() {return add_response("Content-Type:%s\r\n", "text/html");
}
响应报文分为两种
- ① 一种是请求文件的存在,通过
io
向量机制iovec
,声明两个iovec
- 第一个指向
m_write_buf(包含响应首行和响应头部)
- 第二个指向
mmap
的地址m_file_address
;(响应体)
- 第一个指向
- ② 一种是请求出错,这时候只申请一个
iovec
,指向m_write_buf(包含响应首行、响应头部、响应体)
>>process_write : 根据服务器处理HTTP请求的结果,决定返回给客户端的内容
// 定义HTTP响应的一些状态信息
const char* ok_200_title = "OK";
const char* error_400_title = "Bad Request";
const char* error_400_form = "Your request has bad syntax or is inherently impossible to satisfy.\n";
const char* error_403_title = "Forbidden";
const char* error_403_form = "You do not have permission to get file from this server.\n";
const char* error_404_title = "Not Found";
const char* error_404_form = "The requested file was not found on this server.\n";
const char* error_500_title = "Internal Error";
const char* error_500_form = "There was an unusual problem serving the requested file.\n";// 根据服务器处理HTTP请求的结果,决定返回给客户端的内容
bool http_conn::process_write(HTTP_CODE ret) {switch (ret){// 内部错误,500case INTERNAL_ERROR:// 状态行add_status_line( 500, error_500_title );// 消息头add_headers( strlen( error_500_form ) );if ( ! add_content( error_500_form ) ) {return false;}break;// 报文语法有误,400case BAD_REQUEST:add_status_line( 400, error_400_title );add_headers( strlen( error_400_form ) );if ( ! add_content( error_400_form ) ) {return false;}break;// 请求资源不存在,404case NO_RESOURCE:add_status_line( 404, error_404_title );add_headers( strlen( error_404_form ) );if ( ! add_content( error_404_form ) ) {return false;}break;// 资源没有访问权限,403case FORBIDDEN_REQUEST:add_status_line( 403, error_403_title );add_headers(strlen( error_403_form));if ( ! add_content( error_403_form ) ) {return false;}break;// 请求资源可以正常访问,200case FILE_REQUEST:add_status_line(200, ok_200_title );// 如果请求的资源存在if(m_file_stat.st_size !=0 ) {add_headers(m_file_stat.st_size);// 第一个iovec指针指向响应报文缓冲区,长度指向m_write_idxm_iv[ 0 ].iov_base = m_write_buf;m_iv[ 0 ].iov_len = m_write_idx;// 第二个iovec指针指向mmap返回的文件指针,长度指向文件大小m_iv[ 1 ].iov_base = m_file_address;m_iv[ 1 ].iov_len = m_file_stat.st_size;m_iv_count = 2;// 发送的全部数据为响应报文头部信息和文件大小bytes_to_send = m_write_idx + m_file_stat.st_size;return true;} else {// 如果请求的资源大小为0,则返回空白html文件const char* ok_string = "<html><body></body></html>";add_headers(strlen(ok_string));if(!add_content(ok_string)) return false;}default:return false;}// 除FILE_REQUEST状态之外,其余状态只申请一个iovec,指向响应报文缓冲区m_iv[ 0 ].iov_base = m_write_buf;m_iv[ 0 ].iov_len = m_write_idx;m_iv_count = 1;bytes_to_send = m_write_idx;return true;
}
2.6 write 写事件
该函数实现了分散写的功能,也就是把要发送的数据分为多个部分进行传输
① 首先,在函数开始时,会判断是否存在字节需要发送,如果没有,则结束此次响应
② 通过writev()函数进行分散写操作,将 m_iv 数组中保存的多个缓冲区的数据写入到套接字中。
temp : 表示本次写入的字节数量
- bytes_have_send : 记录已经发送的字节数
- bytes_to_send : 表示剩余需要发送的字节数
// 写HTTP响应
// 写事件是指服务器向客户端“写”数据,也就是服务器向客户端发送数据(即HTTP响应报文)
bool http_conn::write()
{int temp = 0;// 若要发送的数据长度为0// 表示响应报文为空,一般不会出现这种情况if ( bytes_to_send == 0 ) {// 将要发送的字节为0,这一次响应结束。modfd( m_epollfd, m_sockfd, EPOLLIN ); // 重新初始化HTTP对象init();return true;}while(1) {// 分散写// 将响应报文的状态行、消息头、空行和响应正文发送给浏览器端temp = writev(m_sockfd, m_iv, m_iv_count);if ( temp <= -1 ) {// 判断缓冲区是满了// 如果TCP写缓冲没有空间,则等待下一轮EPOLLOUT事件,虽然在此期间,// 服务器无法立即接收到同一客户的下一个请求,但可以保证连接的完整性if( errno == EAGAIN ) {// 重新注册写事件modfd( m_epollfd, m_sockfd, EPOLLOUT );return true;}// 如果发送失败,但不是缓冲区问题,取消映射unmap();return false;}bytes_have_send += temp;// 已发送的字节数// 更新已发送字节数bytes_to_send -= temp;// 剩余需要发送的字节数// 第一个iovec头部信息的数据已发送完,发送第二个iovec数据if (bytes_have_send >= m_iv[0].iov_len) {// 不再继续发送头部信息m_iv[0].iov_len = 0;m_iv[1].iov_base = m_file_address + (bytes_have_send - m_write_idx);m_iv[1].iov_len = bytes_to_send;}// 继续发送第一个iovec头部信息的数据else{m_iv[0].iov_base = m_write_buf + bytes_have_send;m_iv[0].iov_len = m_iv[0].iov_len - temp;}// 判断条件,数据已全部发送完if (bytes_to_send <= 0){// 取消映射unmap();// 在epoll树上重置EPOLLONESHOT事件modfd(m_epollfd, m_sockfd, EPOLLIN);// 浏览器的请求为长连接if (m_linger){// 重新初始化HTTP对象init();return true;}else{return false;}}}
}
2.7 总结:生成响应报文的具体流程(来自这篇文章)
- 进行请求报文的时候,进行URL分析。使用一个文件或其他对象映射到内存,不能映射则报错!
- 根据状态码,若为错误状态码,则提取对应的报错介绍和报错信息,作为响应头和响应正文;若为正确的状态码,则提取成功信息作为响应头,提取的内存映射作为响应正文。根据当前的响应头和响应正文的情况,对写缓冲区进行相应的写入(状态行、响应头)
- 将写事件写入epoll事件表,之后主线程会自动将写缓冲区中的前半部分报文和响应正文通过分散写传回给客户端
2.8 演示效果:
heheda@heheda:~/Linux/heheda_test/webserver$ g++ main.cpp -pthread
heheda@heheda:~/Linux/heheda_test/webserver$ ./a.out
按照如下格式运行:a.out port_number
heheda@heheda:~/Linux/heheda_test/webserver$ ./a.out 9999
create the 0th thread
create the 1th thread
create the 2th thread
create the 3th thread
create the 4th thread
create the 5th thread
create the 6th thread
create the 7th thread
heheda read
读取到了数据:GET /index.html HTTP/1.1
Host: 192.168.90.131:9999
Connection: keep-alive
Upgrade-Insecure-Requests: 1
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6got 1 http line : GET /index.html HTTP/1.1
got 1 http line : Host: 192.168.90.131:9999
got 1 http line : Connection: keep-alive
got 1 http line : Upgrade-Insecure-Requests: 1
oop!unknow header Upgrade-Insecure-Requests: 1
got 1 http line : User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
oop!unknow header User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
got 1 http line : Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
oop!unknow header Accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.7
got 1 http line : Accept-Encoding: gzip, deflate
oop!unknow header Accept-Encoding: gzip, deflate
got 1 http line : Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
oop!unknow header Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
got 1 http line :
write....
heheda read
读取到了数据:GET /images/image1.jpg HTTP/1.1
Host: 192.168.90.131:9999
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
Accept: image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://192.168.90.131:9999/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6got 1 http line : GET /images/image1.jpg HTTP/1.1
got 1 http line : Host: 192.168.90.131:9999
got 1 http line : Connection: keep-alive
got 1 http line : User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
oop!unknow header User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
got 1 http line : Accept: image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
oop!unknow header Accept: image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
got 1 http line : Referer: http://192.168.90.131:9999/index.html
oop!unknow header Referer: http://192.168.90.131:9999/index.html
got 1 http line : Accept-Encoding: gzip, deflate
oop!unknow header Accept-Encoding: gzip, deflate
got 1 http line : Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
oop!unknow header Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
got 1 http line :
write....
heheda read
读取到了数据:GET /favicon.ico HTTP/1.1
Host: 192.168.90.131:9999
Connection: keep-alive
User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
Accept: image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
Referer: http://192.168.90.131:9999/index.html
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6got 1 http line : GET /favicon.ico HTTP/1.1
got 1 http line : Host: 192.168.90.131:9999
got 1 http line : Connection: keep-alive
got 1 http line : User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
oop!unknow header User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/116.0.0.0 Safari/537.36 Edg/116.0.1938.69
got 1 http line : Accept: image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
oop!unknow header Accept: image/webp,image/apng,image/svg+xml,image/*,*/*;q=0.8
got 1 http line : Referer: http://192.168.90.131:9999/index.html
oop!unknow header Referer: http://192.168.90.131:9999/index.html
got 1 http line : Accept-Encoding: gzip, deflate
oop!unknow header Accept-Encoding: gzip, deflate
got 1 http line : Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
oop!unknow header Accept-Language: zh-CN,zh;q=0.9,en;q=0.8,en-GB;q=0.7,en-US;q=0.6
got 1 http line :
write....
推荐和参考文章:
超详细的HTTP协议请求报文、响应报文教程! - 知乎 (zhihu.com)
“Web 服务器” 笔记04 ------ 生成、写HTTP响应_http响应报文为啥用writev_CV发烧友的博客-CSDN博客
writev函数 - lypbendlf - 博客园 (cnblogs.com)
最新版Web服务器项目详解 - 06 http连接处理(下) (qq.com)
【webserver】 第8节 响应报文的生成_webserver中http处理类中写缓冲区大小小于读缓冲区_几日行云的博客-CSDN博客
相关文章:
WebServer 解析HTTP 响应报文
一、基础API部分,介绍stat、mmap、iovec、writev、va_list 1.1 stat 作用:获取文件信息 #include <sys/types.h> #include <sys/stat.h> #include <unistd.h>// 获取文件属性,存储在statbuf中 int stat(const char *…...
开利网络参与“大湾区独角兽创投大赛”进行蚓链数字生态项目路演
9月10日,广州市开利网络科技有限公司受邀参与位于广州国际医药港的“大湾区独角兽创投大赛”,进行“蚓链——企业数字化生态”项目的初赛路演。“大湾区独角兽创投大赛”是由中国企业新闻观察网指导,由中国商协会服务工作委员会、广东蚁米孵化…...
前端的8种跨域解决方案
在前端开发中,常见的跨域解决方案有以下8种: JSONP(JSON with Padding):利用<script>标签的跨域特性,通过动态创建<script>标签,请求一个带有回调函数的接口,服务器返回…...
Linux知识点 -- 网络编程套接字
Linux知识点 – 网络编程套接字 文章目录 Linux知识点 -- 网络编程套接字一、预备知识1.认识端口号2.套接字3.TCP协议与UDP协议4.网络字节序 二、socket编程接口1.socket常见API2.sockaddr结构 三、UDP套接字编程1.直接打印客户端信息2.执行客户端发来的指令3.多用户聊天4.在wi…...
逆向大漠插件/用VB6.0实现后台鼠标移动和后台鼠标左键点击
自动化设计软件,在一款做门的设计软件CypCut6.3 上实现了自动化勾选了 复选框。一切都是基于后台的。 Private Const GW_CHILD 5 Private Const GW_HWNDFIRST 0 Private Const GW_HWNDNEXT 2 Public Declare Function FindWindow Lib "user32" Alias &…...
重庆OV证书和EV证书有什么区别
SSL数字证书按照保护的域名数量和类型可以分为单域名SSL证书、多域名SSL证书和通配符SSL证书三种,按照验证方式可以将SSL数字证书分为DV基础型SSL证书、OV企业型SSL证书和EV增强型SSL证书三种。今天就随SSL盾小编了解OV证书和EV证书的区别。 1.OV企业型SSL证书由CA…...
uni-app(微信小程序)图片旋转放缩,文字绘制、海报绘制
总结一下: 要进行海报绘制离不开canvas,我们是先进行图片,文字的拖拽、旋转等操作 最后再对canvas进行绘制,完成海报绘制。 背景区域设置为 position: relative,方便图片在当前区域中拖动等处理。添加图片࿰…...
Spring Boot 2.x基础教程
Spring Boot 2.x基础教程 一、简介1. Spring Boot 2.x 简介2. Spring Boot 2.x 特点3. Spring Boot 2.x 与 Spring Framework 的关系 二、Spring Boot 2.x 环境搭建1. JDK环境安装与配置2. Maven环境安装与配置3. Spring Boot 2.x 项目创建 三、核心功能1. 配置文件及其加载顺序…...
汽车红外夜视系统行业发展总体概况
汽车红外夜视系统是一种技术,旨在帮助驾驶员在夜间或低光条件下提供更好的视觉能力。它利用红外光谱的特性来检测和显示在正常光线下难以察觉的热能辐射。这使驾驶员能够在夜间或恶劣天气条件下更好地识别和辨别道路上的物体、行人、动物或其他车辆。 汽车红外夜视…...
Java 和 PHP GC 的差异和差异出现的原因
JAVA 的 GC 处理 判断草死掉的两种方式:引用计数和可达性分析 可达性分析对 JAVA 比较好用的原因是 JAVA遵守这面向对象的严格要求,每个变量都被对象包裹,所以每个变量都能通过对象来进行遍历找到,最终判断他们的是否被引用&…...
loguru logger使用
一、基本使用 ①标准使用 from loguru import logger# 在标准输出里面输出一行debug日志 logger.debug("Thats dubug")②设置输出格式 from loguru import loggerlogger.remove(0) # 先删除格式 logger.add(sink./logger.log, format"{time: %Y-%m-%d %H:%M…...
vue-自适应布局-postcss-pxtorem
原理: 比如一个375px设计稿 其中一个320px宽度的元素 如何实现自适应布局呢? 其实可以这样理解: 我们先计算出375屏幕时候320px的大小,在屏幕变化时候,这些元素都会等比例缩放 比如屏幕从375 变为750px时候࿰…...
9.12|day 5|day 44 |完全背包| 518. 零钱兑换 II | 377. 组合总和 Ⅳ
● 完全背包 主要是看清01背包和完全背包的区别 //01背包 for(int i 0;i<weight.size();i){ for(int j bagWeight;j>weight[i];j--){dp[j] Math.max(dp[j],dp[j-weight[i]]value[i]); } } //完全背包 for(int i 0;i<weight.size();i){for(int j weight[i];j<…...
C++ 中的原子变量(std::atomic)使用指南
目录 C 中的原子变量(std::atomic)使用指南基本概念使用方法创建原子变量读取值修改值原子操作 常见应用场景1. 计数器2. 控制标志3. 链表和数据结构 示例代码结论 C 中的原子变量(std::atomic)使用指南 原子变量(std…...
【用unity实现100个游戏之9】使用Unity制作类八方旅人、饥荒风格的俯视角2.5D游戏
前言 2.5D游戏 是一种介于二维和三维之间的游戏形式。它通常在二维平面上展示游戏内容,但利用三维技术来实现更加逼真的图像效果。 在2.5D游戏中,角色和环境通常是以平面的形式呈现,但可以在垂直方向上移动。这意味着玩家可以在一个相对较薄…...
如何在群晖中,正确配置 docker 的 ipv6 地址
参考 2023年9月12日 https://synocommunity.com/ https://github.com/wangliangliang2/fix_synology_docker_ipv6 https://post.smzdm.com/p/an3np8m7/ 正文 关于这个话题,国内搜索引擎得到的结果出奇的一致,且过时。 (看的我脑壳痛&#…...
XSS入门 XSS Challenges
level1(直接注入) <script>alert(xss)</script>level2(双引号闭合标签) 测试 <sCr<ScRiPt>IPT>OonN"\/(hrHRefEF)</sCr</ScRiPt>IPT>发现<>"被转换,构造新的语句 "><script>alert(/xss/)</…...
李沐《动手学深度学习》torch.cat() 和 torch.stack()的区别及思考
一、问题引出 好久没更新啦!最近在学习沐神《动手学深度学习》6.5节池化层的时候,发现沐神在两处相似的地方使用了两种Python拼接函数torch.cat()和torch.stack(): 百思不得其解,于是查阅相关文档之后终于弄清楚了两者之间的区别…...
【算法与数据结构】235、LeetCode二叉搜索树的最近公共祖先
文章目录 一、题目二、解法三、完整代码 所有的LeetCode题解索引,可以看这篇文章——【算法和数据结构】LeetCode题解。 一、题目 二、解法 思路分析:本题和这道题类似【算法与数据结构】236、LeetCode二叉树的最近公共祖先,相同的算法也能解…...
bboss 流批一体化框架 与 数据采集 ETL
数据采集 ETL 与 流批一体化框架 特性: 高效、稳定、快速、安全 bboss 是一个基于开源协议 Apache License 发布的开源项目,主要由以下三部分构成: Elasticsearch Highlevel Java Restclient , 一个高性能高兼容性的Elasticsea…...
JVM详细教程
JVM 前言 还在完善中先发布 JVM虚拟机厂家多钟多样,具体实现细节可能不一样,这里主要讲的是虚拟机的规范,以下内容融合了各个平台发布的内容和周志明老师的《深入理解java虚拟机》 JVM概述 如何理解jvm跨平台? 编译成汇编代码…...
Smartbi吴华夫:后疫情时代,BI发展趋势的观察与应对
沿着旧地图找不到新大陆,“基于指标体系的可视化分析和增强分析”成为BI发展新阶段。Smartbi V11系列新品与时俱进,以指标为核心,同时融合BI应用,赋能管理者和业务,成为引领数字化运营的新航标! ——思迈特…...
软件设计模式系列之三———工厂方法模式
1 模式的定义 工厂方法模式是一种常见的设计模式,属于创建型设计模式之一,它在软件工程中用于对象的创建。该模式的主要思想是将对象的创建过程抽象化,将具体对象的实例化延迟到子类中完成,以便在不同情况下可以创建不同类型的对…...
pytorch 多卡分布式训练 调用all_gather_object 出现阻塞等待死锁的问题
pytorch 多卡分布式训练 torch._C._distributed_c10d中的函数all_gather_object 出现阻塞等待死锁的问题 解决办法就是 在进程通信之前调用torch.cuda.set_device(local_rank) For NCCL-based processed groups, internal tensor representations of objects must be moved …...
SpringMvc增删改查
SpringMvc增删改查 一、前期准备二、逆向生成增删改查2.2.aspect切面层2.3.Mybatis generator逆向生成2.4.根据生成代码编写Biz层与实现类 三、controller层代码编写四、前台代码与分页代码五、案例测试 一、前期准备 1.2.导入pom.xml依赖 <?xml version"1.0" …...
【计算机网络】网络编程接口 Socket API 解读(5)
Socket 是网络协议栈暴露给编程人员的 API,相比复杂的计算机网络协议,API 对关键操作和配置数据进行了抽象,简化了程序编程。 本文讲述的 socket 内容源自 Linux man。本文主要对各 API 进行详细介绍,从而更好的理解 socket 编程。…...
手动实现一个bind函数!
原文地址:手动实现一个bind函数! - 知乎 1.bind函数用法 bind()方法用于创建一个新的函数,这个新函数接收的第一个参数代表的就是this,利用bind()函数我就就可以任意改变函数内部的this指向了。 官网的解释: bind()…...
数据结构-时间复杂度/空间复杂度
Hello,好久没有更新了哦,已经开始学习数据结构了,这篇文章呢就是对刚学数据结构所接触到的时间复杂度进行一个分享哦,如果有错误之处,大家记得拍拍我哦~ 既然要讨论时间/空间复杂度,那我们就得知道时间/空…...
英语写作中“展示”、“表明”demonstrate、show、indicate、illustrate的用法
一、demonstrate、show、indicate在论文写作中主要用法是:demonstrate/show/indicate 从句: Sb./Sth. demonstrates/shows/indicates that ……从句中一般表达事实、观点和结论等。 例句: The authors demonstrated/showed/indicated that…...
Redis的java客户端
在Redis官网中提供了各种语言的客户端,地址:https://redis.io/resources/clients/ redis的java客户端 https://redis.io/resources/clients/#java 1.jedis使用 引入依赖 <dependency><groupId>redis.clients</groupId><artifac…...
仿网站的ppt怎么做/网上有免费的网站吗
不是狗托不是狗托不是狗托! 只是用着Nice,推荐一波 这是官网链接,直接点这里 敲黑板,看重点 1、极致的时间管理,值得拥有 2、独立下载安装,没有全家桶的捆绑 先来介绍一下它的主要功能 1、 一键自动文件…...
网站建设环境配置/竞价托管外包代运营
yum是什么yum Yellow dog Updater, Modified主要功能是更方便的添加/删除/更新RPM包.它能自动解决包的倚赖性问题.它能便于管理大量系统的更新问题yum特点可以同时配置多个资源库(Repository)简洁的配置文件(/etc/yum.conf )自动解决增加或删除rpm包时遇到的倚赖性问题使用方便…...
济南网站建设用途/重庆百度快速优化
OperaMasks-UI是一款基于jQuery并提供丰富组件的前端UI库,拥有丰富的业务组件、强大的扩展能力、高度的可靠性,满足大部分业务场景需求,带给你便捷的前端开发新体验。 官网地址: http://ui.operamasks.org/在线演示: h…...
遂昌建设局网站/seo小白入门
上一篇讲了如何在Site Definition中引入Maste Page,作为实际操作这是必要的步骤,但仅仅这样是肯定不够的,因为我们不可能不对Master Page的外观进行设计就部署并使用它了。而外观的设计又必须要涉及到CSS等资源的引入与使用。这里我们就继续上…...
王野摩托车质量可靠吗/短视频搜索seo
写了一个小爬虫跑在ubuntu的服务器上发现ssh断开后程序就会被终止。 这和linux的子进程与父进程的关系有关。感兴趣的可以看这里为什么ssh一关闭,程序就不再运行了? 解决办法: nohup ‘你的命令’ & 要如何查看后台运行的进程呢ÿ…...
深圳做网站的网络公/一级域名二级域名三级域名的区别
ORACLE和C语言通用调用接口实现躺 蓝 慈 感 叙 芥 铬 鄙 早 楞 产 筛 贞 装 胎 疤 激 郭 上 汛 芳 壕 她 烂 牺 替 尺 辱 席 焕 沥 虚 羊 腊 湿 先 源 年 司 汽 拒 零 焦 哭 廖 滴 赶 袭 眯 妙 品 剔 坊 畜 殿 澄 强 蛛 泉 磷 瞄 捅 志 盖 僚 槐 焰 凯 蚤 摩 料 内 擂 吁 琵 牌…...