#ifndef LOCKER_H
#define LOCKER_H
#include
#include
#include
// 线程同步机制封装类
// 互斥锁类
class locker {
public:
locker() {
if (pthread_mutex_init(&m_mutex, NULL) != 0) {
throw std::exception();
}
}
~locker() {
pthread_mutex_destroy(&m_mutex);
}
bool lock() {
return pthread_mutex_lock(&m_mutex) == 0;
}
bool unlock() {
return pthread_mutex_unlock(&m_mutex) == 0;
}
pthread_mutex_t * get() {
return &m_mutex;
}
private:
pthread_mutex_t m_mutex;
};
// 条件变量类
class cond {
public:
cond() {
if (pthread_cond_init(&m_cond, NULL) != 0) {
throw std::exception();
}
}
~cond() {
pthread_cond_destroy(&m_cond);
}
bool wait(pthread_mutex_t * mutex) {
return pthread_cond_wait(&m_cond, mutex) == 0;
}
bool timedwait(pthread_mutex_t * mutex, struct timespec t) {
return pthread_cond_timedwait(&m_cond, 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 std::exception();
}
}
sem(int num) {
if( sem_init( &m_sem, 0, num ) != 0 ) {
throw std::exception();
}
}
~sem() {
sem_destroy( &m_sem );
}
// 等待信号量
bool wait() {
return sem_wait( &m_sem ) == 0;
}
// 增加信号量
bool post() {
return sem_post( &m_sem ) == 0;
}
private:
sem_t m_sem;
};
#endif
#ifndef THREADPOOL_H
#define THREADPOOL_H
#include
#include
#include "locker.h"
#include
#include
// 线程池类,定义成模板类是为了复用,模板参数T是任务类
template<typename T>
class threadpool {
public:
threadpool(int thread_number = 8, int max_requests = 10000);
~threadpool();
bool append(T* request);
private:
static void* worker(void * arg);
void run();
private:
// 线程的数量
int m_thread_number;
// 线程池数组,大小为m_thread_number;
pthread_t * m_threads;
// 请求队列中最多允许的,等待处理的请求数量
int m_max_requests;
// 请求队列
std::list< T*> m_workqueue;
// 互斥锁
locker m_queuelocker;
// 信号量用来判断是否有任务需要处理
sem m_queuestat;
// 是否结束线程
bool m_stop;
};
template<typename T>
threadpool<T>::threadpool(int thread_number, int max_requests) :
m_thread_number(thread_number), m_max_requests(max_requests),
m_stop(false), m_threads(NULL) {
if ((thread_number <= 0) || (max_requests <= 0)) {
throw std::exception();
}
m_threads = new pthread_t[m_thread_number];
if (!m_threads) {
throw std::exception();
}
// 创建thread_number个线程,并将它们设置成线程脱离
for (int i = 0; i < thread_number; ++ i) {
printf("create the %dth trhead\n", i);
if (pthread_create(m_threads + i, NULL, worker, this) != 0) {
delete [] m_threads;
throw std::exception();
}
if (pthread_detach(m_threads[i])) {
delete[] m_threads;
throw std::exception();
}
}
}
template<typename T>
threadpool<T>::~threadpool() {
delete[] m_threads;
m_stop = true;
}
template<typename T>
bool threadpool<T>::append(T * request) {
m_queuelocker.lock();
if (m_workqueue.size() > m_max_requests) {
m_queuelocker.unlock();
return false;
}
m_workqueue.push_back(request);
m_queuelocker.unlock();
m_queuestat.post();
return true;
}
template<typename T>
void* threadpool<T>::worker(void * arg) {
threadpool * pool = (threadpool *) arg;
pool->run();
return pool;
}
template<typename T>
void threadpool<T>::run() {
while(!m_stop) {
m_queuestat.wait();
m_queuelocker.lock();
if(m_workqueue.empty()){
m_queuelocker.unlock();
continue;
}
T* request = m_workqueue.front();
m_workqueue.pop_back();
m_queuelocker.unlock();
if (!request) {
continue;
}
request->process();
}
}
#endif
#ifndef HTTPCONNECTION_H
#define HTTPCONNECTION_H
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "locker.h"
#include
class http_conn {
public:
// 所有的socket上的事件都被注册到一个epoll对象中
static int m_epollfd;
// 统计用户的数量
static int m_user_count;
http_conn() {}
~http_conn() {}
void process(); // 处理客户端的请求
void init(int sockfd, const sockaddr_in & addr); // 初始化连接
void close_conn(); // 关闭连接
bool read(); // 非阻塞地读
bool write(); // 非阻塞地写
private:
int m_sockfd; // 该HTTP连接的socket
sockaddr_in m_address; // 通信socket地址
};
#endif
#include "http_conn.h"
int http_conn::m_epollfd = -1;
int http_conn::m_user_count = 0;
//设置文件描述符非阻塞
void setnonblocking(int fd) {
int old_flag = fcntl(fd, F_GETFL);
int new_flag = old_flag | O_NONBLOCK;
fcntl(fd, F_SETFL, new_flag);
}
// 添加需要监听的文件描述符到epoll中
void addfd(int epollfd, int fd, bool one_shot) {
epoll_event event;
event.data.fd = fd;
event.events = EPOLLIN | EPOLLRDHUP;
// 2.6.17后版本,对方异常连接断开,可以在底层处理,不需要移交上层
if (one_shot) {
event.events | EPOLLONESHOT;
}
epoll_ctl(epollfd, EPOLL_CTL_ADD, fd, &event);
// 设置文件描述符非阻塞
setnonblocking(fd);
}
// 从epoll中删除文件描述符
/**
* @brief 即使使用ET模式,一个socket上的某个事件还是可能被触发多次。这在并发程序中就会引起一个问题
* 比如一个线程在读取某个socket上的数据后开始处理这些数据,而在数据的处理过程中该socket上又有新数据可读
* (EPOLLIN再次被触发),此时另外一个线程被唤醒来读取这些新的数据,于是就出现了两个线程同时操作一个socket的局面
* 一个socket连接在任一时刻都只被一个线程处理,可以使用epoll的EPOLLONESHOT事件实现
*
* 对于注册了EPOLLONESHOT事件的文件描述符,操作系统最多触发其上注册的一个可读、可写或者异常事件
*
* @param epollfd
* @param fd
*/
void removefd(int epollfd, int fd){
epoll_ctl(epollfd, EPOLL_CTL_DEL, fd, 0);
close(fd);
}
// 修改文件描述符,重置socket上EPOLLONESHOT事件,以确保下一次可读时,EPOLLIN事件能被触发
// 不修改,只触发一次
void modfd(int epollfd, int fd, int ev) {
epoll_event event;
event.data.fd = fd;
event.events = ev | EPOLLONESHOT | EPOLLRDHUP;
epoll_ctl(epollfd, EPOLL_CTL_MOD, fd, &event);
}
// 初始化连接
void http_conn::init(int sockfd, const sockaddr_in & addr) {
m_sockfd = sockfd;
m_address = addr;
// 端口复用
int reuse = 1;
setsockopt(m_sockfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
// 添加到epoll对象中
addfd(m_epollfd, sockfd, true);
m_user_count++;//总用户数加1
}
// 关闭连接
void http_conn::close_conn() {
if (m_sockfd != -1) {
removefd(m_epollfd, m_sockfd); // 移除
m_sockfd = -1; // 值赋为-1,就没用了
m_user_count--; // 关闭一个连接,用户数量减一
}
}
bool http_conn::read() {
printf("一次性读完数据");
return true;
}
bool http_conn::write() {
printf("一次性写完数据");
return true;
}
// 由线程池中地工作线程调用,这是处理HTTP请求的入口函数
void http_conn::process() {
// 解析HTTP请求
printf("parse request, create response\n");
// 生成响应
}
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include
#include "locker.h"
#include "threadpool.h"
#include
#include "http_conn.h"
#define MAX_FD 65535 // 最大数量
#define MAX_EVENT_NUMBER 10000 // 同时最大监听的事件数量
//添加信号捕捉
void addsig(int sig, void(handler)(int)) {
struct sigaction sa;
memset(&sa, '\0', sizeof sa);
sa.sa_handler = handler;
sigfillset(&sa.sa_mask);
sigaction(sig, &sa, NULL);
}
// 添加文件描述符到epoll中
extern void addfd(int epollfd, int fd, bool one_shot);
// 从epoll中删除文件描述符
extern void removefd(int epollfd, int fd);
// 修改文件描述符
extern void modfd(int epollfd, int fd, int ev);
int main(int argc, char* argv[]) {
if (argc <= 1) {
printf("按照如下格式运行: %s port_number\n", basename(argv[0]));
exit(-1);
}
// 获取端口号
int port = atoi(argv[1]);
// 对SIGPIE信号进行处理
addsig(SIGPIPE, SIG_IGN);
// 创建线程池,初始化线程池
threadpool<http_conn> * pool = NULL;
try {
pool = new threadpool<http_conn>;
} catch(...) {
exit(-1);
}
// 创建一个数组用于保存所有的客户端信息
http_conn * users = new http_conn[ MAX_FD ];
// 创建监听的套接字
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
// 设置端口复用
int reuse = 1;
setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
// 绑定
struct sockaddr_in address;
address.sin_family = AF_INET;
address.sin_addr.s_addr = INADDR_ANY;
address.sin_port = htons(port);
bind(listenfd, (struct sockaddr*)&address, sizeof(address));
// 监听
listen(listenfd, 5);
// 使用epoll多路复用,创建epoll对象,事件数组,检测到以后写进去,添加监听文件描述符
epoll_event events[MAX_EVENT_NUMBER];
int epollfd = epoll_create(5);
// 将监听的文件描述符添加到epoll对象中
addfd(epollfd, listenfd, false);
http_conn::m_epollfd = epollfd;
while (true) {
// 主线程循环检测哪些线程发生
int num = epoll_wait(epollfd, events, MAX_EVENT_NUMBER, -1);
if ((num < 0) && (errno != EINTR)) {
// epoll调用失败
printf("epoll failure\n");
break;
}
// 循环遍历数组
for (int i = 0; i < num; i ++ ) {
int sockfd = events[i].data.fd;
if (sockfd == listenfd) {
// 有客户端连接进来
struct sockaddr_in client_address;
socklen_t client_addrlen = sizeof(client_address);
int connfd = accept(listenfd, (struct sockaddr *)&client_address, &client_addrlen);
if (http_conn::m_user_count >= MAX_FD) {
// 目前连接数满了
// 给客户端写一个信息:服务器内部正忙。
close(connfd);
continue;
}
// 将新的客户的数据初始化,放到数组中
users[connfd].init(connfd, client_address);
}
else if (events[i].events & (EPOLLRDHUP | EPOLLHUP | EPOLLERR)) {
// 对方异常断开或者错误等事件
// 关闭连接
users[sockfd].close_conn();
}
else if (events[i].events & EPOLLIN) {
// 判断是否有读的事件发生,一次性把所有读出来
if (users[sockfd].read()) {
// 一次性把所有数据都读出来
pool->append(users + sockfd);
}
else {
// 没读到数据,失败,关闭连接
users[sockfd].close_conn();
}
}
else if (events[i].events & EPOLLOUT) {
if (!users[sockfd].write()) {
// 一次性写完所有数据
users[sockfd].close_conn(); // 写失败了关闭连接
}
}
}
}
close(epollfd);
close(listenfd);
delete [] users;
delete pool;
return 0;
}
一个软件程序,或硬件计算机。功能是通过 HTTP 协议与客户端,进行通信,来接收、存储、处理来自客户端 HTTP 请求 Request,并对请求做出 HTTP 响应 Response,返回请求的内容(文件、网页等)或返回 Error 信息。
浏览器,域名/ip:port,浏览器域名解析成相应 IP 地址或直接根据 ip 地址发送 Http 请求,这一过程首先进行 TCP 的三次握手建立与目标 Web 服务器的连接,然后 HTTP 协议生成针对 Web 服务器的 HTTP 请求报文,通过 TCP,IP 等协议发送到目标 Web Server.
socket 监听
sys/socket.h
/* 创建监听socket文件描述符 */
int listenfd = socket(PF_INET, SOCK_STREAM, 0);
/* 创建监听socket的TCP/IP的IPV4 socket地址 */
struct sockaddr_in address;
connect() accept() epoll listenfd
Reactor 模式:要求主线程(I/O 处理单元)只负责监听文件描述符上是否有事件发生(可读、可写),若有,则立即通知工作线程(逻辑单元),将 socket 可读可写事件放入请求队列,交给工作线程处理。
Proactor 模式:将所有的 I/O 操作都交给主线程和内核来处理(进行读、写),工作线程仅负责处理逻辑,如主线程读完成后 users[sockfd].read(),选择一个工作线程来处理客户请求 pool->append(users + sockfd)。
同步(阻塞)I/O:在一个线程中,CPU 执行代码的速度极快,然而,一旦遇到 IO 操作,如读写文件、发送网络数据时,就需要等待 IO 操作完成,才能继续进行下一步操作。这种情况称为同步 IO。
异步(非阻塞)I/O:当代码需要执行一个耗时的 IO 操作时,它只发出 IO 指令,并不等待 IO 结果,然后就去执行其他代码了。一段时间后,当 IO 返回结果时,再通知 CPU 进行处理。