高性能网络IO有2个鲜明特征:
1、没有等待。当然这个特征必须做个界定,即任何在网络发生数据传输请求的时候,都能够马上满足。
2、没有内存拷贝。由于网络IO的数据量十分庞大,所以任何内存拷贝的存在将导致很大的性能损失。
在已知的 IO模型中,都必须存在这2个特征。我们以EPOLL为例,性能最高的是边缘触发模式。只有当缓冲区中存在数据时,应用层才会被通知。而对于内核来说,通 知的时候,本身只高知应用层,数据已经能够提供,但却没有告知数据的具体内容,因此,并没有内存拷贝行为。但是,由于内核和应用层之间,不存在数据共享的 途径,因此将数据从内核中,拷贝到应用层内存中,是必须存在的行为。
事件模型是利用EPOLL等高性能网络IO模型的最好方式,实际上他将消灭等待的时机延迟到最靠近最终使用数据的层次,从而降低了内存拷贝的可能性。但 是,我们也要看到,事件模型是无序的,也就是说,我们在事件模型中不能限定事件发生的顺序。打个比方,一个登录过程分为几个步骤:
1、C-->发送登录请求 --> S
2、S-->登录应答 -->C
3、C-->发送名称、密码 -->S
4、S-->验证结果-->C
这是一个很简单的过程,如果是阻塞模式的话,我们很简单的用几个过程:
1、send(登录请求)
2、recv(登录应答)
3、send(用户、密码)
4、recv(验证结果)
每次recv的时候,我们很显然是知道他一定会返回什么,比如recv(登录应答),如果不是,那么直接报告登录错误,然后退出。
可是事件模式中却不是这样。当你接收到一个登录应答时,就能确定要发送用户和密码吗?否!!!!因为你不知道是否已经发送过登录请求。也许有人会说,我没发登录请求,对方返回登录应答做什么呢?也许是对方的错误,但不管如何,你不能保证。
于是,状态机就被引入到这种事件模型中,来保证具备上下文关系协议中,如何正确运行。当我成功发送登录请求时,本地状态机被置为 LOGIN_REQUEST_SENT,只能当我接收到登录应答时,状态被重新置为LOGIN_USER_PWD_SENT。如此这般,就能保证这个过程 在事件模型被正确执行。
事件模型在某种情况下,却能完全摒弃状态机,比如HTTP服务器中。每个HTTP请求都会携带完整的信息,因此,没有必要考虑状态和上下文关系。
还有一种比较复杂的情况,却需要复杂的状态机,比如telnet协议。这个协议的状态变化很复杂,事件模型很难处理。