- 论坛徽章:
- 0
|
本帖最后由 兰花仙子 于 2010-12-08 14:41 编辑
看到这篇文章及评论,觉得挺有意思的:
http://www.perlfect.com/articles/select.shtml
简单优化下其代码,并写了个客户端脚本进行测试。
服务器端代码如下:- 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端的输出结果如下:
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方式,将数据转发给客户端。 |
|