从程序员的视角来看,我们希望能够以处理普通Erlang程序的方式来处理Erlang系统外的所有活动。为了创造这样的效果,我们需要将Erlang系统外的对象伪装成普通的Erlang进程。端口(Port),一种为Erlang系统和外部世界提供面向字节的通讯信道的抽象设施,就是为此而设计的。

    执行open_port(PortName,PortSettings)可以创建一个端口,其行为与进程类似。执行open_port的进程称为该端口的连接进程。需要发送给端口的消息都应发送至连接进程。外部对象可以通过向与之关联的端口写入字节序列的方式向Erlang系统发送消息,端口将给连接进程发送一条包含该字节序列的消息。

    系统中的任意进程都可以与一个端口建立链接,端口和Erlang进程间的EXIT信号导致的行为与普通进程的情况完全一致。端口只理解三种消息:

    PidC必须是一个连接进程的Pid。这些消息的含义如下:{command,Data} close
    关闭端口。端口将向连接进程回复一条{Port, closed}消息。
    {connect,Pid1}
    将端口的连接进程换位Pid1。端口将向先前的连接进程发送一条{Port, connected}消息。

    此外,连接进程还可以通过以下方式接收数据消息:

    1. receive
    2. {Port, {data, Data}} ->
    3. ...
    4. end

    在这一节中,我们将描述两个使用端口的程序:第一个是在Erlang工作空间内部的Erlang进程;第二个是在Erlang外部执行的C程序。

    {spawn,Command}
    启动名为Command外部程序或驱动。Erlang驱动在附录E中有所描述。若没有找到名为Command的驱动,则将在Erlang工作空间的外部运行名为Command的外部程序。
    Atom
    Atom将被认作是外部资源的名称。这样将在Erlang系统和由该原子式命名的资源之间建立一条透明的连接。连接的行为取决于资源的类型。如果Atom表示一个文件,则一条包含文件全部内容的消息会被发送给Erlang系统;向该端口写入发送消息便可向文件写入数据。
    {fd,In,Out}
    令Erlang进程得以访问任意由Erlang打开的文件描述符。文件描述符In可作为标准输入而Out可作为标准输出。该功能很少使用:只有Erlang操作系统的几种服务(shelluser)需要使用。注意该功能与仅限于UNIX系统。
    PortSettings是端口设置的列表。有效的设置有:{packet,N} stream
    输出的消息不附带消息长度──Erlang进程和外部对象间必须使用某种私有协议。
    use_stdio
    仅对{spawn, Command}形式的端口有效。令产生的(UNIX)进程使用标准输入输出(即文件标识符01)与Erlang通讯。
    nouse_stdio
    与上述相反。使用文件描述符34与Erlang通讯。
    in
    端口仅用于输入。
    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,则它会将除第一个字节以外的所有字节翻倍后返回。
    • 忽略其他数据。运行该程序后我们得到以下结果:
    1. erlang received from port:{data,[10,2,4,6,8,10]}
    2. erlang received from port:{data,[111,104,99,101]}
    3. true

    程序9.3

    程序9.3通过表达式len=read_cmd(buf)读取发送至Erlang端口的字节序列,并用write_cmd(buf,len)将数据发回Erlang。

    文件描述符0用于从Erlang读取数据,而文件描述符1用于向Erlang写入数据。各个C函数的功能如下:

    read_cmd(buf)
    从Erlang读取一条命令。
    write_cmd(buf,len)
    向Erlang写入一个长度为len的缓冲区。
    read_exact(buf,len)
    读取len个字节。
    write_exact(buf,len)
    写入len个字节。
    put_int16(i,s)

    函数read_cmdwrite_cmd假设外部服务和Erlang间的协议由一个指明数据包长度的双字节包头和紧随的数据构成。如图9.1所示。

    图9.1 端口通讯