WebServer 解析HTTP 响应报文

这篇具有很好参考价值的文章主要介绍了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);

WebServer 解析HTTP 响应报文,webserver,WebServer,解析HTTP,响应报文

对可变参数列表的处理过程:

  • 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 3

const 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/stdout
    FILE *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请求和响应步骤

WebServer 解析HTTP 响应报文,webserver,WebServer,解析HTTP,响应报文
2.1 HTTP 响应报文格式

WebServer 解析HTTP 响应报文,webserver,WebServer,解析HTTP,响应报文

响应行\r\n
响应头\r\n
空行\r\n
响应体\r\n

提示: 每项信息之间都要有一个\r\n进行分割
2.2 流程图

浏览器端发出HTTP请求报文,服务器端接收该报文并调用 process_read 对其进行解析,根据解析结果 HTTP_CODE,进入相应的逻辑和模块

  • 服务器子线程完成报文的解析与响应;
  • 主线程监测读写事件,调用 read_oncehttp_conn::write 完成数据的读取与发送

WebServer 解析HTTP 响应报文,webserver,WebServer,解析HTTP,响应报文

2.3 HTTP_CODE 含义 
WebServer 解析HTTP 响应报文,webserver,WebServer,解析HTTP,响应报文 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请求的结果,决定返回给客户端的内容

WebServer 解析HTTP 响应报文,webserver,WebServer,解析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)
    {
		// 内部错误,500
        case INTERNAL_ERROR:
			// 状态行
            add_status_line( 500, error_500_title );
			// 消息头
            add_headers( strlen( error_500_form ) );
            if ( ! add_content( error_500_form ) ) {
                return false;
            }
            break;
		
		// 报文语法有误,400
        case BAD_REQUEST:
            add_status_line( 400, error_400_title );
            add_headers( strlen( error_400_form ) );
            if ( ! add_content( error_400_form ) ) {
                return false;
            }
            break;
		
		// 请求资源不存在,404
        case NO_RESOURCE:
            add_status_line( 404, error_404_title );
            add_headers( strlen( error_404_form ) );
            if ( ! add_content( error_404_form ) ) {
                return false;
            }
            break;
		
		// 资源没有访问权限,403
        case FORBIDDEN_REQUEST:
            add_status_line( 403, error_403_title );
            add_headers(strlen( error_403_form));
            if ( ! add_content( error_403_form ) ) {
                return false;
            }
            break;
			
		// 请求资源可以正常访问,200
        case 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_idx
				m_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;
}

WebServer 解析HTTP 响应报文,webserver,WebServer,解析HTTP,响应报文
WebServer 解析HTTP 响应报文,webserver,WebServer,解析HTTP,响应报文

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 总结:生成响应报文的具体流程(来自这篇文章)
  1. 进行请求报文的时候,进行URL分析。使用一个文件或其他对象映射到内存,不能映射则报错!
  2. 根据状态码,若为错误状态码,则提取对应的报错介绍和报错信息,作为响应头和响应正文;若为正确的状态码,则提取成功信息作为响应头,提取的内存映射作为响应正文。根据当前的响应头和响应正文的情况,对写缓冲区进行相应的写入(状态行、响应头)
  3. 将写事件写入epoll事件表,之后主线程会自动将写缓冲区中的前半部分报文和响应正文通过分散写传回给客户端
2.8 演示效果:

WebServer 解析HTTP 响应报文,webserver,WebServer,解析HTTP,响应报文

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.6


got 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.6


got 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.6


got 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博客文章来源地址https://www.toymoban.com/news/detail-703898.html

到了这里,关于WebServer 解析HTTP 响应报文的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包 赞助服务器费用

相关文章

  • 31、springboot 配置HTTP服务端口及如何通过WebServer实例动态获取项目中的HTTP端口

    代码示例 就是在yml配置文件中配置端口号 代码演示 需求:在项目中获取服务器的动态端口,通过获取WebServer实例来获取动态端口号。 **获取WebServer实例的方法1:**通过WebServerApplicationContext来获取WebServer 获取WebServer实例的方法2: 实现一个监听器接口:ApplicationListener 来获取

    2024年02月12日
    浏览(39)
  • WebServer -- 面试题(下)

    👂 夏风 - Gifty - 单曲 - 网易云音乐 目录 🌼前言 🎂面试题(下) 4)HTTP报文解析 为什么要用状态机 状态转移图画一下 https 协议为什么安全 https 的 ssl 连接过程 GET 和 POST 的区别 5)数据库注册登录 登录说一下 用户名/密码,保存状态了吗?如果要保存,如何做(cookie session)

    2024年04月16日
    浏览(31)
  • WebServer项目的亮点和难点

    面试被问到了这个问题,答得稀烂…但是我觉得这个问题真的问的很好,还是要好好想一想总结一下。 亮点: 并发模型为Reactor 使用Epoll水平触发+EPOLLONESHOT,非阻塞IO 为充分利用多核CPU的性能,以多线程的形式实现服务器,并实现线程池避免线程频繁创建销毁造成的系统开销

    2024年02月09日
    浏览(27)
  • C++初级项目-webserver(1)

    Web服务器是一个基于Linux的简单的服务器程序,其主要功能是接收HTTP请求并发送HTTP响应,从而使客户端能够访问网站上的内容。本项目旨在使用C++语言,基于epoll模型实现一个简单的Web服务器。选择epoll模型是为了高效地处理大量并发连接。 这个项目的目标是实现一个简单的

    2024年02月05日
    浏览(41)
  • 项目:智慧教室(cubemx+webserver)

    第一章:需求与配置 一。项目需求         智能终端(应该是上位机)通过互联网发指令给中控主机,中控主机(stm32)接受到指令后再以无线形式对节点发出命令。 1.利用Zigbee实现教室内,无线传感网络搭建,对接环境传感器,教室内风扇,空调,灯,报警等装置 2.利用

    2024年02月10日
    浏览(26)
  • Http---HTTP响应报文

    1. HTTP响应报文分析 HTTP 响应报文效果图: 响应报文说明: 原始响应报文说明: 说明: 每项数据之间使用: rn 2. HTTP 状态码介绍 HTTP 状态码是 用于表示web服务器响应状态的3位数字代码 。 状态码 说明 200 请求成功 307 重定向 400 错误的请求,请求地址或者参数有误 404 请求资源在服

    2024年03月10日
    浏览(49)
  • .netcore windows app启动webserver

    创建controller: 定义webserver 启动选项: winform启动|停止: 页面预览: 测试: 配置打包单个文件: csproj配置    

    2024年02月11日
    浏览(34)
  • C++初级项目webserver项目流程介绍(2)

    C++的webserver项目是自己在学完网络编程后根据网课的内容做的一个初级的网络编程项目。 这个项目的效果是可以在浏览器通过输入网络IP地址和端口,然后打开对应的文件目录 效果如下: 也可以打开文件夹后点击目录,打开到对应的文件夹中去。 这个就是简单的webserver功能

    2024年02月05日
    浏览(42)
  • ESP32构建简单WebServer服务器

    WebServer 服务器 此案例中,我们将创建一个最简单的 webserver 服务器 案例实现:ESP32 通过 SPIFFS 上传网页文件并保存,之后手机开启热点,ESP32 连接到该热点;与手机处于同一热点下的任何一个设备(下面用电脑演示)都可以通过 ESP32 反馈的内网地址访问其构建的网页! ESP

    2024年02月11日
    浏览(44)
  • 《二》HTTP 请求报文和响应报文、请求方法、状态码

    请求报文: 客户端向服务器发送的请求信息,就叫做请求报文。 客户端发送一个 HTTP 请求到服务器,请求信息包含四部分:请求行、请求头、空行、请求体。 请求行:包含三部分,分别是请求方法、请求资源的路径、协议版本。 请求头:头信息,告诉服务器一些信息,在客

    2023年04月26日
    浏览(46)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包