HTTP--HyperText Transfer Protocol,超文本传输协议,一种用于分布式、协作式和超媒体信息系统的应用层协议,用于传输HTML。
HTML--HyperText Mark-up Language,超文本传输语言
http协议
请求分类
请求方法 意义
OPTIONS 请求一些选项信息,允许客户端查看服务器的性能
GET 请求指定的页面信息,并返回实体主体
HEAD 类似于 get 请求,只不过返回的响应中没有具体的内容,用于获取报头
POST 向指定资源提交数据进行处理请求(例如提交表单或者上传文件)。数据被包含在请求体中。POST请求可能会导致新的资源的建立和/或已有资源的修改
PUT 从客户端向服务器传送的数据取代指定的文档的内容
DELETE 请求服务器删除指定的页面
TRACE 回显服务器收到的请求,主要用于测试或诊断
请求消息
浏览器-->服务器,主要包含:
请求行:说明请求类型,要访问的资源,以及使用的http版本
请求头:说明服务器要使用的附加信息
空行:必须
请求数据:主体,GET请求无,POST有。是浏览器携带给服务器的数据。
GET请求
向服务器请求数据
请求协议: --- 浏览器组织,发送
GET /hello.c Http1.1\r\n
2. Host: localhost:2222\r\n
3. User-Agent: Mozilla/5.0 (X11; Ubuntu; Linux i686; rv:24.0) Gecko/201001 01 Firefox/24.0\r\n
4. Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8\r\n
5. Accept-Language: zh-cn,zh;q=0.8,en-us;q=0.5,en;q=0.3\r\n
6. Accept-Encoding: gzip, deflate\r\n
7. Connection: keep-alive\r\n
8. If-Modified-Since: Fri, 18 Jul 2014 08:36:36 GMT\r\n
【空行】\r\n
#其中GET /hello.c Http1.1\r\n和最后一行的【空行】\r\n最重要
应答协议:
Http1.1 200 OK
2. Server: xhttpd
Content-Type:text/plain; charset=iso-8859-1
3. Date: Fri, 18 Jul 2014 14:34:26 GMT
5. Content-Length: 32 ( 要么不写 或者 传-1, 要写务必精确 ! )
6. Content-Language: zh-CN
7. Last-Modified: Fri, 18 Jul 2014 08:36:36 GMT
8. Connection: close
\r\n
[数据起始。。。。。
。。。。
。。。数据终止]
#其中Http1.1 200 OK和Content-Type:text/plain; charset=iso-8859-1 必须要有
#\r\n必须要有,表示协议头结束,后面就是内容
POST请求
携带数据给服务器
POST / color1.cgi HTTP / 1.1
Host: 192.168.0.23 : 47310
Connection : keep - alive
Content - Length : 10
Cache - Control : max - age = 0
Origin : http ://192.168.0.23:40786
Upgrade - Insecure - Requests : 1
User - Agent : Mozilla / 5.0 (Windows NT 6.1; WOW64) AppleWebKit / 537.36 (KHTML, like Gecko) Chrome / 55.0.2883.87
Safari / 537.36
Content - Type : application / x - www - form - urlencoded
Accept : text / html, application / xhtml + xml, application / xml; q = 0.9, image / webp, */*;q=0.8
Referer: http://192.168.0.23:47310/
Accept-Encoding: gzip, deflate
Accept-Language: zh-CN,zh;q=0.8
Cookie: __guid=179317988.1576506943281708800.1510107225903.8862; monitor_count=281
Form Data
color=gray
第一部分:请求头行,包含请求类型、URI、HTTP协议版本;
请求信息类型通常有:get、post、put等等;
第二部分:即紧跟第一行之后的,请求头部,包含服务器所使用的说明信息;
接下来解释一下这些说明信息的意思:
1、host:请求web服务器的域名地址
2、Connection: 表示是否持久连接;即keep-alive表示持久连接;
3、Cache-Control:指定请求和响应的缓存机制;no-cache(不能缓存)、no-store(在请求消息中发送将使得请求和响应消息都不使用缓存)、 max-age(客户机可以接收生存期不大于指定时间(以秒为单位)的响应)、max-stale(客户机可以接收超出超时期间的响应消息)、min-fresh(客户机可以接收响应时间小于当前时间加上指定时间的响应)、 only-if-cached等等
4、User-Agent: HTTP协议运行的浏览器类型的详细信息;比如:谷歌/67.0.3396.995、Accept: 指浏览器可以接收的内容类型;
6、Accept-Encoding: 客户端浏览器可以支持的web服务器返回内容压缩编码类型;
7、Accept-Language:浏览器支持的语言类型,
8、Cookie: 某些网站为了辨别用户身份、进行 session 跟踪而储存在用户本地终端上的数据(通常经过加密);例如当我们上网时,某些网站能准确的推送我们想要的信息。
第三部分:"\r\n" --> 分割header和body部分的分界线
功能分析
主要就是利用C++进行HTTP报文的字符串解析。
http_conn.h代码实现
class http_conn
{
public:
static const int FILENAME_LEN = 200;
//设置读缓冲区m_read_buf大小
static const int READ_BUFFER_SIZE = 2048;
//设置写缓冲区m_write_buf大小
static const int WRITE_BUFFER_SIZE = 1024;
//报文的请求方法,只用到GET和POST
enum METHOD
{
GET = 0,
POST,
HEAD,
PUT,
DELETE,
TRACE,
OPTIONS,
CONNECT,
PATH
};
//主状态机的状态
enum CHECK_STATE
{
CHECK_STATE_REQUESTLINE = 0,
CHECK_STATE_HEADER,
CHECK_STATE_CONTENT
};
//报文解析的结果,表示HTTP请求的处理结果,一共初始化了八种情形
enum HTTP_CODE
{
NO_REQUEST,//请求不完整,需要继续读取请求报文数据
GET_REQUEST,//获得了完整的HTTP请求
BAD_REQUEST,//HTTP请求报文有语法错误或请求资源为目录
NO_RESOURCE,//请求资源不存在
FORBIDDEN_REQUEST,//请求资源禁止访问,没有读取权限
FILE_REQUEST,//请求资源可以正常访问
INTERNAL_ERROR,//服务器内部错误
CLOSED_CONNECTION
};
从状态机的状态
enum LINE_STATUS
{
LINE_OK = 0,
LINE_BAD,
LINE_OPEN
};
public:
//构造和析构
http_conn() {}
~http_conn() {}
public:
void init(int sockfd, const sockaddr_in &addr, char *, int, int, string user, string passwd, string sqlname);
void close_conn(bool real_close = true);
//报文解析函数,返回的是解析出来的情况,根据不同的值完成不同的操作
void process();
bool read_once();
bool write();
sockaddr_in *get_address()
{
return &m_address;
}
void initmysql_result(connection_pool *connPool);
int timer_flag;
int improv;
//我们需要在对象内部定义出:读缓冲区,写缓冲区
private:
void init();
//处理http请求协议以及http应答协议,使用process_read和process_write
HTTP_CODE process_read();
bool process_write(HTTP_CODE ret);
HTTP_CODE parse_request_line(char *text);//读请求行
HTTP_CODE parse_headers(char *text);//读请求头
HTTP_CODE parse_content(char *text);//读请求数据
HTTP_CODE do_request();
char *get_line() { return m_read_buf + m_start_line; };//读一行
//从状态机,返回值为行的读取状态,有LINE_OK,LINE_BAD,LINE_OPEN
LINE_STATUS parse_line();
void unmap();
bool add_response(const char *format, ...);
bool add_content(const char *content);
bool add_status_line(int status, const char *title);
bool add_headers(int content_length);
bool add_content_type();
bool add_content_length(int content_length);
bool add_linger();
bool add_blank_line();
public:
static int m_epollfd;
static int m_user_count;
MYSQL *mysql;
int m_state; //读为0, 写为1
private:
int m_sockfd;
sockaddr_in m_address;
//存储读取的请求报文数据
char m_read_buf[READ_BUFFER_SIZE];
//缓冲区中m_read_buf中数据的最后一个字节的下一个位置
int m_read_idx;
//缓冲区中m_read_buf读取的位置m_checked_idx
int m_checked_idx;
//m_read_buf中已经解析的字符个数
int m_start_line;
//存储发出的响应报文数据
char m_write_buf[WRITE_BUFFER_SIZE];
//指示buffer中的长度
int m_write_idx;
//主状态机的状态
CHECK_STATE m_check_state;
//请求方法
METHOD m_method;
//以下为解析请求报文中对应的6个变量
//存储读取文件的名称
char m_real_file[FILENAME_LEN];
char *m_url;
char *m_version;
char *m_host;
int m_content_length;
bool m_linger;
char *m_file_address;
struct stat m_file_stat;
struct iovec m_iv[2];
int m_iv_count;
int cgi; //是否启用的POST
char *m_string; //存储请求头数据
int bytes_to_send;
int bytes_have_send;
char *doc_root;
map<string, string> m_users;
int m_TRIGMode;
int m_close_log;
char sql_user[100];
char sql_passwd[100];
char sql_name[100];
};
#endif
http_conn.cpp
http_conn.h的具体实现
viod process()
处理http请求协议:process_read函数
处理http应答协议:process_write函数
Linux网络编程:状态机_linux 状态机_才文嘉的博客-CSDN博客
处理http请求协议使用主从状态机,即process_read,从状态机使用parse_line。主状态机由从状态机驱动。
process_read:解析http请求报文是请求行、请求头还是请求数据;
parse_line:负责读取http报文的一行,解析这一行是否结束;
void http_conn::process()
{
//读取浏览器发出的http请求
HTTP_CODE read_ret = process_read();
if (read_ret == NO_REQUEST)
{
//NO_REQUEST,表示请求不完整,需要继续接收请求数据
modfd(m_epollfd, m_sockfd, EPOLLIN, m_TRIGMode);
return;
}
//写应答协议,往浏览器传输
bool write_ret = process_write(read_ret);
if (!write_ret)//读完,关闭cfd
{
close_conn();
}
modfd(m_epollfd, m_sockfd, EPOLLOUT, m_TRIGMode);
}
LINE_STATUS parse_line()
从状态机函数
从状态机负责读取http报文的一行,解析这一行是否结束,用以支持主状态机判断是何种报文。
从状态机关心当前一行读完整了(LINE_OK)、不完整(LINE_OPEN)、格式有误(LINE_BAD)。一个主状态机状态包含多个从状态机状态
具体如下:
因为在HTTP报文中可以通过查找\r\n将报文拆解成单独的行进行解析,从状态机负责读取buffer中的数据,将每行数据末尾的\r\n置为\0\0,并更新从状态机在buffer中读取的位置m_checked_idx,以此来驱动主状态机解析。
流程:
1、从状态机从m_read_buf中逐字节读取,判断当前字节是否为\r
接下来的字符是\n,将\r\n修改成\0\0,将m_checked_idx指向下一行的开头,则返回LINE_OK
接下来达到buffer末尾,表示buffer还需要继续接收,返回LINE_OPEN
否则,表示语法错误,返回LINE_BAD
2、当前字节不是\r,判断是否是\n(一般是上次读取到\r就到了buffer末尾,没有接收完整,再次接收时会出现这种情况)
如果前一个字符是\r,则将\r\n修改为\0\0,将m_checked_idx指向下一行的开头,则返回LINE_OK
3、当前字节既不是\r也不是\n
表示接收不完整,需要继续接收,返回LINE_OPEN
代码
//返回值为行的读取状态,有LINE_OK,LINE_BAD,LINE_OPEN
http_conn::LINE_STATUS http_conn::parse_line()
{
char temp;
//m_read_idx指向缓冲区m_read_buf的数据末尾的下一个字节
//m_checked_idx指向从状态机当前正在分析的字节
for (; m_checked_idx < m_read_idx; ++m_checked_idx)
{
//temp为将要分析的字节
temp = m_read_buf[m_checked_idx];
//如果当前是\r字符,则有可能会读取到完整行
if (temp == '\r')
{
//下一个字符达到了buffer结尾,则接收不完整,需要继续接收
if ((m_checked_idx + 1) == m_read_idx)
return LINE_OPEN;
//下一个字符是\n,将\r\n改为\0\0
else if (m_read_buf[m_checked_idx + 1] == '\n')
{
m_read_buf[m_checked_idx++] = '\0';
m_read_buf[m_checked_idx++] = '\0';
return LINE_OK;
}
//如果都不符合,则返回语法错误
return LINE_BAD;
}
//如果当前字符是\n,也有可能读取到完整行
//一般是上次读取到\r就到buffer末尾了,没有接收完整,再次接收时会出现这种情况
else if (temp == '\n')
{
//前一个字符是\r,则接收完整
if (m_checked_idx > 1 && m_read_buf[m_checked_idx - 1] == '\r')
{
m_read_buf[m_checked_idx - 1] = '\0';
m_read_buf[m_checked_idx++] = '\0';
return LINE_OK;
}
return LINE_BAD;
}
}
//并没有找到\r\n,需要继续接收
return LINE_OPEN;
}
HTTP_CODE process_read()
主状态机函数,负责对该行数据解析
通过调用从状态机来驱动主状态机,在主状态机进行解析前,从状态机已经将每一行的末尾\r\n符号改为\0\0,以便于主状态机直接取出对应字符串进行处理。
主状态机三种状态:
CHECK_STATE_REQUESTLINE,解析请求行,主状态机的初始状态,调用parse_request_line函数从m_read_buf中解析HTTP请求行,获得请求方法、目标URL及HTTP版本号
CHECK_STATE_HEADER,解析请求头,调用parse_headers函数解析请求头部信息
CHECK_STATE_CONTENT,解析请求数据,仅用于解析POST请求,调用parse_content函数解析,为后面的登录和注册做准备
初始化状态是CHECK_STATE_REQUESTLINE,在使用parse_request_line解析完后进行状态转移CHECK_STATE_HEADER,
状态为CHECK_STATE_HEADER时,在使用parse_headers解析完会进行状态转移CHECK_STATE_CONTENT
CHECK_STATE_CONTENT为最终状态
//通过while循环 封装主状态机 对每一行进行循环处理
//此时 从状态机已经修改完毕 主状态机可以取出完整的行进行解析
http_conn::HTTP_CODE http_conn::process_read()
{
//初始化从状态机的状态
LINE_STATUS line_status = LINE_OK;
HTTP_CODE ret = NO_REQUEST;
char *text = 0;
//判断条件,这里就是从状态机驱动主状态机
while ((m_check_state == CHECK_STATE_CONTENT && line_status == LINE_OK) || ((line_status = parse_line()) == LINE_OK))
{
text = get_line();
//m_start_line 是每一个数据行在m_read_buf中的起始位置
//m_checked_idx 表示从状态机在m_read_buf中的读取位置
m_start_line = m_checked_idx;
LOG_INFO("%s", text);
//三种状态转换逻辑
switch (m_check_state)
{
case CHECK_STATE_REQUESTLINE:
{
//解析请求行
ret = parse_request_line(text);
if (ret == BAD_REQUEST)
return BAD_REQUEST;
break;
}
case CHECK_STATE_HEADER:
{
//解析请求头
ret = parse_headers(text);
if (ret == BAD_REQUEST)
return BAD_REQUEST;
//作为get请求 则需要跳转到报文响应函数
else if (ret == GET_REQUEST)
{
return do_request();
}
break;
}
case CHECK_STATE_CONTENT:
{
//解析消息体
ret = parse_content(text);
//对于post请求 跳转到报文响应函数
if (ret == GET_REQUEST)
return do_request();
//更新 跳出循环 代表解析完了消息体
line_status = LINE_OPEN;
break;
}
//默认状态
default:
return INTERNAL_ERROR;
}
}
return NO_REQUEST;
}
HTTP_CODE parse_request_line(char *text)
process_read函数中当状态是CHECK_STATE_REQUESTLINE调用parse_request_line,用以解析http请求行,获得请求方法,目标url及http版本号
char *strpbrk(const char *str1, const char *str2) 依次检验字符串 str1 中的字符,当被检验字符在字符串 str2 中也包含时,则停止检验,并返回该字符位置。
返回值:str1 中第一个匹配字符串 str2 中字符的字符数,如果未找到字符则返回 NULL。
int strcasecmp (const char *s1, const char *s2)
用来比较参数s1和s2字符串前n个字符,比较时会自动忽略大小写的差异。
若参数s1和s2字符串相等返回0,s1大于s2则返回大于0的值,s1小于s2则返回小于0的值
size_t strspn(const char *str1, const char *str2) 检索字符串 str1 中第一个不在字符串 str2 中出现的字符下标
返回值:str1 中第一个不在字符串 str2 中出现的字符下标
size_t strlen(const char* str)求出字符串长度
char *strcat(char *dest, const char *src) 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾
//解析http请求行,获得请求方法,目标url及http版本号
http_conn::HTTP_CODE http_conn::parse_request_line(char *text)
{
//请求行中最先含有空格和\t任一字符的位置并返回
m_url = strpbrk(text, " \t");
//没有目标字符 则代表报文格式有问题
if (!m_url)
{
return BAD_REQUEST;
}
//用于将前面的数据取出
*m_url++ = '\0';
//取出数据 确定请求方式
char *method = text;
if (strcasecmp(method, "GET") == 0)
m_method = GET;
else if (strcasecmp(method, "POST") == 0)
{
m_method = POST;
cgi = 1;
}
else
return BAD_REQUEST;
//m_url此时跳过了第一个空格或者\t字符,但是后面还可能存在
//不断后移找到请求资源的第一个字符
m_url += strspn(m_url, " \t");
//判断http的版本号
m_version = strpbrk(m_url, " \t");
if (!m_version)
return BAD_REQUEST;
*m_version++ = '\0';
m_version += strspn(m_version, " \t");
//目前社长项目仅支持http1.1
if (strcasecmp(m_version, "HTTP/1.1") != 0)
return BAD_REQUEST;
//对请求资源的前七个字符进行判断
//对某些带有http://的报文进行单独处理
if (strncasecmp(m_url, "http://", 7) == 0)
{
m_url += 7;
m_url = strchr(m_url, '/');
}
//https的情况
if (strncasecmp(m_url, "https://", 8) == 0)
{
m_url += 8;
m_url = strchr(m_url, '/');
}
//不符合规则的报文
if (!m_url || m_url[0] != '/')
return BAD_REQUEST;
//当url为/时,显示欢迎界面
if (strlen(m_url) == 1)
//把"judge.html"所指向的字符串追加到m_url的末尾
strcat(m_url, "judge.html");
//主状态机状态转移
m_check_state = CHECK_STATE_HEADER;
return NO_REQUEST;
}
HTTP_CODE parse_headers(char *text)
主状态机状态转移为CHECK_STATE_HEADER调用parse_request_line,用以解析请求头部信息
//解析http请求的一个头部信息
http_conn::HTTP_CODE http_conn::parse_headers(char *text)
{
//判断是空行还是请求头
if (text[0] == '\0')
{
//具体判断是get还是post请求
if (m_content_length != 0)
{
//post请求需要改变主状态机的状态
m_check_state = CHECK_STATE_CONTENT;
return NO_REQUEST;
}
return GET_REQUEST;
}
//解析头部连接字段
else if (strncasecmp(text, "Connection:", 11) == 0)
{
text += 11;
//跳过空格和\t字符
text += strspn(text, " \t");
if (strcasecmp(text, "keep-alive") == 0)
{
//判断是否为长连接
m_linger = true;
}
}
//解析请求头 内容长度字段
else if (strncasecmp(text, "Content-length:", 15) == 0)
{
text += 15;
text += strspn(text, " \t");
m_content_length = atol(text);
}
//解析请求头部host字段
else if (strncasecmp(text, "Host:", 5) == 0)
{
text += 5;
text += strspn(text, " \t");
m_host = text;
}
else
{
LOG_INFO("oop!unknow header: %s", text);
}
return NO_REQUEST;
}
HTTP_CODE parse_content(char *text)
- 用于解析POST请求,调用parse_content函数解析消息体
- 用于保存POST请求消息体,为后面的登陆和注册做准备
//判断http请求是否被完整读入
http_conn::HTTP_CODE http_conn::parse_content(char *text)
{
//判断是否读取了消息体
if (m_read_idx >= (m_content_length + m_checked_idx))
{
text[m_content_length] = '\0';
//POST请求中最后为输入的用户名和密码
m_string = text;
return GET_REQUEST;
}
return NO_REQUEST;
}
do_request() 具体处理函数
process_read返回值是对请求文件的分析结果,一部分是语法错误的BAD_REQUEST,一部分则是我们认可的规则然后作出的对应的响应。
do_request()的具体做法是将网站根目录和url文件拼接,然后通过stat判断文件属性。另外为了提高访问速度,通过mmap进行映射,将普通文件迎神到内存逻辑地址。
char *strcpy(char *dest, const char *src) 把 src 所指向的字符串复制到 dest
返回值:该函数返回一个指向最终的目标字符串 dest 的指针
http_conn::HTTP_CODE http_conn::do_request()
{
//将初始化的m_real_file赋值为网站根目录
strcpy(m_real_file, doc_root);
int len = strlen(doc_root);
//printf("m_url:%s\n", m_url);
//找到m_url中/的位置
const char *p = strrchr(m_url, '/');
//处理cgi
//实现登录和注册校验
if (cgi == 1 && (*(p + 1) == '2' || *(p + 1) == '3'))
{
//根据标志判断是登录检测还是注册检测
char flag = m_url[1];
char *m_url_real = (char *)malloc(sizeof(char) * 200);
strcpy(m_url_real, "/");
strcat(m_url_real, m_url + 2);
strncpy(m_real_file + len, m_url_real, FILENAME_LEN - len - 1);
free(m_url_real);
//将用户名和密码提取出来
//user=123&passwd=123
char name[100], password[100];
int i;
for (i = 5; m_string[i] != '&'; ++i)
name[i - 5] = m_string[i];
name[i - 5] = '\0';
int j = 0;
for (i = i + 10; m_string[i] != '\0'; ++i, ++j)
password[j] = m_string[i];
password[j] = '\0';
if (*(p + 1) == '3')
{
//如果是注册,先检测数据库中是否有重名的
//没有重名的,进行增加数据
char *sql_insert = (char *)malloc(sizeof(char) * 200);
strcpy(sql_insert, "INSERT INTO user(username, passwd) VALUES(");
strcat(sql_insert, "'");
strcat(sql_insert, name);
strcat(sql_insert, "', '");
strcat(sql_insert, password);
strcat(sql_insert, "')");
if (users.find(name) == users.end())
{
m_lock.lock();
int res = mysql_query(mysql, sql_insert);
users.insert(pair<string, string>(name, password));
m_lock.unlock();
if (!res)
strcpy(m_url, "/log.html");
else
strcpy(m_url, "/registerError.html");
}
else
strcpy(m_url, "/registerError.html");
}
//如果是登录,直接判断
//若浏览器端输入的用户名和密码在表中可以查找到,返回1,否则返回0
else if (*(p + 1) == '2')
{
if (users.find(name) != users.end() && users[name] == password)
strcpy(m_url, "/welcome.html");
else
strcpy(m_url, "/logError.html");
}
}
//如果请求资源为/0,表示跳转注册界面
if (*(p + 1) == '0')
{
char *m_url_real = (char *)malloc(sizeof(char) * 200);
strcpy(m_url_real, "/register.html");
//将网站目录和/register.html进行拼接,更新到m_real_file中
strncpy(m_real_file + len, m_url_real, strlen(m_url_real));
free(m_url_real);
}
//如果请求资源为/1,表示跳转登录界面
else if (*(p + 1) == '1')
{
char *m_url_real = (char *)malloc(sizeof(char) * 200);
strcpy(m_url_real, "/log.html");
strncpy(m_real_file + len, m_url_real, strlen(m_url_real));
free(m_url_real);
}
else if (*(p + 1) == '5')
{
char *m_url_real = (char *)malloc(sizeof(char) * 200);
strcpy(m_url_real, "/picture.html");
//将网站目录和/log.html进行拼接,更新到m_real_file中
strncpy(m_real_file + len, m_url_real, strlen(m_url_real));
free(m_url_real);
}
else if (*(p + 1) == '6')
{
char *m_url_real = (char *)malloc(sizeof(char) * 200);
strcpy(m_url_real, "/video.html");
strncpy(m_real_file + len, m_url_real, strlen(m_url_real));
free(m_url_real);
}
else if (*(p + 1) == '7')
{
char *m_url_real = (char *)malloc(sizeof(char) * 200);
strcpy(m_url_real, "/fans.html");
strncpy(m_real_file + len, m_url_real, strlen(m_url_real));
free(m_url_real);
}
else
strncpy(m_real_file + len, m_url, FILENAME_LEN - len - 1);
if (stat(m_real_file, &m_file_stat) < 0)
return NO_RESOURCE;
if (!(m_file_stat.st_mode & S_IROTH))
return FORBIDDEN_REQUEST;
if (S_ISDIR(m_file_stat.st_mode))
return BAD_REQUEST;
int fd = open(m_real_file, O_RDONLY);
m_file_address = (char *)mmap(0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0);
close(fd);
return FILE_REQUEST;
}
代码结构
Linux网络编程:状态机_linux 状态机_才文嘉的博客-CSDN博客