免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
查看: 24606929 | 回复: 24606929

从一个Perl程序看网络服务器设计 [复制链接]

论坛徽章:
0
发表于 2010-12-08 14:39 |显示全部楼层
本帖最后由 兰花仙子 于 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方式,将数据转发给客户端。

论坛徽章:
0
发表于 2010-12-08 14:56 |显示全部楼层

论坛徽章:
0
发表于 2010-12-08 16:58 |显示全部楼层
好到仙子姐姐的帖子又学到东西

论坛徽章:
1
未羊
日期:2014-09-08 22:47:27
发表于 2010-12-08 17:01 |显示全部楼层
不错,好文,学习了。

论坛徽章:
0
发表于 2010-12-08 23:54 |显示全部楼层
学习了~刚开始学socket,多谢仙子版主提供好文~

论坛徽章:
0
发表于 2010-12-09 08:16 |显示全部楼层
仙子总是有好帖。

论坛徽章:
2
巳蛇
日期:2014-10-26 22:38:12天蝎座
日期:2016-01-08 09:25:17
发表于 2010-12-09 09:03 |显示全部楼层
仙子姐姐很强大,学习了

论坛徽章:
0
发表于 2010-12-09 12:47 |显示全部楼层
一直用Java写Server程序,看见perl我彻底无语了,真简单啊,服!

论坛徽章:
0
发表于 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

论坛徽章:
0
发表于 2010-12-09 15:18 |显示全部楼层
IO::Seclect不应该说是无阻塞的,准确的说法应该是IO multiplexing,对于每一个socket连接是非阻塞的 而对于用户进程是阻塞的 ,所以才有了后面的结论。


IO::Select本来就是解决socket IO的阻塞问题。
对于用户进程是阻塞的 -- 这个当然如此,你见过有不阻塞的进程吗?
即使是多进程/多线程,也不能解决自身的阻塞问题。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP