【项目收获】web服务器的系统架构解析

本文深入解析了一个Web服务器的架构,从开启线程池和事件循环开始,详细阐述了主线程如何处理连接事件,IO线程如何处理读写事件。文中介绍了线程池的创建、线程与事件循环的关系,以及主线程如何监听新连接。此外,还探讨了IO线程如何通过eventfd进行异步唤醒,处理新连接,以及timerNode和HttpData在超时请求管理中的作用。文章最后提到了日志处理机制,包括前端后端缓冲区的设计,确保日志高效写入磁盘。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

目录

model

http请求解析:

1.开启线程池和事件循环

2.主线程处理连接事件

3.IO线程处理读写事件

4.timerNode和HttpData

5.日志

6.其他

一些问题:


model

 

http请求解析:

 

 

这个层层调用还是有些复杂,要好好捋一捋

1.开启线程池和事件循环

  • Main函数开始运行,首先创建
EventLoop mainLoop;
Server myHTTPServer(&mainLoop, threadNum, port);		//在此构造函数中new了一个EventLoopThreadPool
  • Main函数中开启主事件循环(reactor线程)运行
	myHTTPServer.start();
	mainLoop.loop();
  • 在 myHTTPServer构造函数中,创建了线程池eventLoopThreadPool_(new EventLoopThreadPool(loop_,threadNum)),其线程数量由threadNum指定
  • 在myHTTPServer.start()中,启动了线程池:eventLoopThreadPool_->start();

好,在启动线程池之后,为线程池添加事件循环线程:

for (int i = 0; i < numThreads_; ++i) {
		std::shared_ptr<EventLoopThread> t(new EventLoopThread());	//根据传入的线程数创建指向loop线程的智能指针
		threads_.push_back(t);		//将管理循环线程的指针填入已创建线程的数组,便于统一管理
		loops_.push_back(t->startLoop());	//开启新建线程的循环,并放入管理循环的数组中
	}

在new EventLoopThread()中,我们一层一层往下看:

在EventLoopThread的构造函数中,有

thread_(bind(&EventLoopThread::threadFunc, this), "EventLoopThread"),  //绑定线程的函数

//其中
void EventLoopThread::threadFunc() {
	EventLoop loop;
	{
		MutexLockGuard lock(mutex_);
		loop_ = &loop;		//引用	,到这里loop_就不为NULL了,即条件变量等到了
		cond_.notify();
	}

	loop.loop();
	//assert(exiting_);
	loop_ = NULL;			//就是起到一个EventLoopThread创建EventLoop并启动事件循环的作用
}

其中thread_变量是一个Thread类,相当于我们调用了他的构造函数并绑定好了创建loop的函数,如下:

Thread::Thread(const ThreadFunc &func, const string &n)
	:started_(false),
	joined_(false),
	pthreadId_(0),
	tid_(0),
	func_(func),
	name_(n),
	latch_(1)
{
	setDefaultName();
}

接着我们看loops_.push_back(t->startLoop()); 这个函数的目的是开启线程的loop,并push进loops_数组中,便于管理。

我们看看他往下到底做了啥:

EventLoop* EventLoopThread::startLoop() {		//由EventLoopThreadPool::start()调用
	assert(!thread_.started());
	thread_.start();

	{
		MutexLockGuard lock(mutex_);		//将锁搞成一个类,在函数段内创建临时锁对象并加锁,当退出函数段时自动调用对象析构函数进行解锁
		// 一直等到threadFun在Thread里真正跑起来
		while (loop_ == NULL)
			cond_.wait();		//等在条件变量上 ,	条件变量也封装为一个条件类,并且this类中包含了cond的对象
	}
	return loop_;
}

调用了thread_.start(); 追踪一波:

void Thread::start() {
	assert(!started_);
	started_ = true;
	ThreadData* data = new ThreadData(func_, name_,&tid_, &latch_);
	if (pthread_create(&pthreadId_, NULL, &startThread, data)) {
		started_ = false;
		delete data;
	}
	else {							//注意,pthread_create创建成功是返回0的
		latch_.wait();
		assert(tid_ > 0);
	}
}

好了,实际上start就是pthread_create创建线程并绑定好我们之前给他的函数。

创建好的线程会运行threadFunc函数,创建loop并唤醒主线程,好返回自己的loop并填进loops_中。

至此完成IO线程的loop创建步骤。

  • 然后,在创建完线程之后,myHTTPServer紧接着配置他的channel:
void Server::start() {
	eventLoopThreadPool_->start();			//开启线程池
	//acceptChannel_->setEvents(EPOLLIN | EPOLLET | EPOLLONESHOT);
	acceptChannel_->setEvents(EPOLLIN | EPOLLET);
	acceptChannel_->setReadHandler(bind(&Server::handNewConn, this));    //处理新连接事件
	acceptChannel_->setConnHandler(bind(&Server::handThisConn, this));    //处理读写事件
	loop_->addToPoller(acceptChannel_, 0);	//将负责处理接受连接的channel 挂到epoll去
	started_ = true;
}

这里不得不仔细看看myHTTPServer的构造函数主要做了哪些工作:

Server::Server(EventLoop *loop, int threadNum, int port)
	: loop_(loop),
	threadNum_(threadNum),
	eventLoopThreadPool_(new EventLoopThreadPool(loop_,threadNum)),		//新建事件循环线程池
	started_(false),
	acceptChannel_(new Channel(loop_)),		//新建channel
	port_(port),
	listenFd_(socket_bind_listen(port_))	//绑定端口,初始化listenfd
{
	acceptChannel_->setFd(listenFd_);
	handle_for_sigpipe();			//在util中实现
	if (setSocketNonBlocking(listenFd_) < 0) {	//设置非阻塞监听
		perror("set socket non block failed");
		abort();
	}
}

原来他已经创建好了acceptchannel、listenfd并且绑定了端口、设置了非阻塞监听。

回到上一个程序段,一系列的set操作将acceptchannel 对新连接的处理函数进行了配置,

并且loop_->addToPoller(acceptChannel_, 0);    //将负责处理接受连接的channel 挂到epoll红黑树上去。

自此,主线程已经可以开始监听新连接事件了。

 

 

 

 

2.主线程处理连接事件

  • 准备工作完成后,主线程就开始loop了:mainLoop.loop();

好,在主线程loop中有:

void EventLoop::loop() {
	assert(!looping_);		//确保线程中的eventloop未启动
	assert(isInLoopThread());	//确保eventloop已绑定线程
	looping_ = true;
	quit_ = false;
	//LOG_TRACE << "EventLoop " << this << " start looping";
	std::vector<SP_Channel> ret;		//SP即shared_ptr
	while (!quit_) {					//不断循环取出eventloop中epoll获得的事件
		//cout << "doing" << endl;
		ret.clear();
		ret = poller_->poll();		//获得带有事件信息的channel指针数组
		eventHandling_ = true;
		for (auto &it : ret)
			it->handleEvents();	//线程逐个处理,在poll中把epoll_wait拿到的事件都放在ret数组中channel对象的revent里,这里逐个拿出来处理
		eventHandling_ = false;
		doPendingFunctors();			
		poller_->handleExpired();  //主线程处理过期连接(定时器大根堆)
	}
	looping_ = false;
}

在while循环中,调用  ret = poller_->poll();        //将channel事件拷贝过来存进vector中,然后逐个处理。

  • 我们细看他是怎么拿到事件的:

ret = poller_->poll();   下一层是:    

std::vector<SP_Channel> Epoll::poll() {	
	while (true) {				//不停取,直到取到事件就退出
		int event_count = epoll_wait(epollFd_, &*events_.begin(), events_.size(), EPOLLWAIT_TIME);		//取到事件并放入events_数组中
		if (event_count < 0)
			perror("epoll wait error");
		std::vector<SP_Channel> req_data = getEventsRequest(event_count);
		if (req_data.size() > 0)
			return req_data;
	}
}

//其中
// 分发处理函数
std::vector<SP_Channel> Epoll::getEventsRequest(int events_num) {
	std::vector<SP_Channel> req_data;
	for (int i = 0; i < events_num; ++i) {
		// 获取有事件产生的描述符
		int fd = events_[i].data.fd;
		SP_Channel cur_req = fd2chan_[fd];		//从数组中拿到当前channel

		if (cur_req) {
			cur_req->setRevents(events_[i].events);	//revents   输出 ,  此处将活跃事件赋值给channel对象的revents_变量
			cur_req->setEvents(0);
			// 加入线程池之前将Timer和request分离
			//cur_req->seperateTimer();
			req_data.push_back(cur_req);
		}
		else {
			LOG << "SP cur_req is invalid";
		}
	}
	return req_data;
}

首先epoll_wait拿到活跃事件,并放入数组events_中,(对于主线程,所有事件都是对listenfd的连接事件)然后调用getEventsRequest 拿到带有每个活跃事件信息的channel数组(数组中每个channel带着一个活跃事件)。

  • 好,拿到活跃事件(装在channel)后,逐个处理
for (auto &it : ret)
	it->handleEvents();		
//线程逐个处理,在poll中把epoll_wait拿到的事件都放在ret数组中channel对象的r
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值