服务器编程之路:进无止境(上)

首先不好意思,盗用了福特汽车的广告语,呵呵。

今天想在这里探讨一下高性能服务器(server)编程的一些通用技术(或者说是思想)。编程技术发展至今,高性能服务器编程领域仍然是C语言的菜。而C语言在服务器编程中的技术,也不断在实践中提高,正暗含我们的题目。

有基础的初学者写的第一个基于TCP的服务器程序,想必大概是这样的:

while (1)

{

listen();                      // TCP套接字监听

fd = accept();          // 接受远端连接

create_thread(fd);        // 把这个连接扔到一个新开的线程里进行业务处理

}

现在我们知道,这样的服务器性能是有问题的。每接受一个连接,就要开一个线程进行处理,假设这个服务器有1000个并发,就需要同时有1000条线程。而OS调度进程或线程是有代价的,调度1000个线程,OS是很累的,平白无故耗了很多CPU在线程切换上,非常不划算。

虽然性能上不理想,但上面这第一版服务器程序结构上还是合理的。一个线程服务一个连接,线程相当于一个沙盒,里面怎么处理,都不会影响其他连接。所以对于性能要求不高的场景,用这种逻辑的程序简单方便。实际中,apache的http服务就是按照这种逻辑实现的。

但码农们永远不会满足于这点性能的,这也不符合高性能服务器的要求。现在的服务器往往要求上万的并发度(意味着同时有上万条连接),这就不能靠无限扩张线程数来实现了。所以问题转化为:如何使用有限的线程数,服务无限的连接数?

主流的,并早已被大家接受的方案是这样的:

1.        使用IO复用机制(如select/epoll),把所有已连接的套接字监听起来。

2.        当某个套接字发生网络事件(比如有数据到来),回调给使用者。

3.        回调所在的线程,是从一个有限大小的线程池中取出来的。

这即所谓的“事件模型”。所有处理不是顺序的,而是由事件触发,在回调中处理。

开源代码中,比较有名的库libevent,干的就是这个事情。在我们公司内,主要用在设备的netframework,软件线libdsl里面的DEngine组件,互联网团队使用的litepi库中的IOEngine,都是这个思路。

看起来很美是不是?至少性能上解决了“使用有限的线程数,服务无限的连接数”这个命题。但实际上,这个命题隐含了两个预设条件,如果使用者不能很好地理解这种模式的运行原理,就免不了要踩雷。

预设条件1:根据抽屉原理,一旦连接数大于线程数,必然存在一条线程服务多个连接的情况。因此,连接不再有一个干净的运行沙盒。如果在处理一个连接时造成阻塞,就会阻塞这个线程,从而把该线程上的其他连接也阻塞了。

预设条件2:事件模型下,事件有可能被不同的线程回调。而正常情况下,一个连接的多个事件必然共享这个连接的数据,因此这些数据就会存在多线程竞争问题。使用者必须意识到同一个连接的处理,实际是在多线程中的,这往往与直觉相悖。

关于上面两个预设条件,可以多说两句:

关于条件1,直观的打个比方:好比打地鼠游戏,冒出一个地鼠你就得打。但万一你把一只地鼠打坏了(卡在洞口缩不进去了),那这整台游戏机就坏了。。。

关于条件2,如果能够将同一个连接的所有事件都固定在一个线程中回调,就能避免多线程竞争的问题了。这种模式称为“非对称”处理,相对的,前一种称为“对称”处理。非对称处理的缺陷在于无法均衡使用各条线程。但是对于大并发(并发上万)的场景,数量一大,就能达到统计平均,这种缺陷也就不存在了。所以这也是一个取舍的问题,netframework和DEngine是对称处理,IOEngine就是非对称处理,它大大简化了使用者的编程。

好了,我们给出第二版的服务器程序,采用了“事件模型”,它是不是完美了呢?性能上,也许是;但在使用上,绝对不是。最根本的一个问题,它违反了人类做事的直觉。因为一般人,就算是水平很高的码农,干事情也喜欢一件事情顺序干到底(愚蠢的人类啊)。正常人盖房子肯定是:我要搬砖,我要砌墙,我要粉刷,完工!但是事件模型却要求:来来来,你去搬砖;来来来,你去砌墙;来来来,你去粉刷;好了,房子盖好了。人无法控制程序,反而被程序驱使,算不算悲哀呢?

至此,我们抛出第二个命题:有没有满足命题一(使用有限线程服务无限连接)的同时,又能让我们顺序地、同步地写程序?

答案当然是:有的!

(未完待续)

时间: 2024-05-29 14:17:36

服务器编程之路:进无止境(上)的相关文章

服务器编程之路:进无止境(下)

(接上文) 为了找到第二个命题的解决方法,我们可以再回过头来看看本文中第一版的服务器程序.前面也说了,第一版程序的问题在于,一条线程服务一个连接,而OS切换线程的开销很大,所以造成性能上不去.但第一版程序绝对是愉快的顺序编程.如果我们想保留顺序编程,那应该怎么克服性能方面的缺陷呢? 问题被直接导向为:既然OS调度线程很吃力,那是否存在一种"用户态线程",由程序自己调度,让OS一边玩儿去? 先抛出答案,所谓的"用户态线程",我们一般的实现就是"协程(coro

linux高性能服务器编程之poll

一.概述: 和select不同的是,poll使用一个pollfd来指向所要监听的fd,事件,返回事件.(pollfd下面详细讲.) 并且poll没有最大的文件描述符数量的限制,是自己定义一个pollfd数组来实现的. 它的缺点和select差不多,即 (1)每次调用poll,都需要把fd集合从用户态拷贝到内核态,这个开销在fd很多时会很大(2)当要确定一个文件描述符的状态时,都需要在内核遍历传递进来的所有fd,这个开销在fd很多时也很大 二.poll介绍篇:  int poll(struct p

转:PHP并发IO编程之路

并发IO问题一直是服务器端编程中的技术难题,从最早的同步阻塞直接Fork进程,到Worker进程池/线程池,到现在的异步IO.协程.PHP程序员因为有强大的LAMP框架,对这类底层方面的知识知之甚少,本文目的就是详细介绍PHP进行并发IO编程的各种尝试,最后再介绍Swoole的使用,深入浅出全面解析并发IO问题. 多进程/多线程同步阻塞 最早的服务器端程序都是通过多进程.多线程来解决并发IO的问题.进程模型出现的最早,从Unix系统诞生就开始有了进程的概念.最早的服务器端程序一般都是Accept

PHP并发IO编程之路

并发IO问题一直是服务器端编程中的技术难题,从最早的同步阻塞直接Fork进程,到Worker进程池/线程池,到现在的异步IO.协程.PHP程序员因为有强大的LAMP框架,对这类底层方面的知识知之甚少,本文目的就是详细介绍PHP进行并发IO编程的各种尝试,最后再介绍Swoole的使用,深入浅出全面解析并发IO问题. 多进程/多线程同步阻塞 最早的服务器端程序都是通过多进程.多线程来解决并发IO的问题.进程模型出现的最早,从Unix系统诞生就开始有了进程的概念.最早的服务器端程序一般都是Accept

linux高性能服务器编程之epoll

一.概述: epoll是多路复用的一种,但它比select和poll更加高效.具体体现在以下几个方面: (1).select能打开的文件描述符是有一定限制的,默认情况下是2048,这对应那些大型服务器来说h是不足的.但 epoll则没有这个限制,它所支持的fd上限是最大可以打开文件的数目,这个数字一般远大于2048,举个例子,在1GB内存的机器上大约是10万左 右,具体数目可以cat  /proc/sys/fs/file-max察看,一般来说这个数目和系统内存关系很大. (2).因为文件描述符是

平板点餐软件---记我的Android编程之路

多次吃饭时看见平板点餐,发现自己原来对移动编程还一无所知,于是从2013年便开始买书上网开始学习,Eclipse开发环境建好了,又在书本上实验了几个用例,但对于编写一套点餐软件却毫无头绪,一天网上(好像是EOE)找到陈江源大侠分析的一个实例,导入后居然可用,但遗憾的是只是个单机版无实用意义. 为了实现网络功能版的点餐系统,我开始了艰难之旅. 首先是学习J2EE,安装My sql ,建立一个Tomcat WEb服务器,使用Servlet处理App发来的消息,这时我发现找到一本好书<Andorid核

编程之路

转自:http://blog.csdn.net/banketree/article/details/39995209 技术永无止境,编程方向现在越来越广了,你还在迷茫地选择方向么? 本篇将简单描述编程各个方面,以及编程之路会遇到的生活问题. 问题与解答 1.编程分为几个阶段? 高手箴言中把编程分为六个阶段:1.熟练地使用某种语言(java).2.精通基于某种平台的开发(Android).3.深入地了解某个平台系统的底层(Android 底层).4.直接在平台上进行比较深层次的开发(Android

拼命三狼,我的编程之路!

大家好,先介绍一下自己吧,我叫小飞,我目前在阿里巴巴集团做后台开发偶尔也会写一下前端的东西. 有很多人在当初选择编程之路的时候很是迷茫纠结,或者家里的反对,令自己很是困扰,但是我很庆辛我自己的当初的选择那就是来到这一条永远学习的道路,换句话说有可能是一辈子学习新的知识,下面的就是我所经历过难忘的岁月: 从高考开始说起吧,那时我高中毕业没有考到自己理想的学校,在这里我相信有很多的朋友也是如此,当时很是失望迷茫,在暑假的几个月里天天都时在考虑该不该去上大学,说实话在那几个月里很不是滋味,上面顶着父母

ZSTU我的编程之路

浙江理工大学我的编程之路答案 语言:C/C++ 推荐编译器:Dev-C++ / CodeBlocks 严禁直接复制提交 作弊行为将会被ACM校队禁止入队 作者:JokerNoCry 1000: 送分题-A+B Problem #include <iostream> using namespace std; int main (){ int a,b; cin>>a>>b; cout<<a+b; } 零基础学C/C++ oj 题目1001--1199 1001: