libevent深入浅出

引子

图1 一种简单的服务架构

在介绍libevent之前,首先抛出一个问题,如果让你设计一个高性能的服务架构,要求服务端能够处理多个客户端连接并响应客户端的请求,如图1所示,你会如何设计?

一个比较直观的想法是在服务端,每来一个客户端连接,即开启一个线程去处理并响应,这种设计的优点是简单易懂,缺点也很明显,如果客户端连接比较多,需要开启多个线程去处理,而操作系统开启线程是需要一定代价的,服务端在多个线程间切换也是比较消耗资源的;另外,多个线程操作临界资源时会带来锁竞争问题,当连接数比较多时竞争会非常激烈。那么接下来,我将介绍一种比较经典的处理方式,学习完之后,也许对于这个系统的设计会有不同的答案。

前置知识

在介绍之前,首先说明几个概念。

  1. I/O多路复用
    进程需要一种预先告知内核的能力,使得内核一旦发现进程指定的一个或多个I/O条件就绪(也就是说输入已准备好,或者描述符已经承接更多的输出),它就通知进程,这种能力称为I/O复用。IO多路复用是一种同步IO模型,实现一个线程可以监视多个文件句柄;一旦某个文件句柄就绪,就能够通知应用程序进行相应的读写操作;没有文件句柄就绪时会阻塞应用程序,交出cpu。多路是指网络连接,复用指的是同一个线程。

  2. 用户空间和内核空间
    现在操作系统都是采用虚拟存储器,那么对32位操作系统而言,它的寻址空间(虚拟存储空间)为4G(2的32次方)。操作系统的核心是内核,独立于普通的应用程序,可以访问受保护的内存空间,也有访问底层硬件设备的所有权限。为了保证用户进程不能直接操作内核(kernel),保证内核的安全,操心系统将虚拟空间划分为两部分,一部分为内核空间,一部分为用户空间。针对linux操作系统而言,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为内核空间,而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF),供各个进程使用,称为用户空间,如图2所示。这里还有一个比较重要的概念,叫DMA(Direct Memory Access直接存储器访问),它的作用是处理各种I/O,包括网络I/O和磁盘I/O。CPU是不会直接处理I/O的,这是因为CPU非常宝贵,而I/O是比较耗时的,如果CPU一直等待某一次I/O事件完成,会带来极大的浪费,且性能会急剧下降,因此需要一种机制能够完成I/O,并通知CPU,DMA即是这个角色。

在这里插入图片描述

图2 用户空间和内核空间

从图2中可以看到,对于一次I/O访问,数据会先被拷贝到内核空间中,然后才会拷贝到用户空间中供应用程序使用,所以在这个过程中会经历两个阶段(对应图2中的1,2两个步骤):

  1. 等待数据准备
  2. 将数据从内核空间拷贝到进程中

正是因为这两个阶段,linux系统中产生了两大类网络模式

  • 同步I/O
    • 阻塞I/O
    • 非阻塞IO
    • I/O多路复用
    • 信号驱动I/O(使用较少)
  • 异步I/O

同步I/O和异步I/O主要区别在于:
用户进程在发起I/O操作之后可以立刻去做其他事情,数据拷贝由硬件拷贝到内核空间、从内核空间拷贝到用户空间都不阻塞,这种就是异步I/O;两个步骤中有任何一步发生阻塞,就是同步I/O。

同步I/O又细分了很多模式,它们的区别是:

同步I/O模式 详细说明
阻塞I/O 在步骤1即阻塞
非阻塞I/O 步骤1不阻塞,需轮询;步骤2阻塞
I/O多路复用 与阻塞I/O一样,优点是可以处理多个连接

阻塞I/O典型代码(大部分socket接口都是阻塞型的,当然可以设置为非阻塞,以python语言为例,主要是代码简单清晰,其他语言类似)

## 客户端
import socket
sk = socket.socket()
sk.connect(('0.0.0.0', 9600))
while True:
    msg = input('>>>>')
    sk.send(msg.encode())
    if msg.lower() == 'quit':
        break
    ret = sk.recv(1024)
    if ret.lower() == 'quit':
        break
    print(ret.decode())
sk.close()
#########################################################################
## 服务端
import socket
# 默认TCP,参数可以不写
sk = socket.socket(family=socket.AF_INET, type=socket.SOCK_STREAM)  
sk.bind(('0.0.0.0', 9600))
sk.listen(1)
while True:
    conn, ip_addr =</
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值