Chinaunix首页 | 论坛 | 博客
  • 博客访问: 1721339
  • 博文数量: 171
  • 博客积分: 11553
  • 博客等级: 上将
  • 技术积分: 3986
  • 用 户 组: 普通用户
  • 注册时间: 2006-05-25 20:28
文章分类

全部博文(171)

文章存档

2012年(2)

2011年(70)

2010年(9)

2009年(14)

2008年(76)

分类:

2010-12-20 23:03:16

看到这篇文章及评论,觉得挺有意思的:


简单优化下其代码,并写了个客户端脚本进行测试。
服务器端代码如下:

use strict;
use IO::Socket;
use IO::Select;

$|++; # 因为print到终端,所以这里要打开autoflush

my $s = IO::Socket::INET->new(LocalAddr => 'localhost', # 创建一个侦听socket

                              LocalPort => 1234,
                              Listen => 5,
                              Proto => 'tcp')
        or die $@;

my $read_set = new IO::Select(); # 创建一个IO::Select目标

$read_set->add($s); # 把上述侦听socket加入IO::Select的检查队列


while (1) { # 一个死循环

   # IO::Select有一个静态select方法,第一个参数如果设置,表示检查可读的socket

   # 该方法一直block,直到有可用的句柄返回

   # 返回一个三参数列表,第一个参数表示可读的socket句柄集合(一个数组引用)

  my ($rh_set) = IO::Select->select($read_set, undef, undef, undef);

  foreach my $rh (@$rh_set) { # 遍历可读的socket

     # 如果当前可读的socket等于侦听socket,那么说明有请求进来,应该及时accept

     # accept后,把已建立连接的socket句柄加入检查队列

     if ($rh == $s) {
        my $ns = $rh->accept();
        $read_set->add($ns);

     # 使用sysread读取数据,每次读取32字节,并且只处理这32字节

     # 如果客户端发送多于32字节的数据包,会分几次处理,如果几个客户端同时发送,处理过程是无序的

     # 避免使用<>方式读取socket,因为<>面向行读取,perlio对它做了缓冲,IO::Select看不到这个缓冲

     }else {
        my $buf = undef;
        if (sysread($rh,$buf,32)) {
            print $rh->fileno, " ", $buf, " ";

        # sysread的返回是读取的字节数量,如果返回0,则说明抵达文件末尾(EOF)

        # 如果sysread返回0,那么说明客户端关闭socket,我们从检查队列里删除该句柄,同时关闭socket句柄

        } else {
            print "no more data, close socket " . $rh->fileno . "\n";
            $read_set->remove($rh);
            $rh->close;
        }
     }
  }
}


客户端代码如下:

 

use strict;
use IO::Socket;

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


   my $child = fork;
   if ($child) {
      push(@childs, $child);

   } else {
     # 创建到server的连接socket

      my $sock=IO::Socket::INET->new(PeerAddr => 'localhost',
                               PeerPort => 1234,
                               Proto => 'tcp') or die $@;

      for (1..10) { # 每个子进程里,发送10次数据

          print $sock random() . "\n";
          select(undef, undef, undef, 0.25); # 每发送一次,就休眠0.25秒

      }

      $sock->close or die $!; # 发送完后关闭socket,并退出子进程

      exit 0;
   }
}

for (@childs) { # 回收子进程

    my $tmp = waitpid($_, 0);
    print "done with pid $tmp\n";
}

sub random { # 该函数产生随机字串

    my @x=(0..9,'a'..'z','A'..'Z');
    join '',map {$x[int rand @x]} 1..49; # 返回49字节长度的串

}


执行perl server.pl(server程序),然后开另一个窗口,执行perl client.pl(client程序)。
我们看到server端的输出结果如下:



QUOTE:
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等应用服务器,为什么需要前端的反向代理?


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

chinaunix网友2010-12-22 14:35:22

很好的, 收藏了 推荐一个博客,提供很多免费软件编程电子书下载: http://free-ebooks.appspot.com