高并发服务器模型

1 多进程

2 多线程

  • 客户端 没有给服务器发送数据,服务器若采用多线程,多进程则全部阻塞掉

3 多路IO复用

3.1 select(采用的是轮询模型)

  • 采用select, 将所有的文件描述符给select去监控,有数据到达则返回
  • socket –> listenfd 1
  • accept –> connfd 2 3 4 5
  • 都没有请求则在select上面阻塞
  • select 会返回就绪文件描述符个数
  • 检测到 文件描述符1 有数据到达则去调用accept返回 文件描述符6
  • 检测到 文件描述符2 3 4 5中3 5有数据到达则返回2(阻塞中返回),轮询判断是那几个文件描述符到达,主控线程去读这几个文件描述符调用read
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int select(
int nfds, //最大文件描述符个数+1
fd_set *readfds, //可读文件描述符,传入传出参数
fd_set *writefds, //可写文件描述符,传入传出参数
fd_set *exceptfds, //异常文件描述符,传入传出参数
struct timeval * timeout //定时阻塞监控时间 timeval的结构体(包含秒和微秒)
)
int pselect(
int nfds, //最大文件描述符个数+1
fd_set *readfds, //可读文件描述符,传入传出参数
fd_set *writefds, //可写文件描述符,传入传出参数
fd_set *exceptfds, //异常文件描述符,传入传出参数
const struct timespec * timeout, //timespec结构体(包含秒和纳秒)
const sigset_t * sigmask// 阻塞信号屏蔽字
)
  1. 单个进程可监控的fd数量被限制,即能监听端口的大小有限
    – 一般来说这个数目和系统内存关系很大,具体数目可以cat /proc/sys/fs/file-max察看。32位机默认是1024个。64位机默认是2048

  2. 对socket进行扫描时是线性扫描,即采用轮询的方法,效率较低
    – 当套接字比较多的时候,每次select()都要通过遍历FD_SETSIZE个Socket来完成调度,不管哪个Socket是活跃的,都遍历一遍这会浪费很多CPU时间。如果能给套接字注册某个回调函数,当他们活跃时,自动完成相关操作,那就避免了轮询,这正是epoll与kqueue做的

  3. 需要维护一个用来存放大量fd的数据结构,这样会使得用户空间和内核空间在传递该结构时复制开销大


  • sigset_t 为信号集,未决(是否未决)和阻塞(是否阻塞)标志都可以用它来表示
  • 阻塞的信号将处于未决状态,无法递达
  • 忽略则是信号到达后的一种处理

3.2 poll

  • 解决1024一下客户端使用select很合适
  • 如果客户端过多,select是轮询模型会大大降低服务器响应效率,不应在select 上投入更多精力
  • 不在局限于1024个文件描述符
  • 谁有问题就举手回答,不用去提问(轮询或阻塞)
  • 如果不在监控某个文件描述符,可以吧pollfd中fd设置为-1, poll不在监控此pollfd

它没有最大连接数的限制,原因是它是基于链表来存储的,但是同样有一个缺点:

  1. 大量的fd的数组被整体复制于用户态和内核地址空间之 间,而不管这样的复制是不是有意 义。
  2. poll还有一个特点是“水平触发”,如果报告了fd后,没有被处理,那么下次poll时会再次报告该fd
1
2
3
4
5
6
7
8
9
10
11
int poll(
struct pollfd *fds,
nfds_t nfds,
int timeout
)
struct pollfd{
int fd; //文件描述符
short event; //监控的时间
short revent; //监控中满足条件返回的事件
}

3.3 epoll

  • 提高大量并发连接,只有少量活跃 cpu利用率
  • 复用文件描述符集合来传递结果, 而不用每次等待事件发生时必须重新准备要被监听的文件描述符集合
  • 获取事件的时候,无序遍历整个被监听的文件描述符集合, 只需遍历哪些被内核IO事件异步唤醒而加入Ready队列的文件描述符集合
  • epoll的优点:
  1. 没有最大并发连接的限制,能打开的FD的上限远大于1024(1G的内存上能监听约10万个端口);
  2. 效率提升,不是轮询的方式,不会随着FD数目的增加效率下降。只有活跃可用的FD才会调用callback函数;即Epoll最大的优点就在于它只管你“活跃”的连接,而跟连接总数无关,因此在实际的网络环境中,Epoll的效率就会远远高于select和poll。
  3. 内存拷贝,利用mmap()文件映射内存加速与内核空间的消息传递;即epoll使用mmap减少复制开销。

电平触发(Level Triggered)

  • selet/poll采用这种方式
  • epoll()

边沿触发(Edge Triggered)

  • epoll()
  • 使得用户空间有可能缓存IO状态,减少epoll_wait/epoll_pwait调用
坚持原创分享,您的支持将鼓励我继续创作