Chinaunix

标题: 从一个Perl程序看网络服务器设计 [打印本页]

作者: 兰花仙子    时间: 2010-12-08 14:39
标题: 从一个Perl程序看网络服务器设计
本帖最后由 兰花仙子 于 2010-12-08 14:41 编辑

看到这篇文章及评论,觉得挺有意思的:
http://www.perlfect.com/articles/select.shtml

简单优化下其代码,并写了个客户端脚本进行测试。
服务器端代码如下:
  1. use strict;
  2. use IO::Socket;
  3. use IO::Select;

  4. $|++;  # 因为print到终端,所以这里要打开autoflush
  5. my $s = IO::Socket::INET->new(LocalAddr => 'localhost',  # 创建一个侦听socket
  6.                               LocalPort => 1234,
  7.                               Listen    => 5,
  8.                               Proto     => 'tcp')
  9.         or die $@;

  10. my $read_set = new IO::Select();   # 创建一个IO::Select目标
  11. $read_set->add($s);   # 把上述侦听socket加入IO::Select的检查队列

  12. while (1) {   # 一个死循环
  13.    # IO::Select有一个静态select方法,第一个参数如果设置,表示检查可读的socket
  14.    # 该方法一直block,直到有可用的句柄返回
  15.    # 返回一个三参数列表,第一个参数表示可读的socket句柄集合(一个数组引用)
  16.   my ($rh_set) = IO::Select->select($read_set, undef, undef, undef);   

  17.   foreach my $rh (@$rh_set) {  # 遍历可读的socket
  18.      # 如果当前可读的socket等于侦听socket,那么说明有请求进来,应该及时accept
  19.      # accept后,把已建立连接的socket句柄加入检查队列
  20.      if ($rh == $s) {
  21.         my $ns = $rh->accept();
  22.         $read_set->add($ns);

  23.      # 使用sysread读取数据,每次读取32字节,并且只处理这32字节
  24.      # 如果客户端发送多于32字节的数据包,会分几次处理,如果几个客户端同时发送,处理过程是无序的
  25.      # 避免使用<>方式读取socket,因为<>面向行读取,perlio对它做了缓冲,IO::Select看不到这个缓冲
  26.      }else {
  27.         my $buf = undef;
  28.         if (sysread($rh,$buf,32)) {
  29.             print $rh->fileno, " ", $buf, " ";

  30.         # sysread的返回是读取的字节数量,如果返回0,则说明抵达文件末尾(EOF)
  31.         # 如果sysread返回0,那么说明客户端关闭socket,我们从检查队列里删除该句柄,同时关闭socket句柄
  32.         } else {
  33.             print "no more data, close socket " . $rh->fileno . "\n";
  34.             $read_set->remove($rh);
  35.             $rh->close;
  36.         }
  37.      }
  38.   }
  39. }
复制代码
客户端代码如下:
  1. use strict;
  2. use IO::Socket;

  3. my @childs;
  4.    
  5. for (1..3) {  # fork 3个子进程,同时发送数据

  6.    my $child = fork;
  7.    if ($child) {
  8.       push(@childs, $child);

  9.    } else {
  10.      # 创建到server的连接socket
  11.       my $sock=IO::Socket::INET->new(PeerAddr => 'localhost',
  12.                                PeerPort => 1234,
  13.                                Proto    => 'tcp') or die $@;

  14.       for (1..10) {  # 每个子进程里,发送10次数据
  15.           print $sock  random() . "\n";
  16.           select(undef, undef, undef, 0.25);  # 每发送一次,就休眠0.25秒
  17.       }

  18.       $sock->close or die $!;  # 发送完后关闭socket,并退出子进程
  19.       exit 0;
  20.    }
  21. }

  22. for (@childs) {  # 回收子进程
  23.     my $tmp = waitpid($_, 0);
  24.     print "done with pid $tmp\n";
  25. }   

  26. sub random {  # 该函数产生随机字串
  27.     my @x=(0..9,'a'..'z','A'..'Z');
  28.     join '',map {$x[int rand @x]} 1..49;  # 返回49字节长度的串
  29. }
复制代码
执行perl server.pl(server程序),然后开另一个窗口,执行perl client.pl(client程序)。
我们看到server端的输出结果如下:

4 cOfHmaz4jqOcq4hbQxskWlvky5jlKwq1 4 qGK1M87oW0vKOx3nF
5 FYK0Rs9SwLxsjNFL7XrWiJMx3h88ZY7s 5 a4ONslGOtZBcBOgMO
6 e4wgmzsci4WB3UivkFPliLm46ZfFs7LA 6 DWkuQMQgoGw4dHOYJ
4 FJ2iF4SgeQr6I9Vbvsn5tiNfhh11tSTN 6 uCVk1z2kmbMXPTZ8G1fFbKOVeejxOGLe 5 e7xGXDt4k6MVmCdgjnJ63M3YH6uU1Agn 4 6CB7N2SQgUw4OO2Ay
6 HCZkdVjYrRqTqdSay
5 JMu1vZoHVvy0D0H6X
5 4u0cyyu2nvqnMeY0hAByc6qTug4BLIiu 4 FxAWodnADSM1p5fmeEez1kgboMnfwHUe 5 ogNXiE7230MECflEc
4 EgIQ7pHaBfV37idWu
6 05Qwd0YqHgNxXzU7y11YLMFZO1fjfuNC 6 ELWMy3kB0bkBxerfX
5 4TaDdFw7WhQytVFdRyo5WXWrYSo2UcQp 5 gJxhbBG75U5WucEdY
4 HcEUt6rkaMaI7ydik3k7OnnYIhaOkDRV 4 LHLmQqXztMQTfMrsS
6 FluyG5QBSumM9CRTe7s03TQZ4U7veGLO 6 aMHwNO4zmjbB3wgxZ
5 5KUQ6g5wmLJbRXUD4qaDPccu9WBFomxj 4 BC99aM4p6vgOHigsf7eykCaexjXRBBGq 5 E8TBpDBJfdFmcWW2t
4 uKknnYpSIqG5QTKcz
6 mQRxJbUL8uT2afhC1H0GWkbMoTOtAc19 6 eGFuNm9D8X769mZ5v
5 e07X3FehkD7GJiSP9h5IoBYqUHQMCtmU 4 yl8GlUKXSjnyjAkPc0xSfHpWYo7SatTA 5 oX6WGyoTjha2shfX3
4 M4ootxHWVKQKTLRd1
6 UfsIZzlHy1rUJOKMUc77sh208BCFnkjd 6 j0IUANSAZ5Sveh1RF
5 mXVRK5814ynNVgufysEh5NxIB76rDTEG 4 ORlLTbJ4fMwvdFXtn2U0bY3y3KAPgGbP 5 2GuvbTnPZaDrbvoly
4 cUIPEOoLf82ona9mC
6 o6Va0kkgiS68lrnwvaeXcAxqOicYcJPm 6 Zw8YEyylbBQeBjpVn
5 4T0KAg5KfbMHwU1GUOLRPjBZX9r2dJf8 4 ekUpc4vhYRIy6mgdLuyzVbYYikduPfbN 5 8IkuLJp5r5AEthAkE
4 PIkpk0TUuw69vBsNm
6 7wCuCBogNz1Fur8dfIUuYuYPGkPKMPzM 6 OYW1CDyOKT3Nbehba
5 ZCkKCpHtBwGFJF1okkICdeYdd4B9EfbS 4 CF33tme565DJ6JDJW032dlzhPzBMNRg1 5 uYqMOw3OsRRkFaV59
4 5DZocMljuzCvmAZKM
6 seuRu9QWgzWulhxF4AF6bGVJ5wFl87gK 6 GG2pWJqnbaYQSmAfC
5 Uk43vlipGer54IReLpzqW0MCKR5haN0w 4 oS2MPG19UkKF1iP1XTaZFOVNb4HL8bEB 5 ayS4PUv5y1hgwK0AK
4 aVl0SCT7sDFhQfoGT
6 BWTXDObFHaHoKcmhI5ZkVNjf2pmSyees 6 XOfmwzgQb0y0if9Ee
no more data, close socket 4
no more data, close socket 5
no more data, close socket 6

输出是无序的,每次最多输出32字节。

总结:
IO::Select即多路复用实现无阻塞IO,它通过检查哪个句柄可读、哪个句柄可写,从而切换CPU资源处理读写及对应的事件。
IO::Select的本质是select,select受限于文件句柄的数量,所以才有poll出现。
但同样poll在句柄数量较多时(例如数千个),性能会严重下降,所以才有内核级的epoll出现。
纵观当今linux下的高效web服务器(例如nginx、lighttpd),都是基于epoll的事件驱动服务器,一时之间,似乎无epoll不成产品。

但epoll真的是万能吗?epoll能完全代替多进程/线程吗?很遗憾,答案是否。
nginx之类使用epoll,也就适合做反向代理之类的工作(当然,处理静态文件也可以,现在内核级的sendfile也很强大)。
它们自身并不运行大量的计算任务(比如数据库查询和处理),真正高负荷的计算,还得交给后端应用服务器去完成。
如果nginx自身也运行web服务,也查数据库,也执行复杂逻辑,那么光有epoll会怎么样?
一个慢的运算就会阻塞所有客户请求。所谓几万个并发、性能强劲,全都是浮云。
所以,将来Apache/Java/Rails等应用服务器,永远不会消失,它们所代表的进程/线程技术,会和epoll一样相辅相成、一直存在。

引用javaeye一个牛人的话,反过来说明Java/Rails等应用服务器,为什么需要前端的反向代理?
因为反向代理首先是一个代理,它代替客户端与应用服务器会话。
应用服务器处理数据往往很快,一个中等计算量的请求0.1 - 0.2秒就处理完。
但是,对于Internet客户端,由于网络原因,一个会话往返平均也需要1-2秒。
应用服务器不能将资源消耗在慢客户端的连接处理上,这样会导致大量进程/线程吊在那里。
所以,中间加一个反向代理服务器,有助于性能充分发挥。
应用服务器只需将数据通过局域网扔给反向代理,反向代理再采用它得力的epoll方式,将数据转发给客户端。

作者: toniz    时间: 2010-12-08 14:56

作者: pk984813    时间: 2010-12-08 16:58
好到仙子姐姐的帖子又学到东西
作者: wxlfh    时间: 2010-12-08 17:01
不错,好文,学习了。
作者: x9x9    时间: 2010-12-08 23:54
学习了~刚开始学socket,多谢仙子版主提供好文~
作者: 2gua    时间: 2010-12-09 08:16
仙子总是有好帖。
作者: leiing    时间: 2010-12-09 09:03
仙子姐姐很强大,学习了
作者: zang232    时间: 2010-12-09 12:47
一直用Java写Server程序,看见perl我彻底无语了,真简单啊,服!
作者: hitsubunnu    时间: 2010-12-09 14:05
认真阅读了 仙子的这篇文章

总结:
IO::Select即多路复用实现无阻塞IO,它通过检查哪个句柄可读、哪个句柄可写,从而切换CPU资源处理读写及对应的事件。


个人觉得,这种说发是不准确的
IO::Seclect不应该说是无阻塞的,准确的说法应该是IO multiplexing,对于每一个socket连接是非阻塞的 而对于用户进程是阻塞的 ,所以才有了后面的结论。

有兴趣的可以看一下 UNIX Network Programming 里面讲的5种 IO Models
    blocking IO
    nonblocking IO
    IO multiplexing
    signal driven IO
    asynchronous IO
作者: 兰花仙子    时间: 2010-12-09 15:18
IO::Seclect不应该说是无阻塞的,准确的说法应该是IO multiplexing,对于每一个socket连接是非阻塞的 而对于用户进程是阻塞的 ,所以才有了后面的结论。


IO::Select本来就是解决socket IO的阻塞问题。
对于用户进程是阻塞的 -- 这个当然如此,你见过有不阻塞的进程吗?
即使是多进程/多线程,也不能解决自身的阻塞问题。
作者: hitsubunnu    时间: 2010-12-09 16:03
我说了我的观点是 觉得你说的不够准确
IO非阻塞就是IO非阻塞  IO复用就是IO复用 不能放到一起说
IO复用是由2部分组成的 在socket连接部分是非阻塞的 在selcect部分是阻塞的,所以造成整个用户进程是阻塞的。

【你见过有不阻塞的进程吗?】
我说的是用户进程 没说进程。
作者: じ☆vedě鍶唸    时间: 2010-12-09 16:04
非常漂亮的设计 赞
作者: 兰花仙子    时间: 2010-12-09 16:47
在selcect部分是阻塞的


select本来就是个阻塞函数,它在执行时,会一直阻塞,除非监控到有可用句柄。

所以造成整个用户进程是阻塞的


这个我没明白。。前后有啥因果关系。
作者: shhgs    时间: 2010-12-10 08:32
楼主根本没摸到异步的门。

让flw教教你。他懂Haskell,知道laziness。知道怎么让异步程序不阻塞。
作者: 屠龙    时间: 2010-12-10 09:40
学习了,谢谢仙子姐姐!
作者: xti9er    时间: 2010-12-10 10:32
不错的文章
作者: kingwmj    时间: 2010-12-11 16:27
学习了
作者: huxk    时间: 2010-12-12 17:10
本帖最后由 huxk 于 2010-12-12 17:17 编辑

摘录原文两段话

1 Obviously this provides us with the advantage of always picking up a filehandle that will not block thus avoiding the possibility of delaying the entire program for one lazy filehandle just because it happened to be the first we picked at random. Still, it does not guarantee that the selected filehandle is the best choice, because we still don't know how much data can be read, or how qucikly it can take in data that we wrte to it. But it is definetly a big step forward from our initial program.

2 As we already mentioned earlier, this method does not guarantee progress as it only tests whether a handle is ready to respond to I/O. The question still remains, whether the handle we pick from the ready ones is the one that will respond faster to I/O, and how much data there is available for reading or how much data it is ready to receive. So it is still possible to block a bit after the point where we picked the handle. Also, we did not take into account the impact on performance that the actual processing of requests will have. We might just be printing incoming data to a file, but then again, each request might need heavy processing that would slow down the entire handle processing loop. But these are issues that must be considered in the context of the individual application.

这两段话已经解释得很明白 这就是为什么 select poll epoll被称为 伪异步 的原因吧 充分将CPU时间利用起来 当一组handle都是活跃分子的的时候 select 比 epoll效率高 但有handle数目限制 apache对此有解决办法 通过多进程

不知道perl是否支持windows的完成端口 那个可以直接取得IO结果
作者: 兰花仙子    时间: 2010-12-12 22:04
这两段话已经解释得很明白 这就是为什么 select poll epoll被称为 伪异步 的原因吧


其实原文,包括我自己,没有哪里说这就是异步IO。
某个SB非得往这上面扯。。
作者: 兰花仙子    时间: 2010-12-13 09:29
这个人我block了。。不过他PM的消息我还是贴一下。

shhgs 2010-12-12 22:26  NEW
PERLER SB根本不懂select/poll的异步本质。

看看twisted,领略一下什么是真正的异步通讯。

从10%降到2%,不是没道理的。人家Py 04年就已经完全产品化的东西,Perl到10年才能拿出一个demo。更可笑的是,一群SB在那里一遍high Perl很强大,一遍起哄,性能不行,所以Perl不需要。

人要脑残成什么程度才能像Perler这样SB+2B。

shhgs 2010-12-12 22:27  NEW
TO Perler SB 版主:

本来要跟贴的,既然不能发言,麻烦把我的短信贴到帖子里。

作者: x9x9    时间: 2010-12-13 09:42
这个人脑子里有明显的“教派”观念,这和迂腐没啥区别。不是就问题谈问题。
所有的语言还不是一样,我最早学python很久都没学好,这只能说我不适合学它,但是我从没说Python不好,有机会还是会慢慢学的。
这人如此仇视Perl,不知何故,难道和Nobel不设数学奖一辙乎?
作者: 黑色阳光_cu    时间: 2010-12-13 10:08
http://search.chinaunix.net/bbs. ... bs=1&forums=all

网络扫黄应该从炸毁央视大楼开始
软件根本不应该受版权法的保护
这么卖力地吹捧龙芯,反而让人怀疑你的用心!
龙芯居然5天时间就卖断货了,是骗局还是热卖?


建议举报
作者: wxlfh    时间: 2010-12-13 22:36
这人似乎在CSDN也有个诋毁Perl的帖子,比较偏激,呵呵。
作者: zhlong8    时间: 2010-12-14 09:32
有些人就是那么狭隘,这世界就这样没法改变,ruby 版那位也差不多, flw 也不必太冲动,无视就好
作者: efhilt    时间: 2010-12-14 23:00
Perler的狭隘简直可笑。

不去借鉴twisted已经实现的东西,反而以这个demo以偏盖全地认为select/poll无法做到真正的无阻塞。跟5年前相比,Perl现在的类库优势已经荡然无存了。这难道是偶然的?

5年前我就预言了Perl的衰弱。可是直到今天,还有很多Perler不承认。禁止别人发言,然后找一群小丑在那里讨伐,折算什么行径?Perl为什么挑骆驼作吉祥物?应该选鸵鸟。

最后,对Perler的素质表示鄙夷。这个我已经早就知道了。不过这里再次鄙夷一下。两个版主,不论男女,都是整天嘴里叼着一个生殖器。
作者: cinanine    时间: 2010-12-15 06:57
挺有意思
作者: gregorian    时间: 2010-12-15 10:00
个人感觉如果你只能看到perl的不好,说明你还没有掌握到它的精髓。
任何事物都有好坏2个方面的。说不出它的好,等于不知道它的怀。
作者: myfifi    时间: 2010-12-15 13:13
顶,兰花仙子!
几十年前,世人还预言:汽车的诞生,将导致自行车消失......

请问对吗?

gregorian 说的对

个人感觉如果你只能看到perl的不好,说明你还没有掌握到它的精髓。
任何事物都有好坏2个方面的。说不出它的 ...
gregorian 发表于 2010-12-15 10:00




perl、python、php、c/c++等等都能解决问题,用与不用自己选择。不要发这样的2B言论,某些人一个月总有那么“几天”。看不惯......
作者: scrit    时间: 2010-12-15 14:23
学习了,谢谢仙子!
作者: scrit    时间: 2010-12-15 14:25
学习了,谢谢仙子!
作者: linuxwt    时间: 2011-11-05 03:37
回复 25# efhilt
借用上面那位仁兄的话!你脑子里有明显的“教派”观念,这和迂腐没啥区别。你学的语言塑造了你的思考和认知这个世界的方式。对事物和概念的认识以及对如何表达它们都没有完整的视野!真的很悲哀...(不好意思说脏话了,下次不敢了,嘿嘿!)
作者: 笨鱼月色    时间: 2011-12-12 14:42
回复 25# efhilt


    本人一直在CU潜水较多,比较自私地吸取各位前辈的知识,今天特意申请一个ID回复您:语言本无孰好孰坏,关键看每个人的需要而已,我的工作很多处理各种文本各种数据的应用,perl对于我来说就是相当的好;有时候,你不喜欢某些东西,不是代表你可以去诋毁;
作者: 小perl    时间: 2011-12-12 15:31
仙子,怎么没有反应,
作者: dahe_1984    时间: 2013-01-10 16:30
回复 33# 小perl

perl在cmd下执行????

   
作者: redpig315    时间: 2013-01-10 22:51
好不容易看到仙子大人发表精神食粮,偶参上,求仙子大人多多宣传perl,回复 1# 兰花仙子


   
作者: 懒狒狒    时间: 2013-01-11 14:05
仙子姐姐,学习啦,
作者: 小perl    时间: 2013-01-29 20:20
是啊,好久没来了。。。回复 34# dahe_1984


   
作者: wenxin1234114    时间: 2013-08-02 12:43
$|++;  # 因为print到终端,所以这里要打开autoflush

$|什么意思,求解
作者: dahe_1984    时间: 2016-12-08 15:35
故地重游,满眼凄凉呀
作者: yakczh_cu    时间: 2016-12-09 08:54
本帖最后由 yakczh_cu 于 2016-12-09 08:55 编辑

my $ns = $rh->accept();

以后,有什么办法,把$ns 转发给另一个进程?




欢迎光临 Chinaunix (http://bbs.chinaunix.net/) Powered by Discuz! X3.2