从一个Perl程序看网络服务器设计
简单优化下其代码,并写了个客户端脚本进行测试。
服务器端代码如下:
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 . “ ”;
$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() . “ ”;
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 ”;
}
sub random { # 该函数产生随机字串
my @x=(0..9,’a’..’z,’A’..’Z);
return 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方式,将数据转发给客户端。
补充:综合编程 , 其他综合 ,