谈谈网络游戏中的“网络”编程
????近段时间总是有不少人问我关于完成端口模型的一些资料,很多时候其实我很郁闷,为什么大家会选择使用完成端口呢?或者说很多时候他们竟为了使用完成端口,而使用WINDOWS作为网络游戏服务器平台,还一开口罗列出一堆使用WINDOWS平台的网络游戏案例。有些兄弟跟我一直争论“Windows?2003?Server很猛的,效率很高的”,这句话的正确性暂且不讨论,先说Windows?2003?Server多少钱一张授权?降低成本不就等于多赚钱嘛。放着免费且高效的Linux不用,篇篇要花钱买贵的?而且性能还低得一塌糊涂?
不要着急,放心,我说的是对的,下面来看看IBM的一些研究者对Linux和Windows下编程的性能比较。
首先来看看线程创建的速度比较:
600) this.width=600;this.Loaded=true}" border=0 name=UserImg Loaded="true"????当然,也许你会说,现在都用线程池了,都是在开始的时候创建好线程,然后运行的时候直接拿来用就好了,恩,那么再来看看SOCKET的速度。
600) this.width=600;this.Loaded=true}" border=0 name=UserImg Loaded="true"????不用脸红脖子粗,这个只是简单的测试,不过有小道消息说Windows在对于本地127.0.0.1地址传输的时候,速度会提高很多,那么到底是不是呢?我们来看看下面这张测试结果:
600) this.width=600;this.Loaded=true}" border=0 name=UserImg Loaded="true"????其中带叉叉的线就是在针对127.0.0.1这个地址的情况下的传输速度,看得出来,那个小道消息确实是真的,Windows2000在对于本机地址传输的时候确实要快很多。那么Windows和Linux相比,还有什么缺点呢?Windows在线程方面还有个特点,叫Thread?Creation?Decay,什么意思呢?就是指随着运行时间的越来越长,刚刚启动的时候创建线程的速度和启动了一段时间之后,线程的创建速度会不一样,这也就是为什么一台Linux服务器可以几年都不重启,而Windows操作系统则不行。我这里并不是否认Windows,我在这里只是想说,Windows系统由于它所提供的Built-in?GUI,导致内核在处理各个系统调用的时候,或多或少会有些损耗。
????好像有点跑题了,这些和网络都没什么关系,那么我们回过头来看网络编程,首先我们来讨论一下网络的根本,根本是什么?是IO。接收一些数据,发送一些数据,在C/S模式下,又根据接收发送行为的不同,而分成了服务器和客户端两种程序模型。那么究竟该如何处理这些IO呢?我们来看看目前几种常见的方法:
Blocking?Read-Write
????关于这个最常见的模型就是:
while?(size?=?read(fd,?buf,?max_size,?0)?)
ProcessMessage(buf,?size);
????用这种模型几乎无法处理面对多个用户连接的情况,因为在这种情况下,read的操作是阻塞的,会一直等到有消息到达才会返回。于是大家为了让这种模型适用于多个用户连接,就想了一个办法,那么就是:
Multi?Threading?Blocking?IO
????就是为每个用户连接,开启一个线程来跑上面的那个循环。但是,随着用户人数的增加,线程数量越来越多,虽然大部分时间都消耗在阻塞上,但是操作系统却为此付出了相当大的用户级线程间切换的代价。
IOCP?(I/O?Completion?Port)
????这就是很多人都津津乐道的完成端口,的确,在Windows平台下,它是最完美的关于IO的解决方案,不仅效率高,而且占用资源也相对较少,对于在Windows平台下编写网络相关的操作,用它肯定是没错了。其原理就类似车站的售票窗口一样,有固定,有限的几个窗口来处理所有的请求。
Asynchronous?IO
????那么什么又是AIO呢?这是一个和以上三者不同的一个概念,或者说只是一种定义,不像上面,都是特定的看得到的结构模型。而这里所谓的AIO的定义是为了使在更大限度上对操作系统IO带宽的利用,从而产生的一种高效率的IO处理结构。这只是一个定义,而事实上对AIO的实现是多种多样的。一般来讲,真正的AIO,也就是按照AIO的思想来实现的,我们叫真AIO,比如Windows下的WSAASync系列的API,和Linux下POSIX?AIO系列的API,其原理是在执行IO的时候立刻返回,然后让操作系统在处理完IO以后呼叫其回调函数。所以,对于用其他方法,达到AIO目的的方法,我们叫假AIO,因为假AIO从AIO的定义出发,到达的效果基本和真AIO一样,典型的方法就是select/poll/epoll配合非阻塞IO读写的来实现的假AIO。事实上呢,关于AIO的争论很多,Linux的内核对AIO的支持的实现方法也几乎在每个版本都有改动。有一些对AIO的PATCH甚至几乎和完成端口是一样的模型,但是仍然被称为是AIO。
????好,谈到这里,希望大家对目前一些主流的IO处理方式有所了解了,那么我们再来看看如何根据不同的需求来选择不同的IO处理方案。
????首先考虑到的显然是效率问题,就像刚刚在介绍IOCP时中说的一样,IOCP毫无疑问是任何情况下Windows来解决IO问题的最佳方案,那么Linux下呢?对于Linux下IO操作的选择应该有些取舍,其关键在于对select/poll/epoll配合非阻塞IO模拟的假AIO和POSIX系列的真AIO?API之间的取舍。
????在Linux?2.5版本之前,POSIX?AIO几乎没什么特有的优势,因此几乎所有的基于Linux的网络应用都使用的select/poll/epoll配合非阻塞模拟来实现高效的IO处理方案,但是在Linux?2.5的内核中,对AIO的支持改进非常大,从而导致使用POSIX?AIO的可能。但是缺点也是显而易见的,由于在内核中对AIO的支持是由和操作系统相关十分紧密一系列操作系统级的系统调用组成的。导致兼容性上会出现一定的问题,所以,这里的建议是,如果不太理解POSIX?AIO的,最好别贸然选择使用POSIX?AIO,而是老老实实的用select/poll/epoll配合非阻塞的IO处理方法吧,尽管两者之间的性能差别在目前来说还不算很大,但是我想随着不断更新的Linux内核,POSIX?AIO会变得越来越好。