Realize the connection between Golang and Erlang (Port)

Keywords: Go Erlang network Unix emulator

title: realize the connection between Golang and Erlang (Port)

categories: Golang

In Erlang, there are many ways to interact with other languages. The common ways are

  1. Interaction using TCP protocol
  2. Using Port
  3. Using ERL? Interface to realize
  4. CNode
  5. NIF

The latter several kinds of difficulties are all there, but also use a more complex C/C + +, and more prone to problems. The way of TCP is through network protocol, and I don't like it very much, so I'll leave Port to connect to Erlang server.

The introduction of Port in Erlang's official documents

Using Port in Erlang is very simple, which is actually to interact with external programs through standard input and output streams.

Now I still use the code in the official document and only make some modifications to realize the interaction between Erlang and Golang.

%% complex1.erl

-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),
    %% Be careful{packet, 2}Represents the transmission in 2 bytes
    %% Port = open_port({spawn, ExtPrg}, [{packet, 2}]),
    %% You don't need to process this header here. You don't need this parameter
    Port = open_port({spawn, ExtPrg}, []),
    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.

%% Code, convert data to binary
encode({foo, X}) -> <<1:8, X:8>>;
encode({bar, Y}) -> <<2:8, Y:8>>].

%% It's a little special here,
decode(Data) -> erlang:list_to_binary(Data).

For Golang, you need to read the data of the standard input stream. Here I use bufio to process it (in fact, it's not necessary, hahaha). Because the header of the data stream is not used to record the length of the data, the data length is set to the byte array of 2 by default.

// port.go

package main

import (
    "bufio"
    "os"
)

func main() {
    reader := bufio.NewReader(os.Stdin)
    writer := bufio.NewWriter(os.Stdout)
    for {
        buff := make([]byte, 2)
        _, err := reader.Read(buff)
        if err != nil {
            panic(err.Error())
        }
        switch int(buff[0]) {
        case 1:
            buff[1] = byte(foo(int(buff[1])))
        case 2:
            buff[1] = byte(bar(int(buff[1])))
        }
        writer.Write(buff[1:2])
        // It should be noted here that Flush should be used for processing, otherwise the erlang end will not receive the information
        writer.Flush()
    }
}

func foo(num int) int {
    return num + 1
}

func bar(num int) int {
    return 2 * num
}

Use

Step 1 compile golang

go build port.go

Step 2 run Erlang virtual machine

unix> erl
Erlang (BEAM) emulator version 4.9.1.2

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

Step 3 run

2> complex1:start("extprg").
<0.34.0>
3> complex1:foo(3).
4
4> complex1:bar(5).
10
5> complex1:stop().
stop

Posted by tha_mink on Fri, 06 Dec 2019 14:36:07 -0800