目录
1、locker.h
#ifndef LOCKER_H
#define LOCKER_H
/********************* 封装线程同步的类和头文件 ********************/
#include <pthread.h>
#include <exception>
#include <semaphore.h>
using namespace std;
// 互斥锁类
class locker{
public:
locker(){
if( pthread_mutex_init(&m_mutex, NULL) != 0 )
throw exception(); // 抛出异常
}
~locker(){
pthread_mutex_destroy(&m_mutex);
}
bool lock(){
return pthread_mutex_lock(&m_mutex) == 0;
}
bool trylock(){
return pthread_mutex_trylock(&m_mutex) == 0;
}
bool unlock(){
return pthread_mutex_unlock(&m_mutex) == 0;
}
private:
pthread_mutex_t m_mutex;
};
// 条件变量类
class cond{
public:
cond(){
if( pthread_cond_init(&m_cond, NULL) != 0 )
throw exception();
}
~cond(){
pthread_cond_destroy(&m_cond);
}
bool wait(pthread_mutex_t *m_mutex){
return pthread_cond_wait(&m_cond, m_mutex) == 0;
}
bool timedwait(pthread_mutex_t* m_mutex, struct timespec t){
return pthread_cond_timedwait(&m_cond, m_mutex, &t) == 0;
}
bool signal(){
return pthread_cond_signal(&m_cond) == 0;
}
bool broadcast(){
return pthread_cond_broadcast(&m_cond) == 0;
}
private:
pthread_cond_t m_cond;
};
// 信号量类
class sem{
public:
sem(){
if( sem_init(&m_sem, 0, 0) != 0)
throw exception();
}
~sem(){
sem_destroy(&m_sem);
}
bool wait(){
return sem_wait(&m_sem) == 0;
}
bool trywait(){
return sem_trywait(&m_sem) == 0;
}
bool post(){
return sem_post(&m_sem) == 0;
}
private:
sem_t m_sem;
};
#endif
2、pthreadpool.h
#ifndef PTHREADPOOL_H
#define PTHREADPOOL_H
/******************** 封装线程池类头文件 *******************/
#include "locker.h"
#include <list>
#include <iostream>
using namespace std;
template <typename T>
class pthreadpool{
public:
pthreadpool(int workqueue_number = 10000, int pthread_number = 8);
~pthreadpool();
// 向工作队列中添加任务
bool append(T* request);
static void* worker(void* arg);
private:
// 线程池运行函数
void run();
private:
// 工作队列最大容量
int m_workqueue_number;
// 工作队列
list<T*> m_workqueue;
// 工作线程最大数量
int m_pthread_number;
// 线程池数组
pthread_t * m_pthreads;
// 信号量用来判断是否有任务需要处理
sem workqueuetat;
// 工作队列互斥锁
locker workqueuelocker;
// 是否停止线程
bool m_stop;
};
template <typename T>
pthreadpool<T>::pthreadpool(int workqueue_number, int pthread_number) : // 初始化
m_workqueue_number(workqueue_number), m_pthread_number(pthread_number),
m_stop(false), m_pthreads(NULL){
// 创建线程池数组
m_pthreads = new pthread_t[m_pthread_number];
// 创建工作线程,并设置线程分离
for(int i = 0; i < m_pthread_number; i++){
if( pthread_create(m_pthreads + i, NULL, worker, this) == 0 ){
cout << "creat " << i <<"th pthread" << endl;
// printf("创建第%d个线程\n",i);
pthread_detach(m_pthreads[i]);
}
}
}
template <typename T>
pthreadpool<T>::~pthreadpool(){
delete [] m_pthreads;
m_stop = true;
}
template <typename T>
void* pthreadpool<T>::worker(void* arg){
pthreadpool* poll = (pthreadpool*) arg;
poll->run();
return poll;
}
template <typename T>
void pthreadpool<T>::run(){
while( !m_stop ){
workqueuetat.wait();
workqueuelocker.lock();
if( !m_workqueue.empty() ){
// 工作队列中有任务
T* request = m_workqueue.front();
m_workqueue.pop_front();
workqueuelocker.unlock();
// 处理任务函数
request->process();
}
}
}
template <typename T>
bool pthreadpool<T>::append(T* request){
// 工作队列会被所有线程共享,所以要加锁
workqueuelocker.lock();
if( m_workqueue.size() >= m_workqueue_number ){
// 工作队列已满
workqueuelocker.unlock();
printf("工作队列已满!");
return false;
}
// 将任务插入到工作队列中,并且信号量+1
m_workqueue.push_back(request);
workqueuelocker.unlock();
workqueuetat.post();
return true;
}
#endif
3、“Web 服务器” 笔记02
3.1 main.cpp
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <string.h>
#include "pthreadpool.h"
#include "http_conn.h"
#include <signal.h>
#define MAX_FD 65535 //最大的文件描述符个数
#define MAX_EVENT_NUMBER 10000 // 监听的最大的事件数量
int main( int argc, char* argv[] ){
// 获取端口号
int port = atoi(argv[1]);
// 对SIGPIPE信号进行处理
struct sigaction m_sa;
m_sa.sa_flags = 0;
m_sa.sa_handler = SIG_IGN;
sigfillset( &m_sa.sa_mask ); // 将临时阻塞信号集中的所有的标志位置为1
sigaction(SIGPIPE, &m_sa, NULL);
// 创建线程池
pthreadpool<http_conn>* pool = new pthreadpool<http_conn>;
// pthreadpool<http_conn>* pool = NULL;
// 创建一个数组,用于保存所有客户端信息
http_conn * users = new http_conn[MAX_FD];
// 创建监听的套接字
//int listenfd = socket(PF_INET, SOCK_STREAM, 0);
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
// 端口复用
int optval = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
// 绑定
struct sockaddr_in sa;
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = INADDR_ANY;
bind(listenfd, (struct sockaddr *)&sa, sizeof(sa));
// 监听
listen(listenfd, 5);
// 创建epoll实例,并将监听的文件描述符放入epoll实例中
int epollfd = epoll_create(5); // 所有的连接文件描述符共享一个 epollfd
http_conn::m_epollfd = epollfd;
struct epoll_event epev;
epev.events = EPOLLIN | EPOLLRDHUP;
epev.data.fd = listenfd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &epev);
// 创建epoll_event数组,将发生了变化的文件描述符放入其中
struct epoll_event epevs[MAX_EVENT_NUMBER];
while(1){
int number = epoll_wait(epollfd, (epoll_event *)&epevs, MAX_FD, -1);
// 循环遍历epoll_event数组
for(int i=0; i<number; i++){
int sockfd = epevs[i].data.fd;
if(sockfd == listenfd){
// 有客户连接进来
struct sockaddr_in client_address;
int client_address_len = sizeof(client_address);
int connfd = accept(listenfd, (struct sockaddr*)&client_address, (socklen_t *)&client_address_len);
if(http_conn::m_user_count > MAX_FD){
// 用户连接已满
close(connfd);
continue;
}
// 将新客户的信息初始化,放到users数组中
users[connfd].init(connfd);
}else if(epevs[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)){
// 客户端异常断开或者错误等事件发生,关闭连接
users[sockfd].close_conn();
}else if(epevs[i].events & EPOLLIN){
// 读数据
printf("读取客户端%d\n",sockfd);
if(users[sockfd].read()){
// 一次性把所有数据都读完,将任务添加到工作队列中
pool->append(users + sockfd);
}else{
users[sockfd].close_conn();
}
}else if(epevs[i].events & EPOLLOUT){
// 写数据
// 一次性把数据都写完,关闭文件描述符
if(users[sockfd].write()){
users[sockfd].close_conn();
}
}
}
}
close(epollfd);
close(listenfd);
delete [] users;
delete pool;
return 0;
}
3.2 http_conn.h
#ifndef HTTP_CONN_H
#define HTTP_CONN_H
#include <stdio.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <unistd.h>
#include <fcntl.h>
class http_conn{
public:
static int m_user_count; // 用户数量
static int m_epollfd; // epoll实例的文件描述符
http_conn(){}
~http_conn(){}
void process(); // 处理客户端请求
void init(int sockfd); // 初始化新接收的客户端
bool read(); // 一次性把数据都读完
bool write(); // 一次性把数据都写完
int m_sockfd; // 连接的文件描述符
void close_conn(); // 关闭连接
private:
};
#endif
3.3 http_conn.cpp
#include "http_conn.h"
// 初始化
int http_conn::m_user_count = 0;
int http_conn::m_epollfd = -1;
// 关闭连接
void http_conn::close_conn(){
// 即从epoll实例中删除该连接的文件描述符
epoll_ctl(m_epollfd, EPOLL_CTL_DEL, m_sockfd, 0);
close(m_sockfd);
m_sockfd = -1;
http_conn::m_user_count -=1;
}
// 初始化新接收的客户端
void http_conn::init(int sockfd){
printf("有新的客户端%d连接进来了\n",sockfd);
http_conn::m_sockfd = sockfd;
// 端口复用
int optval = 1;
setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
// 将连接的客户端的文件描述符放到epoll实例中
struct epoll_event epev;
epev.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT;
// epev.events = EPOLLIN | EPOLLRDHUP;
epev.data.fd = m_sockfd;
epoll_ctl(m_epollfd, EPOLL_CTL_ADD, m_sockfd, &epev);
m_user_count += 1; // 总用户数+1
}
// 一次性把数据都读完
bool http_conn::read(){
printf("一次性读完数据\n");
return true;
}
// 一次性把数据都写完
bool http_conn::write(){
printf("一次性写完数据\n");
return true;
}
// 工作线程去处理HTTP请求的的入口代码
void http_conn::process(){
// 解析HTTP请求
printf("解析HTTP请求,并生成响应\n");
// 响应HTTP请求
//printf("响应HTTP请求");
}
4、“Web 服务器” 笔记03
4.1 main.cpp
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <string.h>
#include "pthreadpool.h"
#include "http_conn.h"
#include <signal.h>
#define MAX_FD 65535 //最大的文件描述符个数
#define MAX_EVENT_NUMBER 10000 // 监听的最大的事件数量
int main( int argc, char* argv[] ){
// 获取端口号
int port = atoi(argv[1]);
// 对SIGPIPE信号进行处理
struct sigaction m_sa;
m_sa.sa_flags = 0;
m_sa.sa_handler = SIG_IGN;
sigfillset( &m_sa.sa_mask ); // 将临时阻塞信号集中的所有的标志位置为1
sigaction(SIGPIPE, &m_sa, NULL);
// 创建线程池
pthreadpool<http_conn>* pool = new pthreadpool<http_conn>;
// pthreadpool<http_conn>* pool = NULL;
// 创建一个数组,用于保存所有客户端信息
http_conn * users = new http_conn[MAX_FD];
// 创建监听的套接字
//int listenfd = socket(PF_INET, SOCK_STREAM, 0);
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
// printf("listenfd:%d\n",listenfd);
// 端口复用
int optval = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
// 绑定
struct sockaddr_in sa;
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = INADDR_ANY;
bind(listenfd, (struct sockaddr *)&sa, sizeof(sa));
// 监听
listen(listenfd, 5);
// 创建epoll实例,并将监听的文件描述符放入epoll实例中
int epollfd = epoll_create(5); // 所有的连接文件描述符共享一个 epollfd
http_conn::m_epollfd = epollfd;
struct epoll_event epev;
epev.events = EPOLLIN | EPOLLRDHUP;
epev.data.fd = listenfd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &epev);
// printf("epollfd:%d\n",epollfd);
// 创建epoll_event数组,将发生了变化的文件描述符放入其中
struct epoll_event epevs[MAX_EVENT_NUMBER];
while(true){
int number = epoll_wait(epollfd, (epoll_event *)&epevs, MAX_FD, -1);
// printf("%d\n",number);
// 循环遍历epoll_event数组
for(int i=0; i<number; i++){
int sockfd = epevs[i].data.fd;
if(sockfd == listenfd){
// 有客户连接进来
struct sockaddr_in client_address;
int client_address_len = sizeof(client_address);
int connfd = accept(listenfd, (struct sockaddr*)&client_address, (socklen_t *)&client_address_len);
printf("有新的客户端%d连接进来了\n",connfd);
if(http_conn::m_user_count > MAX_FD){
// 用户连接已满
close(connfd);
continue;
}
// 将新客户的信息初始化,放到users数组中
users[connfd].init(connfd);
}else if(epevs[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)){
// 客户端异常断开或者错误等事件发生,关闭连接
users[sockfd].close_conn();
}else if(epevs[i].events & EPOLLIN){
// 读数据
printf("读取客户端%d\n",sockfd);
if(users[sockfd].read()){
// 一次性把所有数据都读完,将任务添加到工作队列中
pool->append(users + sockfd);
}else{
users[sockfd].close_conn();
}
}else if(epevs[i].events & EPOLLOUT){
// 写数据
// 一次性把数据都写完,关闭文件描述符
if(users[sockfd].write()){
users[sockfd].close_conn();
}
}
}
}
close(epollfd);
close(listenfd);
delete [] users;
delete pool;
return 0;
}
4.2 http_conn.h
#ifndef HTTP_CONN_H
#define HTTP_CONN_H
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <unistd.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#define READ_BUF_SIZE 2048 // 读缓冲大小
#define FILE_LEN 200 // 文件路径长度
class http_conn{
public:
static int m_user_count; // 用户数量
static int m_epollfd; // epoll实例的文件描述符
// HTTP请求方法,这里只支持GET
enum METHOD {GET = 0, POST, HEAD, PUT, DELETE, TRACE, OPTIONS, CONNECT};
/*
解析客户端请求时,主状态机的状态
CHECK_STATE_REQUESTLINE:当前正在分析请求行
CHECK_STATE_HEADER:当前正在分析头部字段
CHECK_STATE_CONTENT:当前正在解析请求体
*/
enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT };
// 从状态机的三种可能状态,即行的读取状态,分别表示
// 1.读取到一个完整的行 2.行出错 3.行数据尚且不完整
enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };
/*
服务器处理HTTP请求的可能结果,报文解析的结果
NO_REQUEST : 请求不完整,需要继续读取客户数据
GET_REQUEST : 表示获得了一个完成的客户请求
BAD_REQUEST : 表示客户请求语法错误
NO_RESOURCE : 表示服务器没有资源
FORBIDDEN_REQUEST : 表示客户对资源没有足够的访问权限
FILE_REQUEST : 文件请求,获取文件成功
INTERNAL_ERROR : 表示服务器内部错误
CLOSED_CONNECTION : 表示客户端已经关闭连接了
*/
enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, NO_RESOURCE, FORBIDDEN_REQUEST, FILE_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };
http_conn(){}
~http_conn(){}
void process(); // 处理客户端请求
void init(int sockfd); // 初始化新接收的客户端
void init(); // 初始化新接收的客户端的其他信息
bool read(); // 一次性把数据都读完
bool write(); // 一次性把数据都写完
int setnonblocking(int fd); // 设置文件描述符为非阻塞
int m_sockfd; // 连接的文件描述符
void close_conn(); // 关闭连接
void modfd(); // 修改文件描述符,重置socket上的EPOLLONESHOT事件,以确保下一次可读时,EPOLLIN事件能被触发
private:
char* read_buf; // 读缓冲
int m_read_index; // 指向每次向读缓冲读入数据后的下一个位置
int startline; // 获取行数据时,每行的起始位置
int m_checked_index; // 当前正在分析的字符在读缓冲区中的位置
METHOD m_method; // 请求报文首行,方式
char* m_url; // 请求报文首行,目标URL(统一资源定位器)
char* m_version; // 请求报文首行,版本号
int m_content_length; // 请求报文头部,请求体长度
bool m_linger; // 请求报文头部,是否连接
char* m_host; // 主机名
HTTP_CODE process_read(); // 解析HTTP请求
char* getline(); // 从读缓冲中获取一行数据
LINE_STATUS parse_line(); // 解析一行,返回这行数据的完整性
HTTP_CODE parse_request_line(char* text); // 解析请求行
HTTP_CODE parse_headers(char* text); // 解析请求头部
HTTP_CODE parse_content(char* text); // 解析请求体
HTTP_CODE do_request(); //
CHECK_STATE m_checked_state; // 主状态机的状态
char m_real_file[FILE_LEN]; // 客户请求的目标文件的完整路径,其内容等于 doc_root + m_url, doc_root是网站根目录
struct stat m_file_stat; // 目标文件的状态。通过它我们可以判断文件是否存在、是否为目录、是否可读,并获取文件大小等信息
char* m_file_address; // 客户请求的目标文件被mmap到内存中的起始位置
};
#endif
4.3 http_conn.cpp
#include "http_conn.h"
// 初始化
int http_conn::m_user_count = 0;
int http_conn::m_epollfd = -1;
// 网站的根目录
const char* doc_root = "/home/nowcoder/Linux/webserver/resources";
// 设置文件描述符非阻塞
int http_conn::setnonblocking(int fd) {
int old_option = fcntl( fd, F_GETFL );
int new_option = old_option | O_NONBLOCK;
fcntl( fd, F_SETFL, new_option );
return old_option;
}
// 修改文件描述符,重置socket上的EPOLLONESHOT事件,以确保下一次可读时,EPOLLIN事件能被触发
void http_conn::modfd(){
struct epoll_event epev;
epev.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT;
epev.data.fd = m_sockfd;
epoll_ctl(m_epollfd, EPOLL_CTL_MOD, m_sockfd, &epev);
}
// 关闭连接
void http_conn::close_conn(){
// 即从epoll实例中删除该连接的文件描述符
epoll_ctl(m_epollfd, EPOLL_CTL_DEL, m_sockfd, 0);
close(m_sockfd);
m_sockfd = -1;
http_conn::m_user_count -=1;
}
// 初始化新接收的客户端
void http_conn::init(int sockfd){
// printf("有新的客户端%d连接进来了\n",sockfd);
http_conn::m_sockfd = sockfd;
// 端口复用
int optval = 1;
setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
// 将连接的客户端的文件描述符放到epoll实例中
struct epoll_event epev;
epev.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT;
// epev.events = EPOLLIN | EPOLLRDHUP;
epev.data.fd = m_sockfd;
epoll_ctl(m_epollfd, EPOLL_CTL_ADD, m_sockfd, &epev);
setnonblocking(m_sockfd);
m_user_count += 1; // 总用户数+1
init();
}
// 初始化新接收的客户端的其它信息
void http_conn::init(){
read_buf = new char[READ_BUF_SIZE];
m_read_index = 0;
startline = 0;
m_checked_state = CHECK_STATE_REQUESTLINE;
m_checked_index = 0;
m_method = GET; // 默认请求方式为GET
m_url = 0;
m_version = 0;
m_content_length = 0;
m_linger = false;
m_host = 0;
memset(m_real_file, 0, FILE_LEN);
}
// 一次性把数据都读完
bool http_conn::read(){
int read_bytes = 0;
while(1){
read_bytes = recv(m_sockfd, read_buf + m_read_index, sizeof(read_buf), 0);
if(read_bytes == -1){
if(errno == EAGAIN | EWOULDBLOCK){
// recv把fd中的数据读完,放到读缓冲去中
break;
}
return false;
}else if(read_bytes == 0){
// 对面关闭连接
return false;
}
m_read_index = m_read_index + read_bytes;
}
return true;
}
// 一次性把数据都写完
bool http_conn::write(){
printf("一次性写完数据\n");
return true;
}
// 从读缓冲中获取一行数据
char* http_conn::getline(){
return read_buf + startline;
}
// 解析一行,返回这行数据的完整性,判断依据是/r/n
http_conn::LINE_STATUS http_conn::parse_line(){
for(; m_checked_index < m_read_index; m_checked_index++){
if(read_buf[m_checked_index] == '\r'){
if((m_checked_index + 1) == m_read_index){
return LINE_OPEN;
}else if(read_buf[m_checked_index + 1] == '\n'){
read_buf[m_checked_index] = '\0'; // 要将字符串获取到,\0为字符串结束符
read_buf[m_checked_index + 1] = '\0'; // \r\n 变为 \0\0
m_checked_index += 1; // 下一次分析字符时的位置
return LINE_OK;
}
return LINE_BAD;
}
if(read_buf[m_checked_index] == '\n'){
if(m_checked_index > 1 && read_buf[m_checked_index - 1] == '\r'){
read_buf[m_checked_index - 1] == '\0';
read_buf[m_checked_index] == '\0';
m_checked_index += 1;
return LINE_OK;
}
return LINE_BAD;
}
}
return LINE_OPEN;
}
// 解析请求行
http_conn::HTTP_CODE http_conn::parse_request_line(char* text){
// GET /index.html HTTP/1.1
m_url = strpbrk(text, " \t");
if(!m_url) return BAD_REQUEST;
*m_url++ = '\0'; // GET\0/index.html HTTP/1.1
if(strcasecmp(text, "GET") == 0){
m_method = GET;
}else{
return BAD_REQUEST;
}
char* version;
version = strpbrk(m_url, " \t"); // m_url为/index.html HTTP/1.1
*version++ = '\0'; // /index.html\0HTTP/1.1
if(strcasecmp(version, "HTTP/1.1") == 0){
m_version = "HTTP/1.1";
}else{
return BAD_REQUEST;
}
/* m_url 可能是下面的两种:
http://192.168.110.129:10000/index.html
或者
/index.html
*/
if(strncasecmp(m_url, "http://", 7) == 0){
m_url += 7;
m_url = strchr(m_url, '/'); // m_url为/index.html
if(m_url[0] != '/' || !m_url){
return BAD_REQUEST;
}
}
m_checked_state = CHECK_STATE_HEADER;
// 请求报文的请求行中方法、目标URL和版本号都正确解析,下一步该解析请求首部了
return NO_REQUEST;
}
// 解析请求头部
http_conn::HTTP_CODE http_conn::parse_headers(char* text){
// 遇到空行表示请求头部已经解析完成
if(text[0] == '\0'){
if(m_content_length == 0){
// 如果请求报文没有消息体,则表示获得一个完整的客户请求
return GET_REQUEST;
}else{
// 如果请求报文有消息体,那么还要继续解析消息体
m_checked_state = CHECK_STATE_CONTENT;
return NO_REQUEST;
}
}else if(strncasecmp(text, "Content_Length:", 15) == 0){
// 处理Content_Length头部字段
text += 15;
text += strspn(text, " \t");
m_content_length = atol(text);
}else if(strncasecmp(text, "Connection:", 11) == 0){
// 处理Connection头部字段,keep-alive
text += 11;
text += strspn(text, " \t");
if(strcasecmp(text, "keep-alive") == 0){
m_linger = true;
}
}else if(strncasecmp(text, "Host:", 5) == 0){
// 处理Host头部字段
text += 5;
text += strspn(text, " \t");
m_host = text;
}else{
// 咱们定义之外的其他头部字段
printf("Oh,我也不知道是啥头部字段 %s",text);
}
return NO_REQUEST;
}
// 解析请求体
http_conn::HTTP_CODE http_conn::parse_content(char* text){
// 我们没有真正解析HTTP请求的消息体,只是判断它是否被完整的读入了
if((m_checked_index + m_content_length) <= m_read_index){
text[ m_content_length ] = '\0';
return GET_REQUEST;
}
return NO_REQUEST;
}
// 解析HTTP请求
http_conn::HTTP_CODE http_conn::process_read(){
char* text = 0;
HTTP_CODE ret = NO_REQUEST;
LINE_STATUS line_status = LINE_OK;
while((line_status = parse_line()) == LINE_OK ||
((m_checked_state == CHECK_STATE_CONTENT) && (line_status == LINE_OK))){
// 读取到一个完整的行
text = getline();
startline = m_checked_index;
printf("得到一行数据:%s\n",text);
switch(m_checked_state){
case CHECK_STATE_REQUESTLINE:{
// 解析请求行
if(parse_request_line(text) == BAD_REQUEST){
return BAD_REQUEST;
}
break;
}
case CHECK_STATE_HEADER:{
// 解析请求头部
ret = parse_headers(text);
if(ret == BAD_REQUEST){
return BAD_REQUEST;
}else if(ret == GET_REQUEST){
return do_request();
}
break;
}
case CHECK_STATE_CONTENT:{
// 解析请求主体
if(parse_content(text) == GET_REQUEST){
return do_request();
}
}
default:{
return INTERNAL_ERROR;
}
}
}
return NO_REQUEST;
}
// 当得到一个完整、正确的HTTP请求时,我们就分析目标文件的属性,
// 如果目标文件存在、对所有用户可读,且不是目录,则使用mmap将其
// 映射到内存地址m_file_address处,并告诉调用者获取文件成功
http_conn::HTTP_CODE http_conn::do_request()
{
// "/home/nowcoder/Practice/resources"
strcpy( m_real_file, doc_root );
int len = strlen( doc_root );
strncpy( m_real_file + len, m_url, FILE_LEN - len - 1 );
// 获取m_real_file文件的相关的状态信息,-1失败,0成功
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)
m_file_address = ( char* )mmap( 0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );
close(fd);
return FILE_REQUEST;
}
// 工作线程去处理HTTP请求的的入口代码
void http_conn::process(){
// 解析HTTP请求
HTTP_CODE read_ret = process_read();
if( read_ret == NO_REQUEST){
// HTTP请求不完整,需要继续读取客户数据
// 重置epolloneshot事件
modfd();
return;
}
// 响应HTTP请求
//printf("响应HTTP请求");
}
5、“Web 服务器” 笔记04
5.1 main.cpp
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <netinet/in.h>
#include <sys/epoll.h>
#include <unistd.h>
#include <string.h>
#include "pthreadpool.h"
#include "http_conn.h"
#include <signal.h>
#define MAX_FD 65535 //最大的文件描述符个数
#define MAX_EVENT_NUMBER 10000 // 监听的最大的事件数量
int main( int argc, char* argv[] ){
// 获取端口号
int port = atoi(argv[1]);
// 对SIGPIPE信号进行处理
struct sigaction m_sa;
m_sa.sa_flags = 0;
m_sa.sa_handler = SIG_IGN;
sigfillset( &m_sa.sa_mask ); // 将临时阻塞信号集中的所有的标志位置为1
sigaction(SIGPIPE, &m_sa, NULL);
// 创建线程池
pthreadpool<http_conn>* pool = new pthreadpool<http_conn>;
// pthreadpool<http_conn>* pool = NULL;
// 创建一个数组,用于保存所有客户端信息
http_conn * users = new http_conn[MAX_FD];
// 创建监听的套接字
//int listenfd = socket(PF_INET, SOCK_STREAM, 0);
int listenfd = socket(AF_INET, SOCK_STREAM, 0);
// printf("listenfd:%d\n",listenfd);
// 端口复用
int optval = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
// 绑定
struct sockaddr_in sa;
sa.sin_family = AF_INET;
sa.sin_port = htons(port);
sa.sin_addr.s_addr = INADDR_ANY;
bind(listenfd, (struct sockaddr *)&sa, sizeof(sa));
// 监听
listen(listenfd, 5);
// 创建epoll实例,并将监听的文件描述符放入epoll实例中
int epollfd = epoll_create(5); // 所有的连接文件描述符共享一个 epollfd
http_conn::m_epollfd = epollfd;
struct epoll_event epev;
epev.events = EPOLLIN | EPOLLRDHUP;
epev.data.fd = listenfd;
epoll_ctl(epollfd, EPOLL_CTL_ADD, listenfd, &epev);
// printf("epollfd:%d\n",epollfd);
// 创建epoll_event数组,将发生了变化的文件描述符放入其中
struct epoll_event epevs[MAX_EVENT_NUMBER];
while(true){
int number = epoll_wait(epollfd, (epoll_event *)&epevs, MAX_FD, -1);
// printf("%d\n",number);
// 循环遍历epoll_event数组
for(int i=0; i<number; i++){
int sockfd = epevs[i].data.fd;
if(sockfd == listenfd){
// 有客户连接进来
struct sockaddr_in client_address;
int client_address_len = sizeof(client_address);
int connfd = accept(listenfd, (struct sockaddr*)&client_address, (socklen_t *)&client_address_len);
printf("有新的客户端%d连接进来了\n",connfd);
if(http_conn::m_user_count > MAX_FD){
// 用户连接已满
close(connfd);
continue;
}
// 将新客户的信息初始化,放到users数组中
users[connfd].init(connfd);
}else if(epevs[i].events & (EPOLLERR | EPOLLHUP | EPOLLRDHUP)){
// 客户端异常断开或者错误等事件发生,关闭连接
users[sockfd].close_conn();
}else if(epevs[i].events & EPOLLIN){
// 读数据
printf("读取客户端%d\n",sockfd);
if(users[sockfd].read()){
// 一次性把所有数据都读完,将任务添加到工作队列中
pool->append(users + sockfd);
}else{
users[sockfd].close_conn();
}
}else if(epevs[i].events & EPOLLOUT){
// 写数据
// 一次性把数据都写完,关闭文件描述符
if(users[sockfd].write()){
users[sockfd].close_conn();
}
}
}
}
close(epollfd);
close(listenfd);
delete [] users;
delete pool;
return 0;
}
5.2 http_conn.h
#ifndef HTTP_CONN_H
#define HTTP_CONN_H
#include <stdio.h>
#include <stdlib.h>
#include <sys/epoll.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/mman.h>
#include <stdarg.h>
#include <sys/uio.h>
#include <unistd.h>
#include <unistd.h>
#include <fcntl.h>
#include <errno.h>
#define READ_BUF_SIZE 2048 // 读缓冲大小
#define FILE_LEN 200 // 文件路径长度
#define WRITE_BUF_SIZE 2048 // 写缓冲大小
class http_conn{
public:
static int m_user_count; // 用户数量
static int m_epollfd; // epoll实例的文件描述符
// HTTP请求方法,这里只支持GET
enum METHOD {GET = 0, POST, HEAD, PUT, DELETE, TRACE, OPTIONS, CONNECT};
/*
解析客户端请求时,主状态机的状态
CHECK_STATE_REQUESTLINE:当前正在分析请求行
CHECK_STATE_HEADER:当前正在分析头部字段
CHECK_STATE_CONTENT:当前正在解析请求体
*/
enum CHECK_STATE { CHECK_STATE_REQUESTLINE = 0, CHECK_STATE_HEADER, CHECK_STATE_CONTENT };
// 从状态机的三种可能状态,即行的读取状态,分别表示
// 1.读取到一个完整的行 2.行出错 3.行数据尚且不完整
enum LINE_STATUS { LINE_OK = 0, LINE_BAD, LINE_OPEN };
/*
服务器处理HTTP请求的可能结果,报文解析的结果
NO_REQUEST : 请求不完整,需要继续读取客户数据
GET_REQUEST : 表示获得了一个完成的客户请求
BAD_REQUEST : 表示客户请求语法错误
NO_RESOURCE : 表示服务器没有资源
FORBIDDEN_REQUEST : 表示客户对资源没有足够的访问权限
FILE_REQUEST : 文件请求,获取文件成功
INTERNAL_ERROR : 表示服务器内部错误
CLOSED_CONNECTION : 表示客户端已经关闭连接了
*/
enum HTTP_CODE { NO_REQUEST, GET_REQUEST, BAD_REQUEST, NO_RESOURCE, FORBIDDEN_REQUEST, FILE_REQUEST, INTERNAL_ERROR, CLOSED_CONNECTION };
http_conn(){}
~http_conn(){}
void process(); // 处理客户端请求
void init(int sockfd); // 初始化新接收的客户端
void init(); // 初始化新接收的客户端的其他信息
bool read(); // 一次性把数据都读完
bool write(); // 一次性把数据都写完
int setnonblocking(int fd); // 设置文件描述符为非阻塞
int m_sockfd; // 连接的文件描述符
void close_conn(); // 关闭连接
void modfd(int ep); // 修改文件描述符,重置socket上的EPOLLONESHOT事件,以确保下一次可读时,EPOLLIN事件能被触发
private:
char* read_buf; // 读缓冲
int m_read_index; // 指向每次向读缓冲读入数据后的下一个位置
int startline; // 获取行数据时,每行的起始位置
int m_checked_index; // 当前正在分析的字符在读缓冲区中的位置
METHOD m_method; // 请求报文首行,方式
char* m_url; // 请求报文首行,目标URL(统一资源定位器)
char* m_version; // 请求报文首行,版本号
int m_content_length; // 请求报文头部,请求体长度
bool m_linger; // 请求报文头部,是否连接
char* m_host; // 主机名
HTTP_CODE process_read(); // 解析HTTP请求
char* getline(); // 从读缓冲中获取一行数据
LINE_STATUS parse_line(); // 解析一行,返回这行数据的完整性
HTTP_CODE parse_request_line(char* text); // 解析请求行
HTTP_CODE parse_headers(char* text); // 解析请求头部
HTTP_CODE parse_content(char* text); // 解析请求体
HTTP_CODE do_request();
void unmap(); // 对内存映射区执行munmap操作
CHECK_STATE m_checked_state; // 主状态机的状态
char m_real_file[FILE_LEN]; // 客户请求的目标文件的完整路径,其内容等于 doc_root + m_url, doc_root是网站根目录
struct stat m_file_stat; // 目标文件的状态。通过它我们可以判断文件是否存在、是否为目录、是否可读,并获取文件大小等信息
char* m_file_address; // 客户请求的目标文件被mmap到内存中的起始位置
bool process_write(HTTP_CODE read_ret); // 生成HTTP响应
bool add_status_line(int status,const char* title); // 添加响应首行
bool add_headers(int len); // 添加响应头部
bool add_content(const char* form); // 添加响应体
bool add_response(const char* farmat, ...); //往读缓冲中写入待发送的数据
bool add_content_length(int len); // 添加响应头中的Content-Length
bool add_content_type(); // 添加响应头中的Content-Type
bool add_linger(); // 添加响应头中的Connection
bool add_blank_line(); // 添加空行
char* write_buf; // 写缓冲
int m_write_index; // 指向每次向写缓冲写入数据后的下一个位置
struct iovec io_vec[2]; // 我们将采用writev来执行写操作,所以定义下面两个成员,其中m_io_count表示被写内存块的数量。
int m_io_count;
int bytes_to_send; // 将要发送的字节数
int bytes_have_sent; // 已经发送的字节数
};
#endif
5.3 http_conn.cpp
#include "http_conn.h"
// 定义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";
// 初始化
int http_conn::m_user_count = 0;
int http_conn::m_epollfd = -1;
// 网站的根目录
const char* doc_root = "/home/nowcoder/Linux/webserver/resources";
// 设置文件描述符非阻塞
int http_conn::setnonblocking(int fd) {
int old_option = fcntl( fd, F_GETFL );
int new_option = old_option | O_NONBLOCK;
fcntl( fd, F_SETFL, new_option );
return old_option;
}
// 修改文件描述符,重置socket上的EPOLLONESHOT事件,以确保下一次可读时,EPOLLIN事件能被触发
void http_conn::modfd(int ep){
struct epoll_event epev;
epev.events = ep | EPOLLRDHUP | EPOLLONESHOT;
epev.data.fd = m_sockfd;
epoll_ctl(m_epollfd, EPOLL_CTL_MOD, m_sockfd, &epev);
}
// 关闭连接
void http_conn::close_conn(){
// 即从epoll实例中删除该连接的文件描述符
epoll_ctl(m_epollfd, EPOLL_CTL_DEL, m_sockfd, 0);
close(m_sockfd);
m_sockfd = -1;
http_conn::m_user_count -=1;
}
// 初始化新接收的客户端
void http_conn::init(int sockfd){
// printf("有新的客户端%d连接进来了\n",sockfd);
http_conn::m_sockfd = sockfd;
// 端口复用
int optval = 1;
setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &optval, sizeof(optval));
// 将连接的客户端的文件描述符放到epoll实例中
struct epoll_event epev;
epev.events = EPOLLIN | EPOLLRDHUP | EPOLLONESHOT;
// epev.events = EPOLLIN | EPOLLRDHUP;
epev.data.fd = m_sockfd;
epoll_ctl(m_epollfd, EPOLL_CTL_ADD, m_sockfd, &epev);
setnonblocking(m_sockfd);
m_user_count += 1; // 总用户数+1
init();
}
// 初始化新接收的客户端的其它信息
void http_conn::init(){
read_buf = new char[READ_BUF_SIZE];
write_buf = new char[WRITE_BUF_SIZE];
m_read_index = 0;
startline = 0;
m_checked_state = CHECK_STATE_REQUESTLINE;
m_checked_index = 0;
m_method = GET; // 默认请求方式为GET
m_url = 0;
m_version = 0;
m_content_length = 0;
m_linger = false;
m_host = 0;
m_file_address = 0;
m_write_index = 0;
memset(read_buf, 0, READ_BUF_SIZE);
memset(m_real_file, 0, FILE_LEN);
memset(write_buf, 0, WRITE_BUF_SIZE);
m_io_count = 0;
bytes_to_send = 0;
bytes_have_sent = 0;
}
// 一次性把数据都读完
bool http_conn::read(){
int read_bytes = 0;
while(1){
read_bytes = recv(m_sockfd, read_buf + m_read_index, sizeof(read_buf), 0);
if(read_bytes == -1){
if(errno == EAGAIN | EWOULDBLOCK){
// recv把fd中的数据读完,放到读缓冲去中
break;
}
return false;
}else if(read_bytes == 0){
// 对面关闭连接
return false;
}
m_read_index = m_read_index + read_bytes;
}
return true;
}
// 从读缓冲中获取一行数据
char* http_conn::getline(){
return read_buf + startline;
}
// 解析一行,返回这行数据的完整性,判断依据是/r/n
http_conn::LINE_STATUS http_conn::parse_line(){
for(; m_checked_index < m_read_index; m_checked_index++){
if(read_buf[m_checked_index] == '\r'){
if((m_checked_index + 1) == m_read_index){
return LINE_OPEN;
}else if(read_buf[m_checked_index + 1] == '\n'){
read_buf[m_checked_index] = '\0'; // 要将字符串获取到,\0为字符串结束符
read_buf[m_checked_index + 1] = '\0'; // \r\n 变为 \0\0
m_checked_index += 1; // 下一次分析字符时的位置
return LINE_OK;
}
return LINE_BAD;
}
if(read_buf[m_checked_index] == '\n'){
if(m_checked_index > 1 && read_buf[m_checked_index - 1] == '\r'){
read_buf[m_checked_index - 1] == '\0';
read_buf[m_checked_index] == '\0';
m_checked_index += 1;
return LINE_OK;
}
return LINE_BAD;
}
}
return LINE_OPEN;
}
// 解析请求行
http_conn::HTTP_CODE http_conn::parse_request_line(char* text){
// GET /index.html HTTP/1.1
m_url = strpbrk(text, " \t");
if(!m_url) return BAD_REQUEST;
*m_url++ = '\0'; // GET\0/index.html HTTP/1.1
if(strcasecmp(text, "GET") == 0){
m_method = GET;
}else{
return BAD_REQUEST;
}
char* version;
version = strpbrk(m_url, " \t"); // m_url为/index.html HTTP/1.1
*version++ = '\0'; // /index.html\0HTTP/1.1
if(strcasecmp(version, "HTTP/1.1") == 0){
m_version = "HTTP/1.1";
}else{
return BAD_REQUEST;
}
/* m_url 可能是下面的两种:
http://192.168.110.129:10000/index.html
或者
/index.html
*/
if(strncasecmp(m_url, "http://", 7) == 0){
m_url += 7;
m_url = strchr(m_url, '/'); // m_url为/index.html
if(m_url[0] != '/' || !m_url){
return BAD_REQUEST;
}
}
m_checked_state = CHECK_STATE_HEADER;
// 请求报文的请求行中方法、目标URL和版本号都正确解析,下一步该解析请求首部了
return NO_REQUEST;
}
// 解析请求头部
http_conn::HTTP_CODE http_conn::parse_headers(char* text){
// 遇到空行表示请求头部已经解析完成
if(text[0] == '\0'){
if(m_content_length == 0){
// 如果请求报文没有消息体,则表示获得一个完整的客户请求
return GET_REQUEST;
}else{
// 如果请求报文有消息体,那么还要继续解析消息体
m_checked_state = CHECK_STATE_CONTENT;
return NO_REQUEST;
}
}else if(strncasecmp(text, "Content_Length:", 15) == 0){
// 处理Content_Length头部字段
text += 15;
text += strspn(text, " \t");
m_content_length = atol(text);
}else if(strncasecmp(text, "Connection:", 11) == 0){
// 处理Connection头部字段,keep-alive
text += 11;
text += strspn(text, " \t");
if(strcasecmp(text, "keep-alive") == 0){
m_linger = true;
}
}else if(strncasecmp(text, "Host:", 5) == 0){
// 处理Host头部字段
text += 5;
text += strspn(text, " \t");
m_host = text;
}else{
// 咱们定义之外的其他头部字段
printf("Oh,我也不知道是啥头部字段 %s",text);
}
return NO_REQUEST;
}
// 解析请求体
http_conn::HTTP_CODE http_conn::parse_content(char* text){
// 我们没有真正解析HTTP请求的消息体,只是判断它是否被完整的读入了
if((m_checked_index + m_content_length) <= m_read_index){
text[ m_content_length ] = '\0';
return GET_REQUEST;
}
return NO_REQUEST;
}
// 解析HTTP请求
http_conn::HTTP_CODE http_conn::process_read(){
char* text = 0;
HTTP_CODE ret = NO_REQUEST;
LINE_STATUS line_status = LINE_OK;
while((line_status = parse_line()) == LINE_OK ||
((m_checked_state == CHECK_STATE_CONTENT) && (line_status == LINE_OK))){
// 读取到一个完整的行
text = getline();
startline = m_checked_index;
printf("得到一行数据:%s\n",text);
switch(m_checked_state){
case CHECK_STATE_REQUESTLINE:{
// 解析请求行
if(parse_request_line(text) == BAD_REQUEST){
return BAD_REQUEST;
}
break;
}
case CHECK_STATE_HEADER:{
// 解析请求头部
ret = parse_headers(text);
if(ret == BAD_REQUEST){
return BAD_REQUEST;
}else if(ret == GET_REQUEST){
return do_request();
}
break;
}
case CHECK_STATE_CONTENT:{
// 解析请求主体
if(parse_content(text) == GET_REQUEST){
return do_request();
}
}
default:{
return INTERNAL_ERROR;
}
}
}
return NO_REQUEST;
}
// 当得到一个完整、正确的HTTP请求时,我们就分析目标文件的属性,
// 如果目标文件存在、对所有用户可读,且不是目录,则使用mmap将其
// 映射到内存地址m_file_address处,并告诉调用者获取文件成功
http_conn::HTTP_CODE http_conn::do_request()
{
// "/home/nowcoder/Practice/resources"
strcpy( m_real_file, doc_root );
int len = strlen( doc_root );
strncpy( m_real_file + len, m_url, FILE_LEN - len - 1 );
// 获取m_real_file文件的相关的状态信息,-1失败,0成功
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)
m_file_address = ( char* )mmap( 0, m_file_stat.st_size, PROT_READ, MAP_PRIVATE, fd, 0 );
close(fd);
return FILE_REQUEST;
}
// 往读缓冲中写入待发送的数据
bool http_conn::add_response(const char* format, ...){
if(m_write_index > WRITE_BUF_SIZE){
return false;
}
va_list elem;
va_start(elem, format);
int len = vsnprintf(write_buf + m_write_index, WRITE_BUF_SIZE - m_write_index , format, elem);
if(len>WRITE_BUF_SIZE - m_write_index){
return false;
}
m_write_index += len;
va_end(elem);
return true;
}
// 添加响应首行
bool http_conn::add_status_line(int status,const char* title){
return add_response("%s %d %s\r\n", "HTTP/1.1", status, title);
}
// 添加响应头部
bool http_conn::add_headers(int len){
add_content_length(len);
add_content_type();
add_linger();
add_blank_line();
}
bool http_conn::add_content_length(int len){
return add_response("Content-Length: %d\r\n", len);
}
bool http_conn::add_content_type(){
return add_response("Content-Type: %s\r\n", "text/HTML");
}
bool http_conn::add_linger(){
return add_response("Connection: %s\r\n", (m_linger == true) ? "keep-alive" : "close");
}
bool http_conn::add_blank_line(){
return add_response("%s", "\r\n");
}
// 添加响应体
bool http_conn::add_content(const char* form){
return add_response("%s", form);
}
// 对内存映射区执行munmap操作
void http_conn::unmap() {
if( m_file_address )
{
munmap( m_file_address, m_file_stat.st_size );
m_file_address = 0;
}
}
// 写HTTP响应
bool http_conn::write(){
int temp = 0;
if(bytes_to_send == 0){
// 将要发送的字节数为0,这一次响应结束
modfd(EPOLLIN);
init();
return true;
}
while(1) {
// 分散写
temp = writev(m_sockfd, io_vec, m_io_count);
if ( temp <= -1 ) {
// 如果TCP写缓冲没有空间,则等待下一轮EPOLLOUT事件,虽然在此期间,
// 服务器无法立即接收到同一客户的下一个请求,但可以保证连接的完整性。
if( errno == EAGAIN ) {
modfd(EPOLLOUT);
return true;
}
unmap();
return false;
}
bytes_have_sent += temp;
bytes_to_send -= temp;
if (bytes_have_sent >= io_vec[0].iov_len)
{
io_vec[0].iov_len = 0;
io_vec[1].iov_base = m_file_address + (bytes_have_sent - m_write_index);
io_vec[1].iov_len = bytes_to_send;
}
else
{
io_vec[0].iov_base = write_buf + bytes_have_sent;
io_vec[0].iov_len = io_vec[0].iov_len - temp;
}
if (bytes_to_send <= 0)
{
// 没有数据要发送了
unmap();
modfd(EPOLLIN);
if (m_linger)
{
init();
return true;
}
else
{
return false;
}
}
}
}
// 生成HTTP响应
bool http_conn::process_write(HTTP_CODE read_ret){
switch(read_ret){
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;
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;
case NO_REQUEST:
add_status_line(404, error_404_title);
add_headers(strlen(error_404_form));
if(!add_content(error_404_form)){
return false;
}
break;
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;
case FILE_REQUEST:
add_status_line(200, ok_200_title);
add_headers(m_file_stat.st_size);
io_vec[0].iov_base = write_buf; // 写缓冲的首地址
io_vec[0].iov_len = m_write_index; // 写缓冲区中待发送的数据长度
io_vec[1].iov_base = m_file_address; // 目标文件的首地址
io_vec[1].iov_len = m_file_stat.st_size; // 目标文件的大小
m_io_count = 2;
bytes_to_send = m_write_index + m_file_stat.st_size;
return true;
default:{
return false;
}
}
// 如果报错,那么将writev将发送的数据
io_vec[0].iov_base = write_buf;
io_vec[0].iov_len = m_write_index;
m_io_count = 1;
bytes_to_send = m_write_index;
return true;
}
// 工作线程去处理HTTP请求的的入口代码
void http_conn::process(){
// 解析HTTP请求
HTTP_CODE read_ret = process_read();
if( read_ret == NO_REQUEST){
// HTTP请求不完整,需要继续读取客户数据
// 重置epolloneshot事件
modfd(EPOLLIN);
return;
}
// 响应HTTP请求
bool write_ret = process_write(read_ret);
if(!write_ret){
// 如果报错,关闭连接
close_conn();
}
modfd(EPOLLOUT);
}
5.4 Resource
1个 image 图片 + 1个index.html 文件。
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>首页</title>
</head>
<body>
<h1>欢迎来到首页~~~</h1> </br>
<img src="images/image1.jpg" alt="ces" />
</body>
</html>