Tell me why
为什么要写这个有关轮子代码的解析呢?如果是用 IO::Socket配合select_read等写的代码,一来需要自己处理客户socket的连接,二来还要整理传入的数据,如此这般使得代码量变得 很巨大,对其解析可能会需要很大的工作量,事实上我也大致写过很长的一个。而且这种具有普遍意义的代码任放在哪个平台上其中的内容大多是不会发生变化的, 因而很多地方都可整合起来。在另一个极端上来说,如果使用像POE::Component::Server::TCP的组件,虽然可以将代码量控制在一个 很小的范围,但是同时也隐藏了绝大多数的细节,对于刚刚接触POE的初学者,这无异于一个神奇的盒子。所以要用夹在中间的轮子代码,先有一个大致的概念, 之后再去深究其中的细节。
一拖n的结构
还是老样子,先把代码的大致结构讲一下。和大多数底层的POE程序差不多,一个session拖一屁股事件句柄。
POE :: Session -> create(
inline_states => {
_start => \& server_start ,
server_accepted => \& server_accepted ,
client_input => \& client_input ,
client_error => \& client_error ,
server_error => \& server_error ,
});
_start 事件建立了一个服务socket,再用ListenAccept轮子监听它。当发现有客户socket连接时,触发了server_accept事件。在 该事件中,我们使用ReadWrite轮子来监视客户输入,并将其返回给客户。如果发生意外,根据事故的制造者,分别交予client_error或者 server_error处理。其中server_error将销毁ListenAccept轮子,从而停止整个程序。
所以,不一样关键在于有了ListenAccept和ReadWrite两个轮子。前者用来监听客户socket连接,后者用来处理传入的数据并将它发送回去。这在很大程度上节约了代码量,使我们可以集中更多的注意力在业务逻辑上,而又不至于对于其中的具体实现一头雾水。
记忆点
对 于这两个轮子,需要牢记的有两点,首先是参数。ListenAccept需要至少三个,分别是Handle、AcceptEvent和 ErrorEvent。Handle是监视对象,AcceptEvent是接受连接时触发的事件,到处可见的ErrorEvent则用来处理错误。这三个 缺一不可,不然运行会发生错误,至少需要写一个空句柄。ReadWrite也差不多,只是InputEevnt代替了Listenaccept中的 AcceptEvent,用来处理客户输入。
第二点要注意的是ARG参数。可以这么认为,ARG0是与轮子具体功能相关的, ARG1则代表了轮子本身的ID。打个比方,因为ListenAccept是用来监听并接受客户连接的,那么ARG0就是被接受了的客户。由于对于轮子的 使用经常是跨事件的,所以必须保留对于轮子的引用。ID因其唯一性,所以可以在哈希表中作为键值以保存对轮子的引用。
测试
#!/usr/bin/perl
use warnings;
use strict;
use POSIX;
use IO :: Socket;
my $sock = IO::Socket::INET->new(
PeerHost => '127.0.0.1',
PeerPort => 12345,
) or die "can't connect to server: $@\n";
my $word = "hello.\nmy name is Jinhao.\nwlecome to POE.
a
\n";
$sock->send($word);
$buffer = '';
$sock->recv($buffer, POSIX::BUFSIZ);
print $buffer unless $buffer eq '';
$sock->shutdown(0);
客 户端还是在本地,应用了一个IO::Socket来发送一段带“\n”的数据,之后接受返回并打印结果。可以印证一点,ReadWrite轮子只有在发现 换行才起作用,也就是说如果发送的数据的最后不是以“\n”结尾的,那么打印结果会少一段。至于为什么没有发现数据结尾,我想可能跟操作系统有关系。