前一篇文章介绍了网络聊天室中的界面,回顾看这。asyncore模块封装了底层的异步操作(比如poll之类),使得我们能很方便使用异步socket。关于这个模块更多的介绍请看官方文档。当然,如果仅仅是介绍如何使用这个模块,那这篇文章就没什么意思了,所以这篇文章将更进一步,讲讲“为什么”,然后从源码的角度来理解这个模块。

1.CPU密集型和IO密集型

在讲为什么之前先来理解一个概念:CPU密集型和IO密集型

cpuio

进程将大部分时间花在CPU计算上则称为CPU密集型,将大部分时间花在IO上则称为IO密集型。

对于一个网络聊天室而言,当客户端发起一个连接请求(建立连接花费的时间较长)时,服务器就处于一个等待IO的阶段,连接建立好后,客户端发送数据给服务器,服务器收到消息后转发给其他客户端,这个转发的过程就是CPU计算阶段。显然服务器属于IO密集型,等待IO的时间要大大高于CPU计算的时间。

2.为什么使用异步socket

举两个很形象的例子:

  • 同步相当于打电话,在对方接听之前必须一直等待。
  • 异步相当于淘宝买东西,在淘宝上付完款后可以去做其他事,当快递来的时候再去取。

那为什么要在服务器端使用异步操作呢?从第一节中我们知道聊天服务器属于IO密集型,如果使用同步操作,那么意味着CPU要进行长时间无用的等待,并且在等待的时候不能把时间片分给其他进程,这样服务器整体的效率就大大降低(CPU总是在执行盲等而不能将时间片分给其他需要的进程)。如果使用异步IO操作的话,服务器进程在没有处理客户端请求(等待IO)时,操作系统可以将时间片分给其他进程,当客户端请求来临的时候,操作系统再唤醒服务器进程继续处理。这样服务器的效率将大大提高。

3.asyncore源码解析

查看源码可以看到,这个库下主要包括了几个全局函数和几个类,最主要的是dispatcher这个基类,这个类封装了异步socket操作,主要的全局函数和类中函数大致如下(省略具体代码):

loop函数开启了异步操作,loop中有一个map保存了需要进行异步操作的dispatcher实例对象obj(源码中将其称为channel),loop的调用流程大致如下:

dispatcher

loop函数中调用了poll,一旦有channel被激活就会进一步执行writable,readable来判断是否可读写,这两个函数实现很简单:

只是简单的返回了true,可以重写这两个方法来改变读写条件。如果可读的话就调用handle_read_event:

这个方法中首先判断是否已经是连接状态,未连接的话直接调用handle_accept来接受这个新连接,否则调用handle_read方法,handle_read方法是需要被重写的:

重写这个方法,自定义收到数据后需要进行的操作。handle_write的调用流程差不多,就不再重复了。

一般情况下在重写handle_read方法时要调用recv方法来获取数据:

如果数据接受完毕了就会调用handle_close关闭这条连接,并且从loop的map中删除这个channel。

理解了上面的内容后再来看看具体的操作代码,肯定会有一种豁然开朗的感觉:

4.实例代码

其中,在create_socket操作中最终会调用add_channel方法将这个dispatcher加入到loop的map中。

观看更多有关 的文章?

*

+
跳转到评论