目录

在之前的两个文章:

C++网络编程快速入门(二):Linux下使用select演示简单服务端程序_拾牙慧者的博客-CSDN博客_c++ linux select select参数解释extern int select (int __nfds, fd_set *__restrict __readfds, fd_set *__restrict __writefds, fd_set *__restrict __exceptfds, struct timeval *__restrict __timeout);__nfds:一般设置 https://hanhandi.blog.csdn.net/article/details/120998962

C++网络编程快速入门(四):EPOLL模型使用_拾牙慧者的博客-CSDN博客_c++ epoll 目录基本使用方法step1:创建epollfdstep2:将fd绑定到epollfdstep3:调用epoll_wait检测事件epoll_wait与poll、select区别所在水平触发与边缘触发基本使用方法step1:创建epollfd创建一个epollfd,若epoll_create调用成功,则返回一个非负值的epollfd,否则返回-1/* Creates an epoll instanc https://hanhandi.blog.csdn.net/article/details/121052560

我们使用了基于事件的服务器,他们都是基于事件循环的简单结构:

while (1) {
  events = getEvents();
  for (e in events) {
    processEvent(e);
  }
}

这也属于并发的一种,基本方法就是基于事件的并发。我们等待某事(即“事件”)发生;当它发生时,检查事件类型,然后做少量的相应工作(可能是I/O请求,或者调度其他事件准备后续处理)。

主循环等待事件发生,然后一次处理这些发生的事件。

具体处理事件交由事件处理程序(event handler)处理。

当event handler处理一个事件时,它是CPU中执行的唯一进程。

这样就可以进行显式调度了。

不过这也带来了下面的思考:

基于事件的服务器如何决定哪个事件发生,尤其是对于网络和磁盘的IO。

也就是说服务器如何确定是否有消息到达,这里主要牵扯到select 和epoll两个机制。

基于事件的服务器不能被另一个线程中断,因为它确实是单线程的。因此,线程化程序中常见的并发性错误并没有出现在基本的基于事件的方法中。这就要求我们的服务器不能阻塞调用。

使用基于线程的服务器时,在发出I/O请求的线程挂起(等待I/O完成)时,其他线程可以运行,从而使服务器能够取得进展。

对于基于事件并发、基于线程并发我有些疑惑,记录于此:

关于线程并发与事件并发还可以参考一下:

在事件并发服务器中,为了克服阻塞调用限制,我们使用非阻塞IO

1、有接口使得应用程序能够发出IO请求,并在IO完成前立即将控制权返回给调用者。

它基于AIO控制块实现。

struct aiocb {
  int aio_fildes;         // 文件描述符
  off_t aio_offset;       //文件内偏移量
  volatile void *aio_buf; // 复制读取结果的目标内存位置
  size_t aio_nbytes;      // 请求的长度
};

int aio_read(struct aiocb *aiocbp) {...};

该函数尝试发起IO,成功的话立即返回,事件服务器也可以继续其工作。

不成功也会立即返回,事件服务器得做好相关处理。

那么我们改如何获知IO何时完成呢?一般有两种思路:1、周期轮询2、系统中断告知

对于第一种,需要用到api:

若已完成,接口返回0;

对于第二种,使用UNIX信号异步IO完成时通知,消除了重复询问系统的需要。

#include <stdio.h>
#include <signal.h>

void handle(int arg) {
  printf("xxxx\n");
}

int main(int argc, char *argv[]) {
  signal(SIGHUP, handle);
  while (1) ; // do nothing except catch some signal
}

在没有异步I/O的系统中,纯基于事件的方法无法实现。

状态管理问题

当事件处理程序发出异步I/O时,它必须打包一些程序状态,以便下一个事件处理程序在I/O最终完成时使用。这个额外的工作在基于线程的程序中是不需要的,因为程序需要的状态在线程栈中。

在线程服务器中:当read()最终返回时,代码立即知道要写入哪个套接字,因为该信息位于线程堆栈中(在变量sd中)

在基于事件的系统中,为了执行相同的任务,我们需要自己存储并管理。

然后将套接字描述符sd记录在由文件描述符fd作为索引的散列表中,当磁盘IO完成时,事件处理程序将用fd来查找sd,将sd返回给对应的调用者。

缺点

2、如果事件处理程序发生页错误,它将被阻塞,并且因此服务器在页错误完成之前不会有进展。尽管服务器的结构可以避免显式阻塞,但由于页错误导致的这种隐式阻塞很难避免,因此在频繁发生时可能会导致较大的性能问题。