首页
学习
活动
专区
工具
TVP
发布
精选内容/技术社群/优惠产品,尽在小程序
立即前往

4.端口 | 4. Ports

本节概述了如何previous section通过使用端口来解决示例问题的示例。

该场景如下图所示:

图4.1:端口通信

4.1 Erlang程序

Erlang和C之间的所有通信都必须通过创建端口来建立。创建端口的Erlang进程被称为端口的连接进程。所有来往于端口的通信都必须经过连接过程。如果连接的进程终止,端口也会终止(即使是正确编写了外部程序,外部程序也会终止)。

通过调用BIFopen_port/2来创建端口,{spawn,ExtPrg}作为第一个参数。该字符串ExtPrg是外部程序的名称,包括任何命令行参数。第二个参数是一个选项列表,在本例中只有{packet,2}。该选项表示将使用2个字节的长度指示符来简化C和Erlang之间的通信。Erlang端口会自动添加长度指示符,但这必须在外部C程序中明确完成。

该过程还设置为陷阱出口,可以检测外部程序的故障:

代码语言:javascript
复制
-module(complex1).
-export([start/1, init/1]).

start(ExtPrg) ->
  spawn(?MODULE, init, [ExtPrg]).

init(ExtPrg) ->
  register(complex, self()),
  process_flag(trap_exit, true),
  Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
  loop(Port).

接下来可以实现complex1:foo/1complex1:bar/1函数了。这两个函数都向complex进程发送消息并收到以下回复:

代码语言:javascript
复制
foo(X) ->
  call_port({foo, X}).
bar(Y) ->
  call_port({bar, Y}).

call_port(Msg) ->
  complex ! {call, self(), Msg},
  receive
    {complex, Result} ->
      Result
  end.

complex过程执行以下操作:

  • 将消息编码为一系列字节。
  • 将其发送到端口。
  • 等待回复。
  • 解码答复。
  • 将其发回给调用者:
代码语言:javascript
复制
loop(Port) ->
  receive
    {call, Caller, Msg} ->
      Port ! {self(), {command, encode(Msg)}},
      receive
        {Port, {data, Data}} ->
          Caller ! {complex, decode(Data)}
      end,
      loop(Port)
  end.

假设C函数的参数和结果都小于256,则采用简单的编码/解码方案。在这个方案中,foo由字节1 bar表示,由2表示,并且参数/结果也由单个字节表示:

代码语言:javascript
复制
encode({foo, X}) -> [1, X];
encode({bar, Y}) -> [2, Y].

decode([Int]) -> Int.

生成的Erlang程序包括停止端口和检测端口故障的功能如下:

代码语言:javascript
复制
-module(complex1).
-export([start/1, stop/0, init/1]).
-export([foo/1, bar/1]).

start(ExtPrg) ->
    spawn(?MODULE, init, [ExtPrg]).
stop() ->
    complex ! stop.

foo(X) ->
    call_port({foo, X}).
bar(Y) ->
    call_port({bar, Y}).

call_port(Msg) ->
    complex ! {call, self(), Msg},
    receive
	{complex, Result} ->
	    Result
    end.

init(ExtPrg) ->
    register(complex, self()),
    process_flag(trap_exit, true),
    Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    loop(Port).

loop(Port) ->
    receive
	{call, Caller, Msg} ->
	    Port ! {self(), {command, encode(Msg)}},
	    receive
		{Port, {data, Data}} ->
		    Caller ! {complex, decode(Data)}
	    end,
	    loop(Port);
	stop ->
	    Port ! {self(), close},
	    receive
		{Port, closed} ->
		    exit(normal)
	    end;
	{'EXIT', Port, Reason} ->
	    exit(port_terminated)
    end.

encode({foo, X}) -> [1, X];
encode({bar, Y}) -> [2, Y].

decode([Int]) -> Int.

4.2 C程序

在C方面,有必要编写用于接收和发送来自/来自Erlang的2个字节长度指示符的数据的函数。默认情况下,C程序将从标准输入(文件描述符0)读取并写入标准输出(文件描述符1)。这些功能的示例,read_cmd/1并且write_cmd/2,如下:

代码语言:javascript
复制
/* erl_comm.c */

typedef unsigned char byte;

read_cmd(byte *buf)
{
  int len;

  if (read_exact(buf, 2) != 2)
    return(-1);
  len = (buf[0] << 8) | buf[1];
  return read_exact(buf, len);
}

write_cmd(byte *buf, int len)
{
  byte li;

  li = (len >> 8) & 0xff;
  write_exact(&li, 1);
  
  li = len & 0xff;
  write_exact(&li, 1);

  return write_exact(buf, len);
}

read_exact(byte *buf, int len)
{
  int i, got=0;

  do {
    if ((i = read(0, buf+got, len-got)) <= 0)
      return(i);
    got += i;
  } while (got<len);

  return(len);
}

write_exact(byte *buf, int len)
{
  int i, wrote = 0;

  do {
    if ((i = write(1, buf+wrote, len-wrote)) <= 0)
      return (i);
    wrote += i;
  } while (wrote<len);

  return (len);
}

请注意,stdin并且stdout是用于缓冲输入/输出,并且必须被用于与二郎的通信。

main函数中,C程序将监听来自Erlang的消息,并根据所选的编码/解码方案,使用第一个字节来确定要调用哪个函数,并将第二个字节作为该函数的参数。然后调用该函数的结果将被发送回Erlang:

代码语言:javascript
复制
/* port.c */

typedef unsigned char byte;

int main() {
  int fn, arg, res;
  byte buf[100];

  while (read_cmd(buf) > 0) {
    fn = buf[0];
    arg = buf[1];
    
    if (fn == 1) {
      res = foo(arg);
    } else if (fn == 2) {
      res = bar(arg);
    }

    buf[0] = res;
    write_cmd(buf, 1);
  }
}
      

注意C程序在while-loop中,检查返回值read_cmd/1。这是因为C程序必须检测端口何时关闭和终止。

4.3运行示例

第一步。编译C代码:

代码语言:javascript
复制
unix> gcc -o extprg complex.c erl_comm.c port.c

第二步。启动Erlang并编译Erlang代码:

代码语言:javascript
复制
unix> erl
Erlang (BEAM) emulator version 4.9.1.2

Eshell V4.9.1.2 (abort with ^G)
1> c(complex1).
{ok,complex1}

第三步。运行示例:

代码语言:javascript
复制
2> complex1:start("extprg").
<0.34.0>
3> complex1:foo(3).
4
4> complex1:bar(5).
10
5> complex1:stop().
stop

扫码关注腾讯云开发者

领取腾讯云代金券

http://www.vxiaotou.com