从程序员的视角来看,我们希望能够以处理普通Erlang程序的方式来处理Erlang系统外的所有活动。为了创造这样的效果,我们需要将Erlang系统外的对象伪装成普通的Erlang进程。端口(Port),一种为Erlang系统和外部世界提供面向字节的通讯信道的抽象设施,就是为此而设计的。
执行open_port(PortName,PortSettings)可以创建一个端口,其行为与进程类似。执行open_port的进程称为该端口的连接进程。需要发送给端口的消息都应发送至连接进程。外部对象可以通过向与之关联的端口写入字节序列的方式向Erlang系统发送消息,端口将给连接进程发送一条包含该字节序列的消息。
系统中的任意进程都可以与一个端口建立链接,端口和Erlang进程间的EXIT信号导致的行为与普通进程的情况完全一致。端口只理解三种消息:
PidC必须是一个连接进程的Pid。这些消息的含义如下:{command,Data} close{connect,Pid1}关闭端口。端口将向连接进程回复一条{Port, closed}消息。
将端口的连接进程换位Pid1。端口将向先前的连接进程发送一条{Port, connected}消息。
此外,连接进程还可以通过以下方式接收数据消息:
- receive
- {Port, {data, Data}} ->
- ...
- end
在这一节中,我们将描述两个使用端口的程序:第一个是在Erlang工作空间内部的Erlang进程;第二个是在Erlang外部执行的C程序。
{spawn,Command}Atom启动名为Command的外部程序或驱动。Erlang驱动在附录E中有所描述。若没有找到名为Command的驱动,则将在Erlang工作空间的外部运行名为Command的外部程序。
{fd,In,Out}Atom将被认作是外部资源的名称。这样将在Erlang系统和由该原子式命名的资源之间建立一条透明的连接。连接的行为取决于资源的类型。如果Atom表示一个文件,则一条包含文件全部内容的消息会被发送给Erlang系统;向该端口写入发送消息便可向文件写入数据。
PortSettings是端口设置的列表。有效的设置有:{packet,N} stream令Erlang进程得以访问任意由Erlang打开的文件描述符。文件描述符In可作为标准输入而Out可作为标准输出。该功能很少使用:只有Erlang操作系统的几种服务(shell和user)需要使用。注意该功能与仅限于UNIX系统。
use_stdio输出的消息不附带消息长度──Erlang进程和外部对象间必须使用某种私有协议。
nouse_stdio仅对{spawn, Command}形式的端口有效。令产生的(UNIX)进程使用标准输入输出(即文件标识符0和1)与Erlang通讯。
in与上述相反。使用文件描述符3、4与Erlang通讯。
out端口仅用于输入。
binary eof端口仅用于输出。
到达文件末尾后端口不会关闭并发送'EXIT'信号,而是保持打开状态并向端口的连接进程发送一条{Port, eof}消息,之后连接进程仍可向端口输出数据。
除了{spawn,Command}类型的端口默认使用usestdio外,所有_类型的端口默认都使用stream。
程序9.2定义了一个简单的Erlang进程,该进程打开一个端口并向该端口发送一串消息。与端口相连的外部对象会处理并回复这些消息。一段时间之后进程将关闭端口。
程序9.2
程序9.2中的open_port(PortName,PortSettings启动了一个外部程序。demo_server是即将运行的程序的名字。
表达式Port!{self(),{command,[1,2,3,4,5]}}向外部程序发送了五个字节(值为1、2、3、4、5)。
- 若程序收到字符串“echo”,则它会向Erlang回复“ohce”。
- 若程序收到的数据块的第一个字节是10,则它会将除第一个字节以外的所有字节翻倍后返回。
- 忽略其他数据。运行该程序后我们得到以下结果:
- erlang received from port:{data,[10,2,4,6,8,10]}
- erlang received from port:{data,[111,104,99,101]}
- true
程序9.3
程序9.3通过表达式len=read_cmd(buf)读取发送至Erlang端口的字节序列,并用write_cmd(buf,len)将数据发回Erlang。
文件描述符0用于从Erlang读取数据,而文件描述符1用于向Erlang写入数据。各个C函数的功能如下:
read_cmd(buf)write_cmd(buf,len)从Erlang读取一条命令。
read_exact(buf,len)向Erlang写入一个长度为len的缓冲区。
write_exact(buf,len)读取len个字节。
put_int16(i,s)写入len个字节。
函数read_cmd和write_cmd假设外部服务和Erlang间的协议由一个指明数据包长度的双字节包头和紧随的数据构成。如图9.1所示。
图9.1 端口通讯