免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
1234下一页
最近访问板块 发新帖
查看: 19851 | 回复: 32
打印 上一主题 下一主题

POE学习笔记 [复制链接]

论坛徽章:
0
跳转到指定楼层
1 [收藏(0)] [报告]
发表于 2007-03-13 17:53 |只看该作者 |倒序浏览
在程序需要同时处理大量连接的情况下,比如服务器程序、spider程序等,一般可以采用多进程、多线程和非阻塞IO三种方式。我自己编程只喜欢用非阻塞IO。在C下面有libevent库可以用,相比较POE是一款高端产品,刚开始有一点摸不着边际,熟悉之后感觉还是很贴心的。
    POE主要分以下几个组件Kernel、Session、Wheel、Filter、Driver,还有更高级的Component组件,不过基本上是前面几种组件的组合。Kernel是POE核心,内部实现了IO读写信号回调等处理,简单应用程序与Kernel交互并不多。Session是一个处理线程,比如一个服务器程序每一个客户端连接就应该对应于一个Session,同理Spider程序中对于每一个web服务器的连接也应该对应于一个Session,一个应用程序可以有很多Session。Wheel、Filter、Driver是对底层IO的封装。
    来看一个简单的客户端的例子:
  1. use warnings;
  2. use strict;

  3. use POE;
  4. use POE::Wheel::SocketFactory;
  5. use POE::Wheel::ReadWrite;

  6. POE::Session->create
  7.         ( inline_states =>
  8.                 { _start => \&start,
  9.                   connected => \&connected,
  10.                   flushed => \&flushed,
  11.                 }
  12.         );

  13. POE::Kernel->run;       
  14.        
  15. sub start {
  16.         print "_start\n";
  17.         my $wheel = POE::Wheel::SocketFactory->new
  18.                 ( RemoteAddress => 'localhost',
  19.                   RemotePort => 8000,
  20.                   SuccessEvent => "connected",
  21.                   FailureEvent => "_stop" ,
  22.                 );
  23.         $_[HEAP]->{wheel} = $wheel;
  24. }

  25. sub connected {
  26.         print "connected\n";
  27.         my ($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0];
  28.         my $wheel = POE::Wheel::ReadWrite->new
  29.                 ( Handle => $socket,
  30.                FlushedEvent => 'flushed',
  31.                 );
  32.         $heap->{wheel} = $wheel;
  33.         $wheel->put("hello server");
  34. }

  35. sub flushed {
  36.         print "flushed\n";
  37.         delete $_[HEAP]->{wheel};
  38. }

复制代码

    打开两个终端,在其中一个输入nc -l -p 8000,另一个终端中执行上面的程序,可以看到在nc终端中输出了hello server,在客户端终端中输出了_started、connected、flushed三行信息。
        这个程序基本上可以分为三部分
        1) POE::Session->create,创建一个Session。
        2) POE::Kernel->run, 启动框架消息分发。
        3) sub start ... 定义各种状态的回调函数。
在创建一个Session时,最重要的参数是inline_states,指定各种状态的回调函数。其中以_字符开始的状态是POE系统定义状态,其他是应用程序自定义状态。POE启动后就会自动将系统中每一个Session的状态设为_start,因此_start对应的处理函数就会被调用,因此下面的代码:
  1. use POE;

  2. POE::Session->create
  3.         ( inline_states =>
  4.                 { _start => sub { print "hello world\n"; }
  5.                 }
  6.         );
  7.        
  8. POE::Kernel->run;
复制代码

会直接打印hello world后退出。
    接着看上面的例子中的_start状态的处理函数,其中的代码
      my $wheel = POE::Wheel::SocketFactory->new
   创建了一个Wheel::SocketFactory,这个Wheel会根据你指定的参数去连接远程的服务,在连接完成后,自动触发SuccessEvent参数指定的状态(上面的程序是connected状态)。
    在connected状态的处理函数中,
      my $wheel = POE::Wheel::ReadWrite->new
    创建了一个Wheel::ReadWrite,在构造函数中你可以指定可读、出错和所有数据已经发送三种情况的对应状态,然后你就可以在对应状态的处理函数中处理各种情况了。
    上面的程序通过指定FlushedEvent,标明了当所有数据被发送后这个Session会被触发到flushed状态。
   (简单总结一下,对应Wheel的参数一般是当某种情况发生时,所属的Session的应该被置为什么状态,而Session的参数主要是各种状态的处理函数。)
    在各个回调函数中,你可以通过@_[KERNEL]得到系统Kernel对象,通过@_[HEAP]得到Session相关数据对象(比如在服务器程序每个Session中就会保留这个客户端的相关信息,地址用户名等),其他不同的处理函数得到的额外参数个数不同,分别可以通过@_[ARG0]、@_[ARG1]、@_[ARG2]得到。比如connected的处理函数就通过@_[ARG0]得到了SocketFactory创建的socket对象。
    下面这一据代码:
      $heap->{wheel} = $wheel;
    有着特殊的意义,当一个Wheel被创建后,应用程序必须把它保存在某处,否则当离开作用域对象被注销后,所有的功能都无法实现了。保存在Session相关数据HEAP中是一个非常自然的选择。类似于flushed处理函数中的代码:
      delete $_[HEAP]->{wheel};
    如果不执行这一句,Wheel就永远存在,程序也就无法退出了。
    下面给出一个简单POE应用程序的框架代码:
  1. use warnings;
  2. use strict;

  3. use POE;
  4. use POE::Wheel::Somewheel;

  5. POE::Session->create
  6.         ( inline_states =>
  7.                 { _start => sub {
  8.                         # do something initial
  9.                         # new some Wheel
  10.                         # put the wheel in HEAP
  11.                   },
  12.                   state_1 => sub {
  13.                           # handle state 1
  14.                           # new some other Wheel
  15.                   },
  16.                   state_2 => sub {
  17.                     # handle state 2
  18.                   },
  19.                 }
  20.         );

  21. POE::Kernel->run;
复制代码

论坛徽章:
0
2 [报告]
发表于 2007-03-13 17:55 |只看该作者
顶,先占位

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
3 [报告]
发表于 2007-03-13 18:11 |只看该作者
其实 POE 不光可以用来作为网络服务器框架。
事实上它是一个包含了很多现成的组件(包括网络组件)的自动机。
凡是可用有穷自动机算法描述的应用,都可以用 POE。
而且 POE 是纯 Perl 实现,因此不用担心会依赖什么。它什么都不依赖。移植性特别好。
另外,用过 PDK 的人都知道,纯 Perl 实现的模块是最好打包的了。

下面给出一个简单的例子,就当是 Hello world 吧。
  1. D:\MoChou\poetut>cat tpoe.pl
  2. #!/usr/bin/perl

  3. use warnings;
  4. use strict;
  5. use POE;

  6. POE::Session->create (
  7.     inline_states =>
  8.     {
  9.         _start => \&session_start,
  10.         _stop  => \&session_stop,
  11.         count  => \&session_count,
  12.     }
  13. );

  14. print "启动 POE 内核...\n";
  15. POE::Kernel->run();
  16. print "POE 内核运行结束。\n";
  17. exit;

  18. sub session_start {
  19.     print "Session 启动。Session ID = ", $_[SESSION]->ID, "\n";
  20.     $_[HEAP]->{count} = 0;
  21.     $_[KERNEL]->yield("count");
  22. }

  23. sub session_stop {
  24.     print "Session 停止。Session ID = ", $_[SESSION]->ID, ".\n";
  25. }

  26. sub session_count {
  27.     my ( $kernel, $heap ) = @_[ KERNEL, HEAP ];
  28.     my $session_id = $_[SESSION]->ID;

  29.     my $count = ++$heap->{count};
  30.     print "数数 $count\n";

  31.     $kernel->yield("count") if $count < 10;
  32. }

  33. D:\MoChou\poetut>tpoe
  34. Session 启动。Session ID = 2
  35. 启动 POE 内核...
  36. 数数 1
  37. 数数 2
  38. 数数 3
  39. 数数 4
  40. 数数 5
  41. 数数 6
  42. 数数 7
  43. 数数 8
  44. 数数 9
  45. 数数 10
  46. Session 停止。Session ID = 2.
  47. POE 内核运行结束。

  48. D:\MoChou\poetut>
复制代码

论坛徽章:
0
4 [报告]
发表于 2007-03-14 10:26 |只看该作者
比较讨厌的是这东西无法通过PDK直接进行编译!

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
5 [报告]
发表于 2007-03-14 10:29 |只看该作者
原帖由 战鹰 于 2007-3-14 10:26 发表
比较讨厌的是这东西无法通过PDK直接进行编译!

呵呵。
那是因为你对 PDK 理解不深,没法自行解决这个问题。
POE 是最好打包的模块了。因为它是纯 Perl 实现的(难以想象)。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
6 [报告]
发表于 2007-03-14 10:31 |只看该作者
POE 里有好多模块都是动态加载的,而 PDK 没法检测到动态加载的模块并包含进去,因此需要解决一下这个问题。
我的解决方法就是,PDK 打包以后,运行时如果缺什么模块,就把那个模块显式地 use 一下。这样问题就解决了。
当然也可以通过配置 perlapp 的工程文件,不过我不想过多地研究 PDK。

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
7 [报告]
发表于 2007-03-14 10:32 |只看该作者
比如我那个决战文本客户端里,就有这么一段代码:

  1. # 加载 POE 以及本程序中所使用到的组件
  2. # 之所以要写这么多是因为有些动态加载的模块 PerlAPP 识别不到,
  3. # 编译后缺少东西不能正常运行,所以必须得写明了。
  4. use POE;
  5. use POE::Session;
  6. use POE::Loop::Select;
  7. use POE::Wheel::SocketFactory;
  8. use POE::Wheel::ReadWrite;
  9. use POE::Driver::SysRW;
  10. use POE::Filter::Line;
  11. use POE::Filter::Stream;
  12. use POE::Resource::Aliases;
  13. use POE::Resource::Events;
  14. use POE::Resource::Extrefs;
  15. use POE::Resource::FileHandles;
  16. use POE::Resource::SIDs;
  17. use POE::Resource::Sessions;
  18. use POE::Resource::Signals;
  19. use POE::Resource::Statistics;
  20. use POE::Resource::Controls;
  21. use POE::Wheel;
复制代码

论坛徽章:
0
8 [报告]
发表于 2007-03-14 13:22 |只看该作者
Wheel:数据传送带

对于应用程序而言,输入输出是一个非常重要而且耗时的部分。通过使用Wheel,应用程序可以方便地监控IO事件并简化对输入输出操作的编写。对于Socket通讯程序来说,ListenAccept、SocketFactory、ReadWrite三种Wheel分别对应监听端口、建立连接和传送数据三种Socket操作。
1. ListenAccept
功用:处理一个监听端口的连接事件。
事件参数:AcceptEvent,其值在有客户连接时被触发。新建立的socket通过ARG0传送给处理函数。
2. SocketFactory
功用:建立到远程的连接。
事件参数:SuccessEvent,其值在连接完成时被触发。新建立的socket通过ARG0传送给处理函数。
3. ReadWrite
功用:读写数据。
事件参数:InputEvent,其值在当有数据到达时被触发。数据通过ARG0传送给处理函数。
        FlushedEvent,其值在当所有缓冲的数据被发送出去后被触发。你可以在这个事件的处理函数中发送新数据、关闭连接等。

下面通过一个具体的聊天室server/client程序来说明Wheel的使用。客户端接收从服务端传送过来的数据,同时监控标准输入,如果有用户输入则把内容传送给服务端;服务端监听新的连接,并把每一个客户端传送过来的数据广播到所有的客户端。客户端这儿使用了一个Wheel ReadLine,它可以监控终端输入。
chats服务端
  1. use warnings;
  2. use strict;

  3. use IO::Socket;
  4. use POE qw /Wheel::ListenAccept Wheel::ReadWrite/;

  5. # 创建监听Socket及处理Session
  6. POE::Session->create
  7.         ( inline_states =>
  8.                 { _start => \&start_server,
  9.                   new_connected => \&new_connected,
  10.                   client_input => \&client_input,
  11.                 }
  12.         );

  13. POE::Kernel->run;       

  14. sub start_server {
  15.         my ($kernel, $heap) = @_[KERNEL, HEAP];
  16.         my $server = IO::Socket::INET->new
  17.                 ( LocalPort => 8000,
  18.           Listen => 16,
  19.           Reuse  => "yes",
  20.         ) or die "can't make server socket: $@\n";
  21.         
  22.         $heap->{server} = POE::Wheel::ListenAccept->new
  23.                 ( Handle => $server,
  24.                   AcceptEvent => 'new_connected',
  25.                 );
  26. }

  27. sub new_connected {
  28.         my ($heap, $client) = @_[HEAP, ARG0];
  29.         my $wheel = POE::Wheel::ReadWrite->new
  30.                 ( Handle => $client,
  31.                   InputEvent => 'client_input',
  32.                 );
  33.         # 系统中每个wheel的ID是唯一的
  34.         $heap->{client}->{ $wheel->ID } = $wheel;
  35. }

  36. sub client_input {
  37.         my ($heap, $input, $wid) = @_[HEAP, ARG0, ARG1];
  38.         # 广播数据。如果愿意,可以屏蔽掉$wid,即发送消息的客户端
  39.         map { $heap->{client}->{$_}->put( $input ) } keys %{$heap->{client}};
  40. }
复制代码

chatc客户端
  1. use warnings;
  2. use strict;

  3. use IO::Socket;
  4. use POE qw /Wheel::SocketFactory Wheel::ReadWrite Wheel::ReadLine/;

  5. POE::Session->create
  6.         ( inline_states =>
  7.                 { _start => \&start_chat,
  8.                   connected => \&connected,
  9.                   connect_fail => \&connect_fail,
  10.                   server_input => \&server_input,
  11.                   user_input => \&user_input,
  12.                 }
  13.         );
  14.        
  15. POE::Kernel->run;

  16. sub start_chat {
  17.         my $wheel = POE::Wheel::SocketFactory->new
  18.                 ( RemoteAddress => 'localhost',
  19.                   RemotePort => 8000,
  20.                   SuccessEvent => "connected",
  21.                   FailureEvent => "connect_fail",
  22.                 );
  23.         $_[HEAP]->{server} = $wheel;
  24. }       

  25. sub connected {
  26.         my ($kernel, $heap, $socket) = @_[KERNEL, HEAP, ARG0];
  27.         my $wheel = POE::Wheel::ReadWrite->new
  28.                 ( Handle => $socket,
  29.                   InputEvent => "server_input",
  30.                   ErrorEvent => "error_happened",
  31.                 );
  32.         $heap->{server} = $wheel;
  33.        
  34.         my $console = POE::Wheel::ReadLine->new
  35.                 ( InputEvent => 'user_input'
  36.                 );
  37.         # 告诉ReadLine监控终端
  38.         $console->get( 'input your message, bye to quit: ');
  39.         $heap->{console} = $console;
  40. }

  41. sub connect_fail {
  42.         delete $_[HEAP]->{server};
  43. }

  44. sub server_input {
  45.         my ($heap, $input) = @_[HEAP, ARG0];
  46.         # 如果使用print "$input\n"会搞乱终端
  47.         $heap->{console}->put( $input );
  48. }

  49. sub user_input {
  50.         my ($heap, $input) = @_[HEAP, ARG0];
  51.         if ($input =~ /(quit)|(exit)|(bye)/i) {
  52.                 delete $heap->{server};
  53.                 delete $heap->{console};
  54.                 return;
  55.         }
  56.         # 发送到服务端
  57.         $heap->{server}->put( $input );
  58.         # 继续监控终端
  59.         $heap->{console}->get( 'input your message, bye to quit: ');
  60. }
复制代码

论坛徽章:
1
2015年辞旧岁徽章
日期:2015-03-03 16:54:15
9 [报告]
发表于 2007-03-14 13:25 |只看该作者
Wheel::ReadLine 在 windows 下好用不?

论坛徽章:
0
10 [报告]
发表于 2007-03-14 13:44 |只看该作者
原帖由 flw 于 2007-3-14 13:25 发表
Wheel::ReadLine 在 windows 下好用不?


查了资料好像是不支持,它使用select监控终端。而win32的select只能用在socket上。
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

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

清除 Cookies - ChinaUnix - Archiver - WAP - TOP