<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>龙睿·LoRui</title>
	<atom:link href="http://www.lorui.com/feed" rel="self" type="application/rss+xml" />
	<link>http://www.lorui.com</link>
	<description>欲速则不达</description>
	<lastBuildDate>Fri, 18 Nov 2011 02:16:19 +0000</lastBuildDate>
	<language>en</language>
	<sy:updatePeriod>hourly</sy:updatePeriod>
	<sy:updateFrequency>1</sy:updateFrequency>
	<generator>http://wordpress.org/?v=3.2.1</generator>
		<item>
		<title>Gomez PEER终于由Pending变成了Active</title>
		<link>http://www.lorui.com/gomez-peer-pending-to-active.html</link>
		<comments>http://www.lorui.com/gomez-peer-pending-to-active.html#comments</comments>
		<pubDate>Fri, 18 Nov 2011 02:14:52 +0000</pubDate>
		<dc:creator>龙睿·LoRui</dc:creator>
				<category><![CDATA[网络赚钱]]></category>
		<category><![CDATA[Gomez]]></category>
		<category><![CDATA[PEER]]></category>
		<category><![CDATA[挂机]]></category>

		<guid isPermaLink="false">http://www.lorui.com/?p=267</guid>
		<description><![CDATA[Gomez PEER注册有有两三年了吧，挂了一年都没有激活一直是Pending，所以就没动它了。前天收到一封Gomez PEER的邮件，说有一次大规模的激活机会，于是又重新挂了。挂了两天之后，今天一登录，竟然Active，泪流满面啊，真不容易！]]></description>
			<content:encoded><![CDATA[<p><a href="https://www.gomezpeerzone.com/Apply.aspx?Referrer=lly365" target="_blank">Gomez PEER</a>注册有有两三年了吧，挂了一年都没有激活一直是Pending，所以就没动它了。前天收到一封<a href="https://www.gomezpeerzone.com/Apply.aspx?Referrer=lly365" target="_blank">Gomez PEER</a>的邮件，说有一次大规模的激活机会，于是又重新挂了。挂了两天之后，今天一登录，竟然Active，泪流满面啊，真不容易！<br />
<a href="http://www.lorui.com/wp-content/uploads/2011/11/GomezPEER.png" target="_blank"><img src="http://www.lorui.com/wp-content/uploads/2011/11/GomezPEER-300x133.png" alt="" title="GomezPEER" width="300" height="133" class="alignnone size-medium wp-image-268" /></a></p>
<img src="http://www.lorui.com/?ak_action=api_record_view&id=267&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://www.lorui.com/gomez-peer-pending-to-active.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Windows 7开放80端口以便让局域网用户访问Apache Web站点</title>
		<link>http://www.lorui.com/windows-7-open-port-80-lan-apache-web-access.html</link>
		<comments>http://www.lorui.com/windows-7-open-port-80-lan-apache-web-access.html#comments</comments>
		<pubDate>Thu, 13 Oct 2011 07:59:09 +0000</pubDate>
		<dc:creator>龙睿·LoRui</dc:creator>
				<category><![CDATA[来点技术]]></category>
		<category><![CDATA[80]]></category>
		<category><![CDATA[apache]]></category>
		<category><![CDATA[firewall]]></category>
		<category><![CDATA[lan]]></category>
		<category><![CDATA[port]]></category>
		<category><![CDATA[web]]></category>
		<category><![CDATA[windows 7]]></category>
		<category><![CDATA[开放]]></category>
		<category><![CDATA[端口]]></category>

		<guid isPermaLink="false">http://www.lorui.com/?p=262</guid>
		<description><![CDATA[即便防火墙允许了Apache，但局域网用户还是无法访问Apache站点，通过以下设置可以解决此问题。为了方便使用英文系统的朋友，LoRui将中英文都写上了。 步骤如下： 1、转到“控制面板”/Control Panel，双击“Windows防火墙”/Windows Firewall 2、单击左边的“高级设置”/Advanced Settings 3、在左边选择“入站规则”/Inbound Rules 4、在右边选择“新建规则”/New Rule 5、在“新建入站规则向导”/New Inbound Rule Wizard中选择“端口”/Port，点击“下一步”/Next 6、选择“TCP”并在“特定本地端口”/Specific local ports输入“80”（或者任何你想要开放的端口），点击“下一步”/Next 7、选择“允许连接”/Allow the connection，点击“下一步”/Next 8、选择你要应用该规则的网络位置，如果你不能确定哪个位置，可以选择全部，然后点击“下一步”/Next 9、给这个规则取个名字，还可以输入描述（可选）]]></description>
			<content:encoded><![CDATA[<p>即便防火墙允许了Apache，但局域网用户还是无法访问Apache站点，通过以下设置可以解决此问题。为了方便使用英文系统的朋友，LoRui将中英文都写上了。<br />
步骤如下：<span id="more-262"></span><br />
<a href="http://www.lorui.com/wp-content/uploads/2011/10/openport80.png"><img src="http://www.lorui.com/wp-content/uploads/2011/10/openport80-300x241.png" alt="" title="openport80" width="300" height="241" class="alignnone size-medium wp-image-265" /></a><br />
1、转到“控制面板”/Control Panel，双击“Windows防火墙”/Windows Firewall<br />
2、单击左边的“高级设置”/Advanced Settings<br />
3、在左边选择“入站规则”/Inbound Rules<br />
4、在右边选择“新建规则”/New Rule<br />
5、在“新建入站规则向导”/New Inbound Rule Wizard中选择“端口”/Port，点击“下一步”/Next<br />
6、选择“TCP”并在“特定本地端口”/Specific local ports输入“80”（或者任何你想要开放的端口），点击“下一步”/Next<br />
7、选择“允许连接”/Allow the connection，点击“下一步”/Next<br />
8、选择你要应用该规则的网络位置，如果你不能确定哪个位置，可以选择全部，然后点击“下一步”/Next<br />
9、给这个规则取个名字，还可以输入描述（可选）</p>
<img src="http://www.lorui.com/?ak_action=api_record_view&id=262&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://www.lorui.com/windows-7-open-port-80-lan-apache-web-access.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>PHP实现“属性”</title>
		<link>http://www.lorui.com/php%e5%ae%9e%e7%8e%b0%e2%80%9c%e5%b1%9e%e6%80%a7%e2%80%9d.html</link>
		<comments>http://www.lorui.com/php%e5%ae%9e%e7%8e%b0%e2%80%9c%e5%b1%9e%e6%80%a7%e2%80%9d.html#comments</comments>
		<pubDate>Fri, 07 Oct 2011 08:37:13 +0000</pubDate>
		<dc:creator>龙睿·LoRui</dc:creator>
				<category><![CDATA[来点技术]]></category>

		<guid isPermaLink="false">http://www.lorui.com/?p=260</guid>
		<description><![CDATA[一直觉得MS开发语言系列的对象“属性”很好，比起其他语言的getXXX和setXXX优雅的多。其实PHP是可以实现这个的——虽然大家在PHP里还是习惯了get/setXXX。PHP至少有两种方法实现： 1、通过默认参数。 class User { private $_name; public function Name($value = NULL) { if($value === NULL) { //get return $this->_name; } else { //set $this->_name = $value; } } } $usr = new User; $usr->Name = "龙睿"; echo $usr->Name; 2、通过魔法函数。 class Document { private $_text; public function __get($name) { $method = "get$name"; return $this->$method(); } [...]]]></description>
			<content:encoded><![CDATA[<p>一直觉得MS开发语言系列的对象“属性”很好，比起其他语言的getXXX和setXXX优雅的多。其实PHP是可以实现这个的——虽然大家在PHP里还是习惯了get/setXXX。PHP至少有两种方法实现<span id="more-260"></span>：<br />
1、通过默认参数。<br />
<code>class User {<br />
	private $_name;</p>
<p>	public function Name($value = NULL) {<br />
		if($value === NULL) { //get<br />
			return $this->_name;<br />
		} else { //set<br />
			$this->_name = $value;<br />
		}<br />
	}<br />
}</p>
<p>$usr = new User;<br />
$usr->Name = "龙睿";<br />
echo $usr->Name;</code></p>
<p>2、通过魔法函数。<br />
<code>class Document {<br />
	private $_text;</p>
<p>	public function __get($name) {<br />
		$method = "get$name";<br />
		return $this->$method();<br />
	}</p>
<p>	public function __set($name, $value){<br />
		$method = "set$name";<br />
		return $this->$method($value);<br />
	}</p>
<p>	public function getText() {<br />
		return $this->_text;<br />
	}</p>
<p>	public function setText($text){<br />
		$this->_text = $text;<br />
	}<br />
}<br />
$doc = new Document;<br />
$doc->Text = "LoRui.com";<br />
echo $doc->Text;</code></p>
<img src="http://www.lorui.com/?ak_action=api_record_view&id=260&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://www.lorui.com/php%e5%ae%9e%e7%8e%b0%e2%80%9c%e5%b1%9e%e6%80%a7%e2%80%9d.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>JQuery EasyUI 写了一个MySQL转PHP实体类、业务逻辑类的小工具</title>
		<link>http://www.lorui.com/jeasyui-php-mysql-orm.html</link>
		<comments>http://www.lorui.com/jeasyui-php-mysql-orm.html#comments</comments>
		<pubDate>Mon, 19 Sep 2011 09:21:43 +0000</pubDate>
		<dc:creator>龙睿·LoRui</dc:creator>
				<category><![CDATA[来点技术]]></category>

		<guid isPermaLink="false">http://www.lorui.com/?p=254</guid>
		<description><![CDATA[从.Net转PHP，习惯了三层编程方法。 今天用JQuery EasyUI 写了一个MySQL转PHP实体类、业务逻辑类的小工具，以后就方便了。]]></description>
			<content:encoded><![CDATA[<p>从.Net转PHP，习惯了三层编程方法。<span id="more-254"></span><br />
今天用JQuery EasyUI 写了一个MySQL转PHP实体类、业务逻辑类的小工具，以后就方便了。<br />
<a href="http://www.lorui.com/wp-content/uploads/2011/09/entity.png" target="_blank"><img src="http://www.lorui.com/wp-content/uploads/2011/09/entity-300x169.png" alt="" title="entity" width="300" height="169" class="alignnone size-medium wp-image-255" /></a></p>
<img src="http://www.lorui.com/?ak_action=api_record_view&id=254&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://www.lorui.com/jeasyui-php-mysql-orm.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Perl，套接字（Sockets）和TCP/IP网络</title>
		<link>http://www.lorui.com/perl-sockets-tcpip.html</link>
		<comments>http://www.lorui.com/perl-sockets-tcpip.html#comments</comments>
		<pubDate>Sun, 30 Jan 2011 16:11:46 +0000</pubDate>
		<dc:creator>龙睿·LoRui</dc:creator>
				<category><![CDATA[来点技术]]></category>
		<category><![CDATA[networking]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[socket]]></category>
		<category><![CDATA[tcp/ip]]></category>
		<category><![CDATA[套接字]]></category>
		<category><![CDATA[网络]]></category>

		<guid isPermaLink="false">http://www.lorui.com/?p=249</guid>
		<description><![CDATA[套接字（Sockets）的简单介绍 套接字允许同一台机器或网络上的程序间进行通讯。它的工作方法很简单：网络上的每台机器通过地址来标识。本教程我们将讨论TCP/IP网络，所以本文所述的网络地址是指IP地址（比如192.168.1.83）。一台机器除了有IP地址外，它还有许多端口，用于同一时段进行多个连接。 一个程序希望从另一个程序那里接收连接，要求操作系统创建一个socket并将其绑定到某个商品。然后，这个程序通过监听这个scoket来接收发送过来的连接。其它程序也建立socket来和它进行通讯。访问者需要指定接收者的IP地址和端口号。如果一切顺利的话，我们马上可以看到，这两个程序使用它们的socket来建立网络通讯。这两个程序可能会交换信息，每个程序都会写入数据到它创建的socket并从该socket中读取数据。 我可以在Perl做这些吗？ 当然。Perl支持socket的底层API。虽然使用API并不坏，但是还有一个非常方便的模块：IO::Socket。它将原始API进行封装，为socket提供更为简单、方便的方法。我们将在本教程中的使用IO::Socket，用来演示两个简单的程序通过socket来通讯。 接收方（Receiver） 首先，我们需要创建一个socket。我们将用它来接收连接。下面的代码演示了如何创建一个用于接收的socket。注意，我们需要指定本地机器名和端口来绑定socket。当然，如果这个端口已经被其它程序占用，将会创建失败。同时注意“Listen”参数：这是连接的最大数，通过socket队列来等等你接受并处理它们。我们暂且只接受一个连接（就是说，当一个连接在我们已经建立了其它连接的时候尝试与我们连接，我们将返回一个类似“连接被拒绝”的错误）。最后，那个“Reuse”选项告诉系统允许在这个程序退出后，重新使用该端口。它确保我们的程序在非正常退出或者非法关闭这个socket时，重新运行这个程序还能使用相同的商品来打开新的socket。 #!/usr/bin/perl ############################################## # (c) 2011 LoRui(i@lorui.com, www.lorui.com) # ############################################## use strict; use warnings; use IO::Socket; my $socket = new IO::Socket::INET ( LocalHost => 'centos.local.lorui.com', LocalPort => '7070', Proto => 'tcp', Listen => 1, Reuse => 1, ) &#124;&#124; die ("无法创建socket：$!\n"); my $new_socket = $socket->accept(); while(&#60;$new_socket&#62;) { print $_; [...]]]></description>
			<content:encoded><![CDATA[<h4>套接字（<em class="highlight">Sockets</em>）的简单介绍</h4>
<p>套接字允许同一台机器或网络上的程序间进行通讯。它的工作方法很简单：网络上的每台机器通过地址来标识。本教程我们将讨论TCP/IP网络，所以本文所述的网络地址是指IP地址（比如192.168.1.83）。一台机器除了有IP地址外，它还有许多端口，用于同一时段进行多个连接。<span id="more-249"></span><br />
一个程序希望从另一个程序那里接收连接，要求操作系统创建一个socket并将其绑定到某个商品。然后，这个程序通过监听这个scoket来接收发送过来的连接。其它程序也建立socket来和它进行通讯。访问者需要指定接收者的IP地址和端口号。如果一切顺利的话，我们马上可以看到，这两个程序使用它们的socket来建立网络通讯。这两个程序可能会交换信息，每个程序都会写入数据到它创建的socket并从该socket中读取数据。</p>
<h4>我可以在Perl做这些吗？</h4>
<p>当然。Perl支持socket的底层API。虽然使用API并不坏，但是还有一个非常方便的模块：IO::Socket。它将原始API进行封装，为socket提供更为简单、方便的方法。我们将在本教程中的使用IO::Socket，用来演示两个简单的程序通过socket来通讯。</p>
<h4>接收方（Receiver）</h4>
<p>首先，我们需要创建一个socket。我们将用它来接收连接。下面的代码演示了如何创建一个用于接收的socket。注意，我们需要指定本地机器名和端口来绑定socket。当然，如果这个端口已经被其它程序占用，将会创建失败。同时注意“Listen”参数：这是连接的最大数，通过socket队列来等等你接受并处理它们。我们暂且只接受一个连接（就是说，当一个连接在我们已经建立了其它连接的时候尝试与我们连接，我们将返回一个类似“连接被拒绝”的错误）。最后，那个“Reuse”选项告诉系统允许在这个程序退出后，重新使用该端口。它确保我们的程序在非正常退出或者非法关闭这个socket时，重新运行这个程序还能使用相同的商品来打开新的socket。</p>
<pre>#!/usr/bin/perl

##############################################
# (c) 2011 LoRui(i@lorui.com, www.lorui.com) #
##############################################

use strict;
use warnings;

use IO::Socket;

my $socket = new IO::Socket::INET (
	LocalHost => 'centos.local.lorui.com',
	LocalPort => '7070',
	Proto => 'tcp',
	Listen => 1,
	Reuse => 1,
) || die ("无法创建socket：$!\n");
my $new_socket = $socket->accept();

while(&lt;$new_socket&gt;) {
	print $_;
}
close $new_socket;</pre>
<p>现在，这个socket已经就绪，可以接收请求的连接了。我们使用 <em class="highlight">accept()</em> 方法来等待一个连接。该方法返回一个新的socket，通过它我们可以和调用程序进行通讯。通过在这个socket上读取/写入可以实现完美的信息交换。这个socket可以像普通的文件句柄那样进行操作。</p>
<h4>调用方（Caller）</h4>
<p>通讯的另一端更加简单。我们要做的只是建立一个socket，在其中指定远程地址和端口。这样构造函数将在成功连接后返回一个socket对象，然后我们可以马上开始发送一些数据。发送数据的方法很简单：就像给其它文件句柄写入数据一样，我们只要把数据写入到socket即可。</p>
<pre>#!/usr/bin/perl

##############################################
# (c) 2011 LoRui(i@lorui.com, www.lorui.com) #
##############################################

use strict;
use warnings;

use IO::Socket;
my $socket = new IO::Socket::INET (
	PeerAddr => 'centos.local.lorui.com',
	PeerPort => '7070',
	Proto => 'tcp',
) || die ("无法创建socket：$!\n");
print $socket "Hello Perl Sockets, I'm LoRui!\n";
close $socket;
</pre>
<h4>前进并尝试它！</h4>
<p>你可以很容易地尝试上面的程序。你需要先运行接收程序再运行发送程序。接收完毕之后，你将在终端看到打印出的那行“Hello Perl Sockets, I'm LoRui!”。如果你在本机测试，可以使用“localhost”作为主机名。</p>
<h4>同步</h4>
<p>用这种方式进行通讯时，有一个需要考虑的重要问题：两端必须遵循事先商定的方式来进行数据交换。否则的话，很容易在其中一方尝试读取或双方都尝试写入时进入僵持状态。没有办法去猜测另一端是否完成了数据发送，除非它们之间有一些通讯协议，在已传送信息的内容里指定通讯的逻辑单元。在上面的例子中，模块非常简单：调用方发送一条信息然后关闭它这端的连接，接收方只需要在它完成之后读取这些数据。<br />
通常客户端-服务端通讯由调用者（客户端）发送一个请求，接着接收者（服务端）作出回应。为了让服务端知道数据已发送完毕，信息的结尾必须有一些标记（比如两个空白行或一个“END REQUEST”行）。服务端只有在接收到这行的时候才开始作出回应，然后关闭这个socket连接。</p>
<p><a rel="license" href="http://creativecommons.org/licenses/by/3.0/" target="_blank"><img alt="知识共享许可协议" style="border-width:0" src="http://i.creativecommons.org/l/by/3.0/88x31.png" /></a><br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by/3.0/">知识共享署名 3.0 Unported许可协议</a>进行许可。</p>
<img src="http://www.lorui.com/?ak_action=api_record_view&id=249&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://www.lorui.com/perl-sockets-tcpip.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Perl进程（Processes）、管道（Pipes）和信号（Signals）之信号</title>
		<link>http://www.lorui.com/perl-signals.html</link>
		<comments>http://www.lorui.com/perl-signals.html#comments</comments>
		<pubDate>Tue, 25 Jan 2011 09:18:20 +0000</pubDate>
		<dc:creator>龙睿·LoRui</dc:creator>
				<category><![CDATA[来点技术]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[Pipes]]></category>
		<category><![CDATA[Processes]]></category>
		<category><![CDATA[Signals]]></category>
		<category><![CDATA[信号]]></category>
		<category><![CDATA[管道]]></category>
		<category><![CDATA[进程]]></category>

		<guid isPermaLink="false">http://www.lorui.com/?p=242</guid>
		<description><![CDATA[本系列文章讨论Perl的三个关键功能：进程（Processes）、管道（Pipes）和信号（Signals）。通过建立一个新进程，Perl程序可以运行另一个程序甚至是它自己的拷贝。管道允许Perl脚本刚其它进行交换数据，而信号使Perl脚本监视和控制其它进程成为可能。本文讨论的是其中的：信号。 信号（ signal ） 正如文件句柄，理解信号是网络编程的基础。信号是通过操作系统发送给你的程序的一个消息，告诉它发生了重要的事情。信号可以指示程序自身的一个错误，比如尝试除0。事件要求立刻反应，例如用户尝试终止这个程序，或者一个非关键信息，如程序启动后，终止一个子进程。 除了由操作系统发送之外，进程间也可以发送消息。例如，当用户按下 Ctrl + C 键时，发送一个中断信号（interrupt signal）给当前正在运行的程序，这个信号不是由操作系统发出，而是由shell（终端，命令提示符）处理并解析组合键。当然，一个进程给自己发信号也是可能的。 常见信号 POSIX标准定义了19个信号。每一个信号都拥有一个小的整数和一个符号名。我们在下面的表格显示它们。 表格的第三列表示，当一个进程接收到这个信号时，会发生什么，有些信号什么都不做。其它的则有些会立刻中断进程、有的则终止进程并导致主存储器信息转存。大部分信号可以被“捕获”。就是说，当接收到信号时，程序可以搭配一个句柄给信号并采取对应的处理。但是，有些信号不能用这种方式拦截。 你不需要完全明白下表中的信号列表，因为有些并不在Perl脚本中发生，或者它们在Perl自身内部用于标示底层BUG你没有办法做任何相关的事。当然，一大把信号是相对常见的，并且我们马上就能看到它们的详细信息。 HUP 信号是个挂断事件。它通常在一个用户从命令行运行程序 ，然后关闭命令行窗口或退出解析器时发生。这个信号的默认行为是结束这个程序。 INT 信号是个用户发送的中断信号。它通常是在用户按下中断键（通常是Ctrl + C）时发生。这个信号的默认行为是结束这个程序。 QUIT 和 INT 相似，但但会促使程序生成核心文件（在Unix系统）。当用户按下“退出”键（通常是 Ctrl + \）时触发该信号。 信号名称 值 注解 描述 HUP 1 A 挂断检测 INT 2 A 从键盘中断 QUIT 3 A 从键盘退出 ILL 4 A 非法指令 ABRT 6 C 放弃 FPE 8 [...]]]></description>
			<content:encoded><![CDATA[<p>本系列文章讨论Perl的三个关键功能：<a href="perl-processes.html">进程（Processes）</a>、<a href="perl-pipes.html">管道（Pipes）</a>和<a href="perl-signals.html">信号（Signals）</a>。通过建立一个新进程，Perl程序可以运行另一个程序甚至是它自己的拷贝。管道允许Perl脚本刚其它进行交换数据，而信号使Perl脚本监视和控制其它进程成为可能。本文讨论的是其中的：信号。</p>
<h3>信号（ <em class="highlight">signal</em> ）</h3>
<p>正如文件句柄，理解信号是网络编程的基础。信号是通过操作系统发送给你的程序的一个消息，告诉它发生了重要的事情。信号可以指示程序自身的一个错误，比如尝试除0。事件要求立刻反应，例如用户尝试终止这个程序，或者一个非关键信息，如程序启动后，终止一个子进程。<span id="more-242"></span><br />
除了由操作系统发送之外，进程间也可以发送消息。例如，当用户按下 Ctrl + C 键时，发送一个中断信号（interrupt signal）给当前正在运行的程序，这个信号不是由操作系统发出，而是由shell（终端，命令提示符）处理并解析组合键。当然，一个进程给自己发信号也是可能的。</p>
<h4>常见信号</h4>
<p>POSIX标准定义了19个信号。每一个信号都拥有一个小的整数和一个符号名。我们在下面的表格显示它们。<br />
表格的第三列表示，当一个进程接收到这个信号时，会发生什么，有些信号什么都不做。其它的则有些会立刻中断进程、有的则终止进程并导致主存储器信息转存。大部分信号可以被“捕获”。就是说，当接收到信号时，程序可以搭配一个句柄给信号并采取对应的处理。但是，有些信号不能用这种方式拦截。<br />
你不需要完全明白下表中的信号列表，因为有些并不在Perl脚本中发生，或者它们在Perl自身内部用于标示底层BUG你没有办法做任何相关的事。当然，一大把信号是相对常见的，并且我们马上就能看到它们的详细信息。<br />
<em class="highlight">HUP</em> 信号是个挂断事件。它通常在一个用户从命令行运行程序 ，然后关闭命令行窗口或退出解析器时发生。这个信号的默认行为是结束这个程序。<br />
<em class="highlight">INT</em> 信号是个用户发送的中断信号。它通常是在用户按下中断键（通常是Ctrl + C）时发生。这个信号的默认行为是结束这个程序。 <em class="highlight">QUIT</em> 和 <em class="highlight">INT</em> 相似，但但会促使程序生成核心文件（在Unix系统）。当用户按下“退出”键（通常是 Ctrl + \）时触发该信号。</p>
<table>
<tr>
<th>信号名称</th>
<th>值</th>
<th>注解</th>
<th>描述</th>
</tr>
<tr>
<td> <em class="highlight">HUP</em> </td>
<td>1</td>
<td>A</td>
<td>挂断检测</td>
</tr>
<tr>
<td> <em class="highlight">INT</em> </td>
<td>2</td>
<td>A</td>
<td>从键盘中断</td>
</tr>
<tr>
<td> <em class="highlight">QUIT</em> </td>
<td>3</td>
<td>A</td>
<td>从键盘退出</td>
</tr>
<tr>
<td> <em class="highlight">ILL</em> </td>
<td>4</td>
<td>A</td>
<td>非法指令</td>
</tr>
<tr>
<td> <em class="highlight">ABRT</em> </td>
<td>6</td>
<td>C</td>
<td>放弃</td>
</tr>
<tr>
<td> <em class="highlight">FPE</em> </td>
<td>8</td>
<td>C</td>
<td>浮点异常</td>
</tr>
<tr>
<td> <em class="highlight">KILL</em> </td>
<td>9</td>
<td>AF</td>
<td>终止信号</td>
</tr>
<tr>
<td> <em class="highlight">USR1</em> </td>
<td>10</td>
<td>A</td>
<td>自定义信号1</td>
</tr>
<tr>
<td> <em class="highlight">SEGV</em> </td>
<td>11</td>
<td>C</td>
<td>无效的内存引用</td>
</tr>
<tr>
<td> <em class="highlight">USR2</em> </td>
<td>12</td>
<td>A</td>
<td>自定义信号2</td>
</tr>
<tr>
<td> <em class="highlight">PIPE</em> </td>
<td>13</td>
<td>A</td>
<td>写入到管道没有读取者</td>
</tr>
<tr>
<td> <em class="highlight">ALRM</em> </td>
<td>14</td>
<td>A</td>
<td>闹钟的定时器信号</td>
</tr>
<tr>
<td> <em class="highlight">TERM</em> </td>
<td>15</td>
<td>A</td>
<td>终止信号</td>
</tr>
<tr>
<td> <em class="highlight">CHLD</em> </td>
<td>17</td>
<td>B</td>
<td>子进程已终止</td>
</tr>
<tr>
<td> <em class="highlight">CONT</em> </td>
<td>18</td>
<td>E</td>
<td>如果停止则继续</td>
</tr>
<tr>
<td> <em class="highlight">STOP</em> </td>
<td>19</td>
<td>DF</td>
<td>停止处理</td>
</tr>
<tr>
<td> <em class="highlight">TSTP</em> </td>
<td>20</td>
<td>D</td>
<td>停止虚拟终端(tty)输入</td>
</tr>
<tr>
<td> <em class="highlight">TTIN</em> </td>
<td>21</td>
<td>D</td>
<td>后台处理虚拟终端(tty)输入</td>
</tr>
<tr>
<td> <em class="highlight">TTOU</em> </td>
<td>22</td>
<td>D</td>
<td>后台处理虚拟终端(tty)输出</td>
</tr>
<tr>
<td cols="3">
		注解：</p>
<ul>
<li>A：默认操作是结束进程。</li>
<li>B：默认操作是忽略该信号。</li>
<li>C：默认操作是结束进程和核心转储。</li>
<li>D：默认操作是停止该进程。</li>
<li>E：默认操作是恢复该进程。</li>
<li>F：信号无法被捕获或忽略。</li>
</ul>
</td>
</tr>
</table>
<p>按照惯例， 在一个进程中使用<em class="highlight">TERM</em> 和 <em class="highlight">KILL</em> 来结束另一个进程。默认的， <em class="highlight">TERM</em>让程序直接结束，但程序可以搭配一个信号句柄给 <em class="highlight">TERM</em>，用来拦截结束请求以及可能在退出执行一些清理工作。 相比之下，<em class="highlight">KILL</em> 信号是无法捕获的，它会强制进程立刻结束。比如，当UINX系统关机时，shutdown（关机）进程首先发送 <em class="highlight">TERM</em> 给所有正在运行的进程，给机会给它们进行清理。如果少数进程在几秒后依然在运行，那么它将发送 <em class="highlight">KILL</em>。<br />
<em class="highlight">PIPE</em> 信号，当一个程序写入到管道或套接字时，远程进程被关闭或退出时发送。这个信号在网络应用程序中很常见，因此我们可以在处理PIPE异常的时候检查它。<br />
<em class="highlight">ALRM</em> 通常与 <em class="highlight">alarm()</em> 协同工作，在某一事先安排的时间将要来临的时候，将信号发送给程序。特殊的是， <em class="highlight">ALRM</em>可以被时间输出块I/O调用。<br />
<em class="highlight">CHLD</em> 在你的进程启动一个子进程后出现，并且子进程的状态在某种程度上已发生改变。状态的代表性的改变是子进程已退出，但每次子进程停止或继续之后， <em class="highlight">CHLD</em> 依然产生。<br />
<em class="highlight">STOP</em> 和 <em class="highlight">TSTP</em> 两个信号都有停止当前进程的效果。使进程无限期地假死；可以通过发送 <em class="highlight">CONT</em> 信号让它恢复。 <em class="highlight">STOP</em> 通常用于一个程序停止另一个程序。当用户在终端按下停止键（UNIX系统上是Ctrl+Z）时，产生<em class="highlight">TSTP</em> 信号。两者的另一个区别是， <em class="highlight">TSTP</em> 可以被捕获，而 <em class="highlight">STOP</em> 不能被捕获或忽略。</p>
<h4>捕获信号</h4>
<p>你可以通过在全局哈希 <em class="highlight">%SIG</em> 中添加一个信号句柄来捕获一个信号。使用你想捕获的信号名作为哈希的键。例如，使用 <em class="highlight">$SIG{INT}</em> 来获取或设置 <em class="highlight">INT</em> 信号句柄。使用引用作为值：一个匿名函数或指向已命名函数的引用。例如，下面的例子是一个设定 <em class="highlight">INT</em> 信号句柄的小脚本。当我们按下中断键的时候，它打印一条短信息并增加计数器。脚本如此运行下去，直到计数到三次中断，说明真正要结束了。在下面的例子中，当我们按下Ctrl+C时，打印一条“别打断我！”的信息。</p>
<pre>#!/usr/bin/perl
#文件：interrupt.pl #1
##############################################
# (c) 2011 LoRui(i@lorui.com, www.lorui.com) #
##############################################

use strict; #2
use warnings;

my $interruptions = 0; #3
$SIG{INT} = \&#038;hanlde_interruptions; #4

while($interruptions < 3) { #5
	print "休息一下……\n"; #6
	sleep 5; #7
} #8

sub hanlde_interruptions { #9
	$interruptions++; #10
	warn "别打断我！你已经打断我 $interruption 次了！\n"; #11
} #12
</pre>
<p>来看看这个脚本的详细信息。</p>
<ul>
<li>1到3行：初始化脚本。开启严格的语法检测并声明一个名为 $interruptions 的全局计数器。这个计数器将保持对 <em class="highlight">INT</em> 接收次数的跟踪。</li>
<li>第4行：设定 <em class="highlight">INT</em> 句柄。我们通过设定 <em class="highlight">$SIG{INT}</em> 来将 <em class="highlight">INT</em> 信号的处理程序关联到 handle_interruptions() 函数。</li>
<li>5到8行：主循环。程序的主循环只是简单的打印一条信息以及用5为参数调用 <em class="highlight">sleep()</em> 函数。这会让程序暂停5秒钟，或者直到接收到一个信号。它只在计数器小于3时才会继续循环。</li>
<li>9到12行：信号处理程序。当 <em class="highlight">INT</em> 信号发生时，将调用handle_interruptions() 函数，即使这个程序此间正在处理别的事情。因此，我们的信号处理程序让计数器递增了一次，并打印一条警告。</li>
</ul>
<p>对于很短的信号处理程序，你可以使用匿名函数进行处理。比如，下面的代码片段和刚刚的行将，但我们不需要给信号处理程序命名：</p>
<pre>$SIG{INT} = sub {
                $interruptions++;
                warn "别打断我！你已经打断我 $interruption 次了！\n";
                 };
</pre>
<p>除了引用代码之外， <em class="highlight">%SIG</em> 接受两个特例。字符串“<em class="highlight">DEFAULT</em>”恢复默认信号的行为。例如，将 <em class="highlight">$SIG{INT}</em> 设定为“<em class="highlight">DEFAULT</em>”，将让 <em class="highlight">INT</em> 信号再次结束当前脚本。字符串“<em class="highlight">IGNORE</em>”将让该信号完全被忽略。<br />
不要为前面提到过的 <em class="highlight">KILL</em> 或 <em class="highlight">STOP</em> 设定信号处理程序感到紧张。这些信号既不可以捕获也不可以忽略，并且它们的默认操作将永远执行。<br />
如果你希望使用相同的方法来捕获多个不同的信号，并且希望在处理程序里识别信号，可以检查处理程序的第一个参数，它包含信号的名字。例如，对于 <em class="highlight">INT</em> 信号，其处理将其称为字符串“INT”：</p>
<pre>$SIG{TERM} = $SIG{HUP} = $SIG{INT} = \&#038;handler
sub handler {
   my $sig = shift;
   warn "处理 $sig 信号.\n";
}</pre>
<h4>处理 PIPE 异常</h4>
<p>现在我们拥有了处理PIPE异常所需要的知识。回想《Perl进程、管道和信号之二：管道》里的示例代码 <i>write_ten.pl</i> 和 <i>read_three.pl</i>中那恐怖的PIPE错误。<i>write_ten.pl</i>打开一个到<i>read_three.pl</i>的管道并尝试向其写入10行文本，但<i>read_three.pl</i>只期望接受3行之后就退出并结束管道。<i>write_ten.pl</i> 并不知道到对方的连接已经被关闭，尝试写入第4行的时候， <em class="highlight">PIPE</em> 信号产生了。<br />
现在，我们要修改<i>write_ten.pl</i>，让它检测并温和的处理 <em class="highlight">PIPE</em> 错误。</p>
<pre>#!/usr/bin/perl
#文件：write_ten_ph.pl #1
##############################################
# (c) 2011 LoRui(i@lorui.com, www.lorui.com) #
##############################################

use strict; #2
use warnings;

my $ok = 1; #3
$SIG{PIPE} = sub { undef $ok };
open(PIPE, "| read_three.pl") or die ("无法打开管道：$!");
select PIPE; $| = 1; select STDOUT;

my $count = 0;
for($_ = 1; $ok &#038;& $_ <= 10; $_++) {
	warn "写入第 $_ 行\n";
	print PIPE "这是第 $_ 行\n" and $count++;
	sleep 1;
}

close PIPE or die "无法关闭管道：$!";
print "共写入 $count 行文本\n";</pre>
<p><em class="highlight">$SIG{PIPE} = sub { undef $ok };</em> 当接受到一个PIPE信号时，该处理程序将取消$ok的定义，让其为假。<br />
其他修改是，替换原始版本的 <em class="highlight">for()</em> 循环到更加高雅的版本，它同时检测$ok。如果$ok为假，退出循环。当我们运行修改后的代码时，可以看到程序正常结束，而且正确的报告了成功写入的行数：</p>
<pre>% write_ten_ph.pl
写入第 1 行
read_three.pl 获取：这是第 1 行
写入第 2 行
read_three.pl 获取：这是第 2 行
写入第 3 行
read_three.pl 获取：这是第 3 行
写入第 4 行
共写入 3 行文本</pre>
<p>另一种常用方法是设置 <em class="highlight">$SIG{INT}</em> 为“<em class="highlight">IGNORE</em>”，以完整的忽略 <em class="highlight">PIPE</em>信号。现在，我们的职责是检查出了什么错，我们可以通过 <em class="highlight">print()</em> 的返回值进行检测。如果 <em class="highlight">print()</em> 返回假值，我们退出循环。<br />
下面的 <i>write_ten_i.pl</i> 代码展示了这种方法。这个脚本开始于将 <em class="highlight">$SIG{INT}</em> 设定为字符串“<em class="highlight">IGNORE</em>”，阻止 <em class="highlight">PIPE</em> 信号。另外，我们修改了打印循环体：如果 <em class="highlight">printf()</em> 成功，我们让计数器递增，否则我们输警告并通过 <em class="highlight">last</em> 退出循环。</p>
<pre>#!/usr/bin/perl
#文件：write_ten_i.pl #1
##############################################
# (c) 2011 LoRui(i@lorui.com, www.lorui.com) #
##############################################

use strict; #2
use warnings;

$SIG{PIPE} = 'IGNORE'; #3

open(PIPE, "| read_three.pl") or die "无法打开管道：$!"; #4
select PIPE; $| = 1; select STDOUT; #5

my $count = 0; #6
for(1..10) { #7
	warn "写入第 $_ 行\n"; #8
	if(print PIPE "这是第 $_ 行\n") { #9
		$count++; #10
	} else { #11
		warn "写入数据时有错误发生：$!\n"; #12
		last; #13
	} #14
	sleep 1; #15
} #16
close PIPE or die "无法关闭管道：$!"; #17

print "共写入 $count 行文本\n"; #18
</pre>
<p>运行结果：</p>
<pre>% write_ten_i.pl
写入第 1 行
read_three.pl 获取：这是第 1 行
写入第 2 行
read_three.pl 获取：这是第 2 行
写入第 3 行
read_three.pl 获取：这是第 3 行
写入第 4 行
写入数据时有错误发生：Broken pipe
共写入 3 行文本</pre>
<p>注意，如果失败，错误信息中的 <em class="highlight">$!</em> 处将显示 “Broken pipe”。如果你希望将这个错误与其它I/O错误分别处理，我们可以通过正则表达式来明确地测试它的值。或用更好的方法：通过它的数字值与EPIPE的错误常量进行比对，例如：</p>
<pre>use Errno ':POSIX';
...
unless (print PIPE "这是第 $_ 行\n") { # 处理写入错误
   last if $! == EPIPE;   # PIPE错误，终止循环
   die "I/O 错误： $!";   # 其它错误，打印错误信息
}
</pre>
<h4>发送信号</h4>
<p>Perl脚本可以使用 <em class="highlight">kill()</em> 函数发送一个信号到其它进程。</p>
<div class="code_syntax">
<h5>$count = kill($signal, @processes)</h5>
<p>	<em class="highlight">kill()</em> 函数发送信号$signal给一个或多个进程。你可以通过数字（比如：2）或符号名（比如： <em class="highlight">INT</em>）来指定要发送的信号。@processes是将信号发送过去的一个或多个进程的PID列表。成功执行信号的进程数将作为 <em class="highlight">kill()</em> 函数的结果返回。
</div>
<p>一个进程只能发送一个信号给其它进程，并且需要对应的权限。一般而言，进程以普通用户权限运行，那么该进程也只能给普通用户权限及普通用户以下权限运行的进程发送信号。是的，以root或超级用户权限运行的进程可以给任何进程发送信号。<br />
<em class="highlight">kill()</em> 函数提供了一些技巧。如果你使用特殊的信号编号0，那么 <em class="highlight">kill()</em> 将返回能发送信号的进程数，而不会实际发送信号。如果你使用负数作为PID， <em class="highlight">kill()</em> 将处理该负数绝对值对应的进程组ID并将信号发送到该组所有的成员。<br />
脚本可以通过传递变量 <em class="highlight">$$</em> 给 <em class="highlight">sill()</em> 来给自己发送信号。该变量保存着当前进程的ID。例如：</p>
<pre>kill INT => $$; # 等效于 kill('INT',$$)</pre>
<h4>让慢的系统调用超时</h4>
<p>当Perl执行系统调用时，信号可能产生。大多数情况下，Perl自动重启并严密监控调用。<br />
少数系统调用不适用此规则。 <em class="highlight">sleep()</em> 就是其中之一，它根据指示数暂停脚本执行对应秒数。如果一个信号中断 <em class="highlight">sleep()</em>，它将过早地结束，返回它完成休眠前的秒数。<em class="highlight">sleep()</em> 的这个属性很有用，因为它可以让脚本一直暂停，直到有预期的事件发生。</p>
<div class="code_syntax">
<h5>$slept = sleep([$seconds])</h5>
<p>	根据指定的秒数暂停，或一直暂停直到接收到一个信号。如果没有给定参数，该函数将永远暂停。 <em class="highlight">sleep()</em> 将返回其实际暂停的秒数。
</div>
<p>另一个例外是四个参数的 <em class="highlight">select()</em>，它可用于定时等待，直到一个或多个设定的I/O文件句柄准备就绪。该函数将在以后文章中描述。</p>
<p>有时候，自动重启系统调用不是你想要的。比如，一个应用程序提示用户输入密码，并尝试从标准输入读取用户输入。你可能希望，读取工作在一段时间后超时退出，以避免用户已离开，程序却还在等待输入。下面的代码片段看上去好像能胜任这个工作：</p>
<pre>my $timed_out = 0; 

$SIG{ALRM} = sub { $timed_out = 1 };

print STDERR "输入密码: ";
alarm (5);    # 5秒超时
my $password = &lt;STDIN&gt;;
alarm (0);

print STDERR "操作已超时\n" if $timed_out;</pre>
<p>这里我们使用 <em class="highlight">alarm()</em> 函数来设计定时器。当定时器过期，操作系统生成一个 <em class="highlight">ALRM</em>信号，我们拦截这个信号并进行处理：设置全局变量 $timed_out 为真。在这个代码里，我们用5秒超时来调用 <em class="highlight">alarm()</em> 函数，然后从标准输入读取一行。读取完成之后，我们以零为参数再次调用 <em class="highlight">alarm()</em>，以关闭定时器。就是说，用户要在5秒钟的时间内输入密码，否则定时器将失效，我们也不再重启该程序。</p>
<div class="code_syntax">
<h5>$seconds_lef = alarm($seconds)</h5>
<p>	为把 <em class="highlight">ALRM</em> 信号在$seconds秒之后传递给进程做准备。如果参数为零，将使定时器失效。
</div>
<p>Perl自动重启使用系统调用变慢的问题中，包括 <em class="highlight">&lt;&gt;</em>。即使闹钟停止了，我们还停留在<em class="highlight">&lt;&gt;</em>调用，等待用户的键盘输入。<br />
这个问题的解决方法是使用 <em class="highlight">eval{}</em> 以及让 <em class="highlight">ALRM</em> 成为 local 变量，取消读取。</p>
<pre>print STDERR "输入密码: ";
my $password =
  eval {
    local $SIG{ALRM} = sub { die "超时\n" };
    alarm (5);    # 5秒超时
    return &lt;STDIN&gt;;
  };
alarm (0);
print STDERR "操作已超时\n" if $@ =~ /timeout/;
</pre>
<p>这个程序中，我们命名 <em class="highlight">eval{}</em> 块让 <em class="highlight">ALRM</em>处理程序局部化（localize）。<em class="highlight">eval{}</em> 块设定闹钟，跟前面一样，尝试从 <em class="highlight">STDIN</em> 读取。如果在 <em class="highlight">&lt;&gt;</em> 返回之前已超时，用户输入将从<em class="highlight">eval{}</em> 块返回，并赋值给 $password。<br />
如果在超时前完成输入， <em class="highlight">ALRM</em> 处理程序将被执行。</p>
<p><a rel="license" href="http://creativecommons.org/licenses/by/3.0/" target="_blank"><img alt="知识共享许可协议" style="border-width:0" src="http://i.creativecommons.org/l/by/3.0/88x31.png" /></a><br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by/3.0/" target="_blank">知识共享署名 3.0 Unported许可协议</a>进行许可。</p>
<img src="http://www.lorui.com/?ak_action=api_record_view&id=242&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://www.lorui.com/perl-signals.html/feed</wfw:commentRss>
		<slash:comments>1</slash:comments>
		</item>
		<item>
		<title>Perl进程（Processes）、管道（Pipes）和信号（Signals）之管道</title>
		<link>http://www.lorui.com/perl-pipes.html</link>
		<comments>http://www.lorui.com/perl-pipes.html#comments</comments>
		<pubDate>Tue, 25 Jan 2011 09:16:54 +0000</pubDate>
		<dc:creator>龙睿·LoRui</dc:creator>
				<category><![CDATA[来点技术]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[Pipes]]></category>
		<category><![CDATA[Processes]]></category>
		<category><![CDATA[Signals]]></category>
		<category><![CDATA[信号]]></category>
		<category><![CDATA[管道]]></category>
		<category><![CDATA[进程]]></category>

		<guid isPermaLink="false">http://www.lorui.com/?p=240</guid>
		<description><![CDATA[本系列文章讨论Perl的三个关键功能：进程（Processes）、管道（Pipes）和信号（Signals）。通过建立一个新进程，Perl程序可以运行另一个程序甚至是它自己的拷贝。管道允许Perl脚本刚其它进行交换数据，而信号使Perl脚本监视和控制其它进程成为可能。本文讨论的是其中的：管道。 管道（Pipe） 两个进程间交换数据。随程序而定，两个进程可能运行在相同的机器上，也可能是运行在LAN（局域网，Local Area Network）的两台机器上，也有可能是互联网上的其中一个。这两个进程会彼此协作。 Perl的管道是IPC（进程间通讯， interprocess communication）的最简单形式。管道是当前脚本的一个文件句柄连接到另一进程的标准输入或标准输出。Perl的管道在UNIX、VMS和Windows已完全实现并在Macintosh的MPW环境有限实现。 操作一个管道 open()的两个参数用于打开管道。首先，第一个参数是文件句柄的名字。第二个参数，是一个程序和它的所有参数，要么在前面、要么在后面跟着管道的符号“ &#124; ”。该命令应该完全按照所使用的操作系统默认的shell来输入，UNIX里是Bourne shell（“sh”），WINDOWS里是DOS/NT命令提示符。你可能需要指定该命令的完整路径，比如/usr/bin/ls或者依赖PATH环境变量来查找。 如果管道符号在程序前面，文件句柄将标准输入发送给它的数据全部写入。如果管道符号在程序后面，文件句柄打开并读取，并把所有读取到的数据传递给程序的标准输出。 举例来说，在UNIX中，ls -l命令将返回当前目录下所有文件的列表。将“ ls -l &#124; ”传递给 open()，我们可以打开一个管道从这个命令里读取： open (LSFH,"ls -l &#124;") or die "无法打开 ls -l: $!"; while (my $line = &#60;LSFH&#62;) { print "我看到了: $line\n"; } close LSFH; 这个片段简单地响应了ls -l命令返回的每一行。 另一个有关管道输出的例子，UNIX的 wc -lw命令将统计文本文件的行数（选项“-l”）和单词数（选项“-w”），并发送到标准输入。下面的代码片段使用管道打开这个命令，写入几行文本给它，然后关闭管道。当程序运行时，统计得到的单词数和行数将通过wc打印到命令窗口： open (WC,"&#124; wc -lw") or die "无法打开单词统计: [...]]]></description>
			<content:encoded><![CDATA[<p>本系列文章讨论Perl的三个关键功能：<a href="perl-processes.html">进程（Processes）</a>、<a href="perl-pipes.html">管道（Pipes）</a>和<a href="perl-signals.html">信号（Signals）</a>。通过建立一个新进程，Perl程序可以运行另一个程序甚至是它自己的拷贝。管道允许Perl脚本刚其它进行交换数据，而信号使Perl脚本监视和控制其它进程成为可能。本文讨论的是其中的：管道。</p>
<h3>管道（<em class="highlight">Pipe</em>）</h3>
<p>两个进程间交换数据。随程序而定，两个进程可能运行在相同的机器上，也可能是运行在LAN（局域网，Local Area Network）的两台机器上，也有可能是互联网上的其中一个。这两个进程会彼此协作。<span id="more-240"></span></p>
<p>Perl的管道是IPC（进程间通讯， interprocess communication）的最简单形式。管道是当前脚本的一个文件句柄连接到另一进程的标准输入或标准输出。Perl的管道在UNIX、VMS和Windows已完全实现并在Macintosh的MPW环境有限实现。</p>
<h4>操作一个管道</h4>
<p><em class="highlight">open()</em>的两个参数用于打开管道。首先，第一个参数是文件句柄的名字。第二个参数，是一个程序和它的所有参数，要么在前面、要么在后面跟着管道的符号“ <em class="highlight">|</em> ”。该命令应该完全按照所使用的操作系统默认的shell来输入，UNIX里是Bourne shell（“sh”），WINDOWS里是DOS/NT命令提示符。你可能需要指定该命令的完整路径，比如/usr/bin/ls或者依赖PATH环境变量来查找。</p>
<p>如果管道符号在程序前面，文件句柄将标准输入发送给它的数据全部写入。如果管道符号在程序后面，文件句柄打开并读取，并把所有读取到的数据传递给程序的标准输出。</p>
<p>举例来说，在UNIX中，<em>ls -l</em>命令将返回当前目录下所有文件的列表。将“ <em class="highlight">ls -l | </em>”传递给 <em class="highlight">open()</em>，我们可以打开一个管道从这个命令里读取：</p>
<pre>open (LSFH,"ls -l |") or die "无法打开 ls -l: $!";
while (my $line = &lt;LSFH&gt;) {
  print "我看到了: $line\n";
}
close LSFH;
</pre>
<p>这个片段简单地响应了ls -l命令返回的每一行。<br />
另一个有关管道输出的例子，UNIX的 <em>wc -lw</em>命令将统计文本文件的行数（选项“-l”）和单词数（选项“-w”），并发送到标准输入。下面的代码片段使用管道打开这个命令，写入几行文本给它，然后关闭管道。当程序运行时，统计得到的单词数和行数将通过wc打印到命令窗口：</p>
<pre>open (WC,"| wc -lw") or die "无法打开单词统计: $!";
print WC "这是第1行。\n";
print WC "这是另一行。\n";
print WC "这是最后一行。\n";
print WC "哦，我说谎了。\n";
close WC;
</pre>
<p>IO::Filehandle通过 <em class="highlight">open()</em> 方法来支持管道：</p>
<pre>$wc = IO::Filehandle->open("| wc - lw") or die "无法打开单词统计: $!";
</pre>
<h4>使用管道</h4>
<p>来看看完整的实用的例子。这个 <em>whos_there.pl</em> 程序打开一个到UNIX <em>who</em>命令的管道并统计当前系统的用户登录次数。</p>
<pre>#!/usr/bin/perl
#whos_there.pl - 统计登记当前系统的用户登录次数
##############################################
# (c) 2011 LoRui(i@lorui.com, www.lorui.com) #
##############################################

use strict; #1
use warnings; #2

my %who;  #3

open(WHOFH, "who |") or die "无法打开who: $!"; #4

while(&lt;WHOFH&gt;) { #5
	next unless m/^(\S+)/; #6
	$who{$1}++; #7
} #8

close WHOFH; #12

#输出统计结果
foreach(sort {$who{$b} &lt;=&gt; $who{$a}} keys %who) { #9
	printf "%10s %d\n", $_, $who{$_}; #10
} #11
</pre>
<p>输出结果：</p>
<pre>% whos_there.pl
   lorui 81
    root 25
  zhang3 1
</pre>
<p>它显示用户“lorui”和“root”分别登录81次和25次，其它用户只登录了一次。这些用户按登录次数的多少来排序。<br />
1到3行：初始化脚本。通过 <em class="highlight">use strict</em> 来开启严格的语法检查。它捕获错误键入的变量、不恰当地使用全局变量、引用字符串失败和其它潜在的错误。我们创建一个哈希（hash）%who 来存储用户和登记次数。<br />
第4行：打开到 <em>who</em> 命令的管道。我们在名为WHOFH的文件句柄上使用 <em class="highlight">open()</em>，使用 <em class="highlight">who |</em> 作为第二个参数。如果 <em class="highlight">open()</em> 调用失败，打印错误信息并终止程序运行。<br />
5到8行：读取who命令的输出。我们每次读取并处理一行who的输出，每行who命令的输出看起来像这样：</p>
<pre>lorui pts/23 Jan 24 16:52 (centos.lorui.com)</pre>
<p>这些字段分别是：用户名，该用户在终端使用的名字；登录时间以及他登录的远程机器。我们使用一个正则模式来提取用户名，然后将用户名存放到%who哈希中，这样一来，用户名成了该哈希的键，每个用户的登录资料成了哈希的值。<br />
<em class="highlight"><WHOFH></em>将在文件结尾处（EOF，End Of File）终止。<br />
9到11行：打印结果。我们以登录次数来将%who的键进行排序，并打印每个用户的登录资料。在这里使用了 <em class="highlight">printf()</em> 格式，“%10s %d \n”告诉 <em class="highlight">pritnf()</em>将其第一个参数格式化为右对齐的10个字符长，如果不足10，则以空格填充。然后以十进制整数打印第二个参数。<br />
第12行：关闭管道。现在，已经完成管道操作了，所以我们使用 <em class="highlight">close()</em> 来关闭它。</p>
<p>和管道一起的 <em class="highlight">open()</em> 和 <em class="highlight">close()</em>稍微得到了增加。它们提供有关子进程的附加信息。当打开一个管道时， <em class="highlight">open()</em>返回命令的PID。这是一个非零的唯一整数，可使用<a href="perl-signals.html">信号</a>来监视和控制子进程。你可以保存这个PID，也可以忽略它而仅把它作为 <em class="highlight">open()</em> 函数的返回值对待。<br />
当管道关闭时， <em class="highlight">close()</em>将进程序的退出代码存储到了全局变量 <em class="highlight">$?</em> 中。和大多数Perl习惯相反， <em class="highlight">$?</em>为零表示命令成功，非零表示命令失败。<br />
另一方面，在使用 <em class="highlight">close()</em> 关闭管道时， <em class="highlight">close()</em>函数调用将被屏蔽，直到所有工作完成并退出。如果你在一个管道读取到EOF之前关闭了它，程序将得到一个PIPE信号。</p>
<h4>反引号运算符：让管道变得简单</h4>
<p>Perl的反引号运行符（`）是创建单一读取程序输出管道的便捷方法。反引号的行为和双引号的行为相似，只有一点不同：反引号会解析并执行其中的命令，比如：</p>
<pre>$ls_output = `ls`;</pre>
<p>这将运行ls命令，捕获其输出并将输出赋给 $ls_output 标量。</p>
<p>在内部，Perl打开一个管道来执行命令，读取它的所有输出并打印到标准输出，关闭管道并返回命令的输出作为该操作的结果。通常操作结果都以换行符结尾，可通过 <em class="highlight">chomp()</em> 来移除。</p>
<p>和双引号一样，反引号解析标量和数组。比如，我们可以创建一个包含参数的变量传递给ls命令，像这样：</p>
<pre>$arguments = '-l -F';
$ls_output = `ls $arguments`;
</pre>
<p>命令的标准错误不通过反引号重定向。如果子进程输出任何诊断或错误信息，它们将和你的输出信息混合在一起。在UNIX系统中，你可以使用Bourne shll的输出重定向系统将子进程的标准错误和标准输出联合在一直，就像这样：</p>
<pre>$ls_output = `ls 2>&#038;1`;</pre>
<p>现在 $ls_output 将包含命令的标准错误和标准输出。</p>
<h4><em class="highlight">pipe()</em>函数：让管道更加强大</h4>
<p>一种强大但稍显复杂的创建管道的方法是使用Perl内置的 <em class="highlight">pipe()</em>函数。 <em class="highlight">pipe()</em>创建一对文件句柄：一个用来读一个一来写。任何写到一个文件句柄的数据可以从另一个句柄读取。</p>
<div class="code_syntax">
<h5>$result = pipe(READHANDLE, WRITEHANDLE)</h5>
<p>	创建一对文件句柄连接到管道。第一个参数是读数据的句柄，第二个是写数据的句柄。如果成功， <em class="highlight">pipe()</em> 返回一个真值。
</div>
<p>为什么 <em class="highlight">pipe()</em> 这么有用？它一般与 <em class="highlight">fork()</em> 函数配合使用，来创建父子进程对，以交换数据。父进程保持一个文件句柄同时关闭另一个，与此同时子进程做相反的工作。父子进程现在可以通过管道进行通讯，以实现并行工作。<br />
一个简短的例子可以阐述这项技术的功能。给定一个正整数，脚本<em>facifib.pl</em>计算其阶乘和该值在斐波纳契级数的位置。利用现代的多处理器机器，这个计算将出现在两个子进程里，而且由父进程启动它们。当你运行这个程序的时候，你可以看到类似下面的结果：</p>
<pre>% facfib.pl 8
factorial(1) => 1
factorial(2) => 2
factorial(3) => 6
factorial(4) => 24
factorial(5) => 120
fibonacci(1) => 1
factorial(6) => 720
fibonacci(2) => 1
factorial(7) => 5040
fibonacci(3) => 2
factorial(8) => 40320
fibonacci(4) => 3
fibonacci(5) => 5
fibonacci(6) => 8
fibonacci(7) => 13
fibonacci(8) => 21
</pre>
<p>以下是代码：</p>
<pre>#!/usr/bin/perl
#facfib.pl - 计算阶乘和斐波纳契级数的位置 #1
##############################################
# (c) 2011 LoRui(i@lorui.com, www.lorui.com) #
##############################################

use strict; #2
use warnings;

my $arg = shift || 10; #3

pipe(READER, WRITER) or die "无法打开管道: $!\n"; #4

if (fork == 0) { #第一个子进程写到WRITER #5
	close READER; #6
	select WRITER; $| = 1; #7
	factorial($arg); #8
	exit 0; #9
} #10

if (fork == 0) {#第二个子进程写到WRITER #11
	close READER; #12
	select WRITER; $| = 1; #13
	my $result = fibonacci($arg); #14
	exit 0; #15
} #16

#父进程关闭WRITER并从READER读取 #17
close WRITER; #18
print while &lt;READER&gt;; #19

sub factorial { #20
	my $target = shift; #21
	for(my $result = 1, my $i = 1; $i <= $target; $i++) { #22
		print "factorial($i) => ", $result *= $i, "\n"; #23
	} #24
} #25

sub fibonacci { #26
	my $target = shift; #27
	my ($a, $b) = (1, 0); #28
	for(my $i = 1; $i <= $target; $i++) { #29
		my $c = $a + $b; #30
		print "fibonacci($i) => $c\n"; #31
		($a, $b) = ($b, $c); #32
	} #33
}#34</pre>
<p>1到3行：初始化模块。开启严格的语法检查并移除并保存命令行参数。如果没有给定参数，使用默认值10。<br />
第4行：创建链接到管道。难过 <em class="highlight">pipe()</em> 来创建到管道的链接。READER 将在main进程（父进程）中使用，用来从子进程中读取它们使用WRITER写入的结果。<br />
5到10行：创建第一个子进程。调用 <em class="highlight">fork()</em> 克隆当前进程。在父进程中， <em class="highlight">fork()</em>返回非零的子进程PID。在子进程中， <em class="highlight">fork()</em>返回数字0。如果我们看到 <em class="highlight">fork()</em>的结果是0，我们知道我们在子进程里。我们关闭READER文件句柄，因为我们不需要它。我们 <em class="highlight">select()</em> WRITER，让它成为默认的输出句柄，并且通过将 <em class="highlight">$|</em> 变量的值设为真值来开启自动清除（autoflush）模式。这是必须的，以确保我们一在子进程进行写，父进程就能获取到相关信息。<br />
现在我们用命令行整型参数调用 factorial() 函数【译注：在Perl应该称之为“子例程”。由于本文大量出现“子进程”，为避免混淆，本文使用“函数”这一说法。】。之后，子进程完成其工作，于是 <em class="highlight">exit()</em> （退出）。我们的 WRITER 副本将自动关闭。<br />
11到16行：创建第二个子进程。回到父进程，我们再次调用 <em class="highlight">fork()</em> 来创建第二个子进程。这个进程调用 fibonacci() 函数而不是 factorial()。<br />
17到19：处理来自子进程的消息。在父进程中，我们之所以关闭WRITER，是因为我们已不再需要它。我们从READER每次读取一行，然后打印结果。它将包含两个子进程的输出。当最后一个子进程完成并关闭它的WRITER文件句柄时，READER返回undef，并将EOF返回给我们。我们可以 <em class="highlight">close()</em> （关闭）READER并检查其返回的代码，或者让Perl在我们退出的时候自动关闭文件句柄，就像我们的代码那样。<br />
20到25行：factorial()函数。我们通过传递参数到一个循环体来计算阶乘。在计算的每一步骤，我们打印计算结果。因为已通过 <em class="highlight">select()</em> 将WRITER设为默认文件句柄，所有这个管道的 <em class="highlight">print()</em> 语句最终将被父进程读取。<br />
26到34行：fibonacci()函数。<br />
<em class="highlight">pipe()</em> 函数也可以通过 <em class="highlight">open()</em> 创建一个连接到另一个程序的文件句柄。我们不使用这种方法，但它的大概流程是：父进程 <em class="highlight">fork()</em>，子进程使用一对文件句柄中的一个重新打开STDIN或STDOUT，然后 <em class="highlight">exec()</em> 通过参数得到程序。这里是一个示例：</p>
<pre>pipe(READER,WRITER) or die "pipe no good: $!";
my $child = fork();
die "Can't fork: $!" unless defined $child;
if ($child == 0) { # child process
   close READER;              # child doesn't need this
   open (STDOUT,">&#038;WRITER");  # STDOUT now goes to writer
   exec $cmd,$args;
   die "exec failed: $!";
}
close WRITER;  # parent doesn't need this
</pre>
<p>在代码的结尾部分，READER附属于$cmd的标准输出，跟下面的紧凑代码效果一样：</p>
<pre>open (READER,"$cmd $args |") or die "pipe no good: $!";</pre>
<h4>双向管道</h4>
<p><em class="highlight">open()</em> 和 <em class="highlight">pipe()</em> 协作创建的是单向的管道文件句柄。如果你想对其它进程同时进行读写，那就没那么幸运了。特别是，下面这个貌似具有前瞻性的代码是不能运行的：</p>
<pre>open(FH,"| $cmd |");</pre>
<p>一种方法是调用两次 <em class="highlight">pipe()</em> 方法，创建两对链接的文件句柄。一对用于父进程等待子进程，另一对用于子进程到父进程，某种程度上像双车道的高速公路。我们不想深入这种方法，但它是标准模块IPC::Open2 和 IPC::Open3 用来创建和设定附加到STDIN、STDOUT和STDERR文件句柄子进程的方法。<br />
更优雅的方法是，通过 <em class="highlight">socketpair()</em> 函数创建一个双向的管道。和 <em class="highlight">pipe()</em> 一样，它也创建两个链接的文件句柄，和前面创建的单向连接不同的是，它创建的是两个文件句柄都是可读写的。数据从一个句柄写入，再从另一个读出，反之亦然。<br />
由于 <em class="highlight">socketpair()</em> 函数和用于网络通讯的 <em class="highlight">socket()</em> 函数很相似，所以我们将在后续文章里专门讨论。</p>
<h4>管道和普通文件句柄的区别</h4>
<p>有时候需要测试一个句柄打开的是文件还是管道。Perl提供了如下测试方法：</p>
<table>
<tr>
<th>测试方法</th>
<th>描述</th>
</tr>
<tr>
<td> <em class="highlight">-p</em> </td>
<td>文件句柄是一个管道</td>
</tr>
<tr>
<td> <em class="highlight">-t</em> </td>
<td>文件句柄在终端打开</td>
</tr>
<tr>
<td> <em class="highlight">-s</em> </td>
<td>文件句柄是一个套接字（socket）</td>
</tr>
</table>
<p>如果文件句柄打开的是一个管道，那么 <em class="highlight">-p</em> 测试将返回真：</p>
<pre>print "我有一个管道!\n" if -p FILEHANDLE;</pre>
<p><em class="highlight">-t</em> 和 <em class="highlight">-s</em> 测试区别其它特殊类型的文件句柄。如果文件句柄在终端（Windows的命令行），那么 <em class="highlight">-t</em> 测试返回真。程序可以利用它来测试STDIN，以确定该程序是通过终端还是通过一个文件的标准重定向运行：</p>
<pre>print "运行于终端，禁用配置提示.\n"
      unless -t STDIN;
</pre>
<p><em class="highlight">-s</em> 测试用于标识一个文件句柄是否通过网络套接字打开：</p>
<pre>print "网络已激活.\n" if -S FH</pre>
<p>还有很多文件测试函数来获取文件大小、修改时间、所有权和其它信息。请通过 <em>perlfunc</em> 查看详情。</p>
<h4>令人不安的管道错误</h4>
<p>当你的脚本从文件句柄中打开一个管道，并且在管道结束后退出或者简单地关闭这个程序，你的程序将收到文件句柄的EOF。那么，在相反的情况下会发什么——当你的脚本在写入管道时，你的程序意外终止或过早地关闭了管道连接？<br />
我们可以写两个小的Perl脚本来找到答案。第一个脚本命名为 <i>write_ten.pl</i>，打开一个管道到第二个程序，并写入十行数据给它。这个脚本检查 <em class="highlight">print()</em> 的返回值，如果返回真值，$count 变量的值加1。当 write_ten.pl 完成后，显示 $count 的值，以标明管道成功写入的行数。第二个程序是 <i>read_three.pl</i>，从标准输入读取3行文本，然后退出。<br />
两个脚本显示在下面。值得注意的是，write_ten.pl 的管道运行在自动清除模式而不是缓存模式，所以能立刻回显每一行文本。write_ten.pl在每写入一行文本后 <em class="highlight">sleep()</em> （暂停）一秒，让 read_three.pl 偶尔报告其接收到的文本。两个脚本一起，可以让我们很容易地看到发生了什么。当我们运行 write_ten.pl里，我们可以看到下面的结果。<br />
代码片段 write_ten.pl：</p>
<pre>#!/usr/bin/perl
# write_ten.pl
##############################################
# (c) 2011 LoRui(i@lorui.com, www.lorui.com) #
##############################################

use strict; #2
use warnings;

open (PIPE, "| read_three.pl") or die "无法打开管道：$!"; #3
select PIPE; $| = 1; select STDOUT; #4

my $count = 0; #5
for(1..10) { #6
	warn "正在写入第 $_ 行\n"; #7
	print PIPE "这是第 $_ 行\n" and $count++; #8
	sleep 1; #9
} #10
close PIPE or die "无法关闭管道：$!"; #11

print "共写入 $count 行文本\n"; #12
</pre>
<p>代码片段 read_three.pl：</p>
<pre>#!/usr/bin/perl
# read_three.pl
##############################################
# (c) 2011 LoRui(i@lorui.com, www.lorui.com) #
##############################################

use strict; #2
use warnings;

for(1..3) { #3
	last unless defined(my $line = &lt;&gt;); #4
	warn "read_three.pl 获取：$line"; #5
} #6
</pre>
<p>运行结果：</p>
<pre>% write_ten.pl
正在写入第 1 行
read_three.pl 获取：这是第 1 行
正在写入第 2 行
read_three.pl 获取：这是第 2 行
正在写入第 3 行
read_three.pl 获取：这是第 3 行
正在写入第 4 行
Broken pipe
</pre>
<p>前3行都如预期的那样工作，当 write_ten.pl 尝试写入第4行文本时，脚本报告 <em class="highlight">Broken pipe</em> 错误。这表明打印“正在写入第x行”的语句并没有返回真值，即管道没有执行。<br />
当程序尝试写入到一个管道并且没有程序来读取这些数据，将返回一个PIPE异常。这个异常从PIPE信号传递给写入者。默认的，这个信号直接终止程序运行。同样的错误也发生在网络应用程序中。当发送者尝试把数据传递给远程程序时，可能造成程序退出或停止接收。<br />
为了有效的处理管道，你必须搭配一个信号处理器，这是我们的下一课题。</p>
<p><a rel="license" href="http://creativecommons.org/licenses/by/3.0/" target="_blank"><img alt="知识共享许可协议" style="border-width:0" src="http://i.creativecommons.org/l/by/3.0/88x31.png" /></a><br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by/3.0/" target="_blank">知识共享署名 3.0 Unported许可协议</a>进行许可。</p>
<img src="http://www.lorui.com/?ak_action=api_record_view&id=240&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://www.lorui.com/perl-pipes.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Perl进程（Processes）、管道（Pipes）和信号（Signals）之进程</title>
		<link>http://www.lorui.com/perl-processes.html</link>
		<comments>http://www.lorui.com/perl-processes.html#comments</comments>
		<pubDate>Tue, 25 Jan 2011 09:12:55 +0000</pubDate>
		<dc:creator>龙睿·LoRui</dc:creator>
				<category><![CDATA[来点技术]]></category>
		<category><![CDATA[exec()]]></category>
		<category><![CDATA[fork()]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[Pipes]]></category>
		<category><![CDATA[Processes]]></category>
		<category><![CDATA[Signals]]></category>
		<category><![CDATA[system()]]></category>
		<category><![CDATA[信号]]></category>
		<category><![CDATA[管道]]></category>
		<category><![CDATA[进程]]></category>

		<guid isPermaLink="false">http://www.lorui.com/?p=236</guid>
		<description><![CDATA[本系列文章讨论Perl的三个关键功能：进程（Processes）、管道（Pipes）和信号（Signals）。通过建立一个新进程，Perl程序可以运行另一个程序甚至是它自己的拷贝。管道允许Perl脚本刚其它进行交换数据，而信号使Perl脚本监视和控制其它进程成为可能。本文讨论的是其中的：进程。 进程（Processes） UNIX、VMS、Windows NT/2000以及其它现代操作系统都是多任务的。它们能同时运行多个程序，每个程序运行在独立的线程运行，称为进程。在多核的电脑中，进程实际上是运行在不同的CPU上同时运行。而在单核的电脑中，操作系统在多个进程中快速切换，每次执行一小部分，使进程看上去是同时运行的， 网络应用程序通过需要同时做两个或两个以上的事情。例如，服务器通常需要立刻处理客户端的请求，与此同时，还需要监视新的请求。多任务大大简化了程序开发，因为它允许你为应用程序所有操作启动新进程。几乎每个进程都是独立的，允许一个进程继续它的工作而不需要担心它是干什么的。 Perl支持两种多任务。一种基于传统UNIX多重处理（multiprocessing）模式，允许当前进程调用fork()函数克隆它自己。fork()执行后，将会有两个各方面几乎一致的进程。一个进程执行一个任务，另一个执行另一个任务。 另一种基于更现代的理念“线程（thread）”，将所有任务放在一个进程里面。无论如何，单个程序可以有多个线程，它们是相互独立的。 本文将介绍fork()以及与进程有关的变量和函数。我们将在以后的文章里讨论多线程（multithreading ）。 fork()函数 fork()函数在所有的UNIX版本的Perl里有效，以及VMS和OS/2。Perl 5.6（及更高版本的Perl）支持Microsoft Windows平台的fork()函数，可惜的是，不支持Macintosh。 Perl的fork()函数没有参数并返回一个数字作为结果。当fork()被调用的时候，它生成一个进程的精确副本。这个副本称为子进程（child），共享当前的值和变量，文件句柄（包含标准I/O缓存的数据）以及其它数据结构。事实上，调用fork()的副本进程都有一样的内存。就好像科幻电影里演的一个人无意中走进克隆出来的小房间里。 要保证和平共处，必须清楚哪个是父进程哪个是子进程。系统里所有的进程都有一个唯一的正整数，称为进程ID（process ID）或PID。 调用fork()函数之后，父进程和子进程判断这个函数的返回值。在父进程里，fork()返回子进程的PID。在子进程里，fork()返回数字0。根据这个返回值来判断是否为父进程。 $pid = fork() 创建一个新进程。在父进程中返回子进程的PID，在子进程中返回0。如果发生错误（比如，内存不足）返回undef，并将$!设为对应的错误信息 如果父进程和子进程想与其它进程通讯，可借助管道（pipe）或者通过共用存储器（shared memory）。父进程和子进程可以发送各自的PID给kill()函数。父进程通过fork()函数的返回值来获取子程序的PID，子进程可以通过调用getppid()函数来获得父进程的PID。进程可以通过特殊的变量$$来获得自己的PID。 $pid = getppid() 返回父进程的PID。每个Perl都有一个父进程，即使它通过命令行直接启动（此时，它的父进程是shell进程）。 $$ $$变量存储着当前进程的PID。它是只读的。 kill()函数将在后面的“信号”一节进行讨论。 正如你希望的那样，一个子进程也可以有它自己的 fork()，创建一个孙进程。原来的父进程可以再次 fork()，当然它的子进程和孙进程也一样可以。这样，Perl脚本可以创建一批完整（而友好的，希望如此）的进程。除非明确操作，这批进程属于同一个进程组（process group）。 每个进程组有一个唯一的ID，通常与共同的祖先（第一个使用 fork()的进程）的PID相同。这个值可以通过 getpgrp()来获得： $processid = getpgrp([$pid]) 如果指定了$pid， getpgrp() 函数返回对应的组ID。如果没有指定PID，返回当前进程所在的进程组的ID。 进程组中的每个成员共享所有从父进程创建时，拥有的文件句柄。特别是，共享 STDIN、 STDOUT 和 。可以在任何子进程中修改关闭文件句柄，或者重新打开它。系统一直保持跟踪：子进程打开文件句柄，但没有关闭文件。直到最后的子进程时，关闭该文件句柄的拷贝。 #!/usr/bin/perl #fork.pl - 创建单个的子进程 ############################################## # (c) [...]]]></description>
			<content:encoded><![CDATA[<p>本系列文章讨论Perl的三个关键功能：<a href="perl-processes.html">进程（Processes）</a>、<a href="perl-pipes.html">管道（Pipes）</a>和<a href="perl-signals.html">信号（Signals）</a>。通过建立一个新进程，Perl程序可以运行另一个程序甚至是它自己的拷贝。管道允许Perl脚本刚其它进行交换数据，而信号使Perl脚本监视和控制其它进程成为可能。本文讨论的是其中的：进程。</p>
<h3>进程（<em class="highlight">Processes</em>）</h3>
<p>UNIX、VMS、Windows NT/2000以及其它现代操作系统都是多任务的。它们能同时运行多个程序，每个程序运行在独立的线程运行，称为进程。在多核的电脑中，进程实际上是运行在不同的CPU上同时运行。而在单核的电脑中，操作系统在多个进程中快速切换，每次执行一小部分，使进程看上去是同时运行的，<span id="more-236"></span></p>
<p>网络应用程序通过需要同时做两个或两个以上的事情。例如，服务器通常需要立刻处理客户端的请求，与此同时，还需要监视新的请求。多任务大大简化了程序开发，因为它允许你为应用程序所有操作启动新进程。几乎每个进程都是独立的，允许一个进程继续它的工作而不需要担心它是干什么的。</p>
<p>Perl支持两种多任务。一种基于传统UNIX多重处理（multiprocessing）模式，允许当前进程调用fork()函数克隆它自己。fork()执行后，将会有两个各方面几乎一致的进程。一个进程执行一个任务，另一个执行另一个任务。</p>
<p>另一种基于更现代的理念“线程（thread）”，将所有任务放在一个进程里面。无论如何，单个程序可以有多个线程，它们是相互独立的。</p>
<p>本文将介绍fork()以及与进程有关的变量和函数。我们将在以后的文章里讨论多线程（multithreading ）。</p>
<h4>fork()函数</h4>
<p>fork()函数在所有的UNIX版本的Perl里有效，以及VMS和OS/2。Perl 5.6（及更高版本的Perl）支持Microsoft Windows平台的fork()函数，可惜的是，不支持Macintosh。</p>
<p>Perl的fork()函数没有参数并返回一个数字作为结果。当fork()被调用的时候，它生成一个进程的精确副本。这个副本称为子进程（child），共享当前的值和变量，文件句柄（包含标准I/O缓存的数据）以及其它数据结构。事实上，调用fork()的副本进程都有一样的内存。就好像科幻电影里演的一个人无意中走进克隆出来的小房间里。</p>
<p>要保证和平共处，必须清楚哪个是父进程哪个是子进程。系统里所有的进程都有一个唯一的正整数，称为进程ID（process ID）或PID。</p>
<p>调用fork()函数之后，父进程和子进程判断这个函数的返回值。在父进程里，fork()返回子进程的PID。在子进程里，fork()返回数字0。根据这个返回值来判断是否为父进程。</p>
<div class="code_syntax">
<h5>$pid = fork()</h5>
<p>创建一个新进程。在父进程中返回子进程的PID，在子进程中返回0。如果发生错误（比如，内存不足）返回undef，并将$!设为对应的错误信息
</p></div>
<p>如果父进程和子进程想与其它进程通讯，可借助管道（pipe）或者通过共用存储器（shared memory）。父进程和子进程可以发送各自的PID给kill()函数。父进程通过fork()函数的返回值来获取子程序的PID，子进程可以通过调用getppid()函数来获得父进程的PID。进程可以通过特殊的变量$$来获得自己的PID。</p>
<div class="code_syntax">
<h5>$pid = getppid()</h5>
<p>返回父进程的PID。每个Perl都有一个父进程，即使它通过命令行直接启动（此时，它的父进程是shell进程）。</p>
<h5>$$</h5>
<p>$$变量存储着当前进程的PID。它是只读的。
</p></div>
<p><em class="highlight">kill()</em>函数将在后面的“<a href="perl-signals.html">信号</a>”一节进行讨论。</p>
<p>正如你希望的那样，一个子进程也可以有它自己的 <em class="highlight">fork()</em>，创建一个孙进程。原来的父进程可以再次 <em class="highlight">fork()</em>，当然它的子进程和孙进程也一样可以。这样，Perl脚本可以创建一批完整（而友好的，希望如此）的进程。除非明确操作，这批进程属于同一个进程组（process group）。 </p>
<p>每个进程组有一个唯一的ID，通常与共同的祖先（第一个使用 <em class="highlight">fork()</em>的进程）的PID相同。这个值可以通过 <em class="highlight">getpgrp()</em>来获得：</p>
<div class="code_syntax">
<h5>$processid = getpgrp([$pid])</h5>
<p>	如果指定了$pid， <em class="highlight">getpgrp()</em> 函数返回对应的组ID。如果没有指定PID，返回当前进程所在的进程组的ID。
</div>
<p>进程组中的每个成员共享所有从父进程创建时，拥有的文件句柄。特别是，共享 <em class="highlight">STDIN</em>、 <em class="highlight">STDOUT</em> 和 <em class="highlight"></em>。可以在任何子进程中修改关闭文件句柄，或者重新打开它。系统一直保持跟踪：子进程打开文件句柄，但没有关闭文件。直到最后的子进程时，关闭该文件句柄的拷贝。</p>
<pre>#!/usr/bin/perl
#fork.pl - 创建单个的子进程
##############################################
# (c) 2011 LoRui(i@lorui.com, www.lorui.com) #
##############################################

use strict;
use warnings;

print "PID = $$\n";

my $child = fork();
die "无法创建子进程：$!" unless defined $child;

if($child > 0) { #父进程
	print "父进程：PID = $$, 子进程 = $child\n";
} else { #子进程
	my $ppid = getppid();
	print "子进程：PID = $$, 父进程 = $ppid\n";
}
</pre>
<p>通过检查 $child 来确定，我们的程序是以父进程运行还是子进程。如果 $child 非零，我们进入父进程处理：我们打印出了我们的PID和 $child 的内容，以及子进程的PID。</p>
<p>如果 $child 是零，那我们运行在子进程里。我们通过调用 <em class="highlight">getppid()</em>来获取父进程的PID，并将其输出。</p>
<p>该代码的执行结果：</p>
<pre>% fork.pl
PID=372
父进程: PID=372, 子进程=373
子进程:  PID=373, 父进程=372
</pre>
<p>注意，在Windows平台运行此脚本，将会出现类似错误：</p>
<pre>The getppid function is unimplemented at fork.pl line 18.</pre>
<p>原因在于，Windows平台的Perl并没有实现 <em class="highlight">getppid()</em>函数，请使用UNIX/Linux测试本代码。</p>
<h4><em class="highlight">system()</em> 和 <em class="highlight">exec()</em>函数</h4>
<p>在Perl启动子进程的另一种方法是 <em class="highlight">system()</em>。<em class="highlight">system()</em>将另一个程序作为子进程运行，等待其完成，并返回结果。如果成功， <em class="highlight">system()</em>返回0（注意，这和一般的Perl习惯不同。【译注：一般地，在Perl（及其它类C语言）里，0表示否定；非0表示肯定】）。如果子程序没有启动，返回-1。或者返回其它错误代码。有关错误代码的完整解释，请通过perlvar查看$?变量的详细介绍。</p>
<div class="code_syntax">
<h5>$status = system('command and arguments')</h5>
<h5>$status = system('command', 'and', 'arguments')</h5>
<p>	<em class="highlight">system()</em>函数将命令（command）作为一个子进程来执行，并等待它退出。该命令和它的参数（arguments）可以在单个字符串里指定，也可以将它们作为列表的元素来指定。在第一种方式里，字符串将完整无缺地传递给shell（【译注：UNIX/Linux的shell；Windows平台的命令提示符(cmd.exe)】）。它允许你执行包含shell元字符（shell metacharacters）（如输入/输出，重定向），但可能无意中执行了shell命令。后一种方式允许你执行带有空白字符分隔的参数，shell元字符和其它特殊字符，但不解析元字符。
</div>
<p><em class="highlight">exec()</em>函数跟 <em class="highlight">system()</em>很像，但替换当前进程为指定的命令。如果成功，没有任何返回值，因为进程已经结束。新进行将拥有与旧的那个同样的PID，并共享相同的 <em class="highlight">STDIN</em>、 <em class="highlight">STDOUT</em> 和 <em class="highlight">STDERR</em>文件句柄。无论如何，其它已打开的文件句柄将自动关闭。（你可以通过改变 <em class="highlight">$~F</em> 变量的值来让 <em class="highlight">exec()</em>保持已打开的文件句柄。详细内容请查看perlvar文档。）</p>
<div class="code_syntax">
<h5>$status = exec('command and arguments')</h5>
<h5>$status = exec('command', 'and', 'arguments')</h5>
<p>	<em class="highlight">exec()</em>执行一个命令，并替换当前进程。只有出现错误的时候，它才返回状态代码。融不会返回任何结果。单值形式和列表形式与 <em class="highlight">system()</em> 的意思一样。
</div>
<p><em class="highlight">exec()</em>通常与将一个命令作为子进程运行，处理完一些指定的步骤的<em class="highlight">fork()</em>配合使用。例子，在上面fork.pl的代码片段后面，子进程在一个文件上重新打开 <em class="highlight">STDOUT</em>，然后调用 <em class="highlight">exec()</em> 来运行 <em>ls -l</em> 命令。在UNIX系统上，这个命令获取一个很长的列表。其效果是，在后台运行 <em>ls -l</em> 命令，并将其输出写到指定的文件中。代码如下：</p>
<pre>my $child = fork();
die "Can't fork: $!" unless defined $child;
if ($child == 0) { # we are in the child now
  open (STDOUT,">log.txt") or die "open() error: $!";
  exec ('ls','-l');
  die "exec error(): $!"; # shouldn't get here
}
</pre>
<p><a rel="license" href="http://creativecommons.org/licenses/by/3.0/" target="_blank"><img alt="知识共享许可协议" style="border-width:0" src="http://i.creativecommons.org/l/by/3.0/88x31.png" /></a><br />本作品采用<a rel="license" href="http://creativecommons.org/licenses/by/3.0/" target="_blank">知识共享署名 3.0 Unported许可协议</a>进行许可。</p>
<img src="http://www.lorui.com/?ak_action=api_record_view&id=236&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://www.lorui.com/perl-processes.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>Perl实现序列化(Serialize)和反序列化(Unserialize)</title>
		<link>http://www.lorui.com/perl-serialize-unserialize.html</link>
		<comments>http://www.lorui.com/perl-serialize-unserialize.html#comments</comments>
		<pubDate>Mon, 17 Jan 2011 07:51:42 +0000</pubDate>
		<dc:creator>龙睿·LoRui</dc:creator>
				<category><![CDATA[来点技术]]></category>
		<category><![CDATA[Perl]]></category>
		<category><![CDATA[retrieve]]></category>
		<category><![CDATA[serialize]]></category>
		<category><![CDATA[storable]]></category>
		<category><![CDATA[store]]></category>
		<category><![CDATA[unserialize]]></category>
		<category><![CDATA[反序列化]]></category>
		<category><![CDATA[序列化]]></category>

		<guid isPermaLink="false">http://www.lorui.com/?p=231</guid>
		<description><![CDATA[序列化（Serialization）通常用于实现将数据和对象存储到一个文件中。生成的字节数据可以重复使用，来生成已保存对象的副本。 处理时间可以说是序列化的优势之一。假设，解析一个文件并从该文件中生成对象。如果这个文件很大，它将会花费一定的时候来处理。 经序列化的对象允许你在这个文件里重复读取对象，从而加快后续处理。 本文将快速演示如何在Perl中序列化和反序列化哈希（hash）、数组（array）、标量（scalaer）和对象（object）。 Perl可以通过Storable模块实现序列化。 哈希（hash）、数组（array）、标量（scalaer）可以通过下面的方式进行序列化： #!/usr/bin/perl ############################################## # (c) 2011 LoRui(i@lorui.com, www.lorui.com) # ############################################## use strict; use warnings; use Storable; #定义一个Hash，用于测试序列化 my %student = ( 'No' => '00342001010103037', 'Name' => 'LoRui' ); store \%student, 'student.lorui'; #序列化，将其保存 print "==== student ====\n"; &#038;dumper(\%student); #输出定义的Hash my %person = %{retrieve 'student.lorui'}; #读取并反序列化，并将取赋给一个变量 print "\n==== person ====\n"; &#038;dumper(\%person); #输出反序列化Hash [...]]]></description>
			<content:encoded><![CDATA[<p><b>序列化（Serialization）</b>通常用于实现将数据和对象存储到一个文件中。生成的字节数据可以重复使用，来生成已保存对象的副本。<br />
处理时间可以说是序列化的优势之一。假设，解析一个文件并从该文件中生成对象。如果这个文件很大，它将会花费一定的时候来处理。<br />
经序列化的对象允许你在这个文件里重复读取对象，从而加快后续处理。<br />
本文将快速演示如何在Perl中序列化和反序列化哈希（hash）、数组（array）、标量（scalaer）和对象（object）。<span id="more-231"></span><br />
Perl可以通过Storable模块实现序列化。</p>
<p>哈希（hash）、数组（array）、标量（scalaer）可以通过下面的方式进行序列化：</p>
<pre>#!/usr/bin/perl

##############################################
# (c) 2011 LoRui(i@lorui.com, www.lorui.com) #
##############################################

use strict;
use warnings;

use Storable;

#定义一个Hash，用于测试序列化
my %student = (
	'No' => '00342001010103037',
	'Name' => 'LoRui'
);

store \%student, 'student.lorui'; #序列化，将其保存

print "==== student ====\n";
&#038;dumper(\%student); #输出定义的Hash

my %person = %{retrieve 'student.lorui'}; #读取并反序列化，并将取赋给一个变量

print "\n==== person ====\n";
&#038;dumper(\%person); #输出反序列化Hash

sub dumper {
	my $hash_ref = shift;
	while(my($key, $val) = each(%$hash_ref)) {
		print "$key => $val\n";
	}
}
</pre>
<p>输出结果：</p>
<pre>
==== student ====
No => 00342001010103037
Name => LoRui

==== person ====
No => 00342001010103037
Name => LoRui
</pre>
<p>序列化一个对象（object），可参考以下方法：</p>
<pre>#!/usr/bin/perl

##############################################
# (c) 2011 LoRui(i@lorui.com, www.lorui.com) #
##############################################

use strict;
use warnings;
use Storable;

package LoRui;

sub new {
	my $class = shift;
	my $this = {};
	bless $this, $class;
	return $this;
}

sub set_value {
	my ($this, $key, $val) = @_;
	$this->{$key} = $val;
}

sub get_value {
	my ($this, $key) = @_;
	return $this->{$key} || 'Undefined';
}

sub show {
	my $this = shift;
	while(my ($key, $val) = each(%$this)) {
		print "$key => $val\n";
	}
}

package main;
my $data_file = 'serialized_obj.lorui';

my $obj = LoRui->new;
$obj->set_value('from', 'LoRui.com');

store $obj, $data_file;

print "### OBJ ###\n";
$obj->show;
print $obj->get_value('to'), "\n";

my $obj_r = retrieve $data_file;
print "\n### OBJ_R ###\n";
$obj_r->show;
print $obj->get_value('to'), "\n";
</pre>
<p>输出结果：</p>
<pre>### OBJ ###
from => LoRui.com
Undefined

### OBJ_R ###
from => LoRui.com
Undefined</pre>
<img src="http://www.lorui.com/?ak_action=api_record_view&id=231&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://www.lorui.com/perl-serialize-unserialize.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
		<item>
		<title>捐了1美金：20年把甲骨文小篆字形搬上网</title>
		<link>http://www.lorui.com/donate-to-chineseetymology.html</link>
		<comments>http://www.lorui.com/donate-to-chineseetymology.html#comments</comments>
		<pubDate>Thu, 13 Jan 2011 13:43:42 +0000</pubDate>
		<dc:creator>龙睿·LoRui</dc:creator>
				<category><![CDATA[闲云野鹤]]></category>
		<category><![CDATA[Chinese Etymology]]></category>
		<category><![CDATA[Richard Sears]]></category>
		<category><![CDATA[小篆]]></category>
		<category><![CDATA[捐赠]]></category>
		<category><![CDATA[甲骨文]]></category>
		<category><![CDATA[金文]]></category>

		<guid isPermaLink="false">http://www.lorui.com/?p=227</guid>
		<description><![CDATA[甲骨文、金文、小篆这些古汉字，给你的印象是什么？广东话发音、台湾闽南话发音和说文解字你接触过哪些？有人花了20年心血把这些中华民族瑰宝整理完整，并搬上互联网，而这个人竟然是外国人！ 一个名叫Richard Sears的外国人，架设了一个名为Chinese Etymology网站，提供了上述所有的内容。 你在这里看到的是我过去20年努力工作的成果， 目标是要把汉字的字源资料放在国际网路上，供大家使用。 请捐赠，这样我就可以在线上保持提供，和及时更新这些资料。 这些资料是我免费提供的，而且没有广告干扰。 1美金，略表心意，感谢他20年的坚持；更感谢他这个外国人帮中华民族做的贡献。感谢、感动、感激……之余，你和我是不是该反省一点什么了？ 信息来源：http://www.cnbeta.com/articles/132238.htm]]></description>
			<content:encoded><![CDATA[<p>甲骨文、金文、小篆这些古汉字，给你的印象是什么？广东话发音、台湾闽南话发音和说文解字你接触过哪些？有人花了20年心血把这些中华民族瑰宝整理完整，并搬上互联网，而这个人竟然是外国人！<span id="more-227"></span><br />
一个名叫Richard Sears的外国人，架设了一个名为<a href="http://www.chineseetymology.org/" target="_blank">Chinese Etymology</a>网站，提供了上述所有的内容。</p>
<pre>你在这里看到的是我过去20年努力工作的成果，
目标是要把汉字的字源资料放在国际网路上，供大家使用。
请捐赠，这样我就可以在线上保持提供，和及时更新这些资料。
这些资料是我免费提供的，而且没有广告干扰。</pre>
<p>1美金，略表心意，感谢他20年的坚持；更感谢他这个外国人帮中华民族做的贡献。感谢、感动、感激……之余，你和我是不是该反省一点什么了？<br />
<small>信息来源：http://www.cnbeta.com/articles/132238.htm</small></p>
<img src="http://www.lorui.com/?ak_action=api_record_view&id=227&type=feed" alt="" />]]></content:encoded>
			<wfw:commentRss>http://www.lorui.com/donate-to-chineseetymology.html/feed</wfw:commentRss>
		<slash:comments>0</slash:comments>
		</item>
	</channel>
</rss>

