在Erlang分布式中,各个节点之间的通讯都是通过Erlang的EPMD (Erlang Port Mapper Daemon)来实现的。首先,节点在EPMD注册节点名称,然后客户端或者另外一个节点与注册的节点通讯时,发送请求数据到EPMD,然后EPMD根据请求内容返回相应的相应信息,客户端或者另外一个节点再根据返回信息与服务节点通讯,详细的Erlang分布式协议可以通过http://www.erlang.org/doc/apps/erts/erl_dist_protocol.html#id2285540得到。
在这里,主要是简单的介绍一下,客户端与服务节点通讯前是如何发送请求信息到EPMD,并获取服务节点的Port后,再与服务节点实现TCP通讯的。
客户端是通过端口号4369来实现与EPMD通讯的。在发送请求数据时,每个请求中的前两个字节是代表本次请求的内容长度(如a图)。在请求获取服务节点端口号中,请求内容则是占有一个字节的请求标识符和服务节点名称(b图)。
a、请求数据格式
2 | n |
Length | Request |
b、请求内容格式
1 | N |
122 | NodeName |
当EPMD接受请求后,返回响应信息,响应信息的第一个字节是一个标识符119,第二个字节是返回结果,如果结果大于0,则说明未找到对应服务节点;结果等于0,则后面接着的是对应的服务节点信息。
a、返回结果大于0,返回的数据格式
1 | 1 |
119 | Result |
b、返回结果等于0,返回的数据格式
1 | 1 | 2 | 1 | 1 | 2 | 2 | 2 | Nlen | 2 | Elen |
119 | Result | PortNo | NodeType | Protocol | HighestVersion | LowestVersion | Nlen | NodeName | Elen | Extra |
上面的所有第一行中的数字都是表示的字节数。
通过上面的简单介绍,已经可以初步了解其实现原理了,为了更好的理解,下面是用C#来模拟实现。
class EPMD
{
private string _alive;
private string _host;
private string _node;
public EPMD(System.String nodeName)
{
int i = nodeName.IndexOf((System.Char)'@', 0);
if (i < 0)
{
_alive = nodeName;
_host = null;
}
else
{
_alive = nodeName.Substring(0, (i) - (0));
_host = nodeName.Substring(i + 1, (nodeName.Length) - (i + 1));
}
if (_alive.Length > 0xff)
{
_alive = _alive.Substring(0, (0xff) - (0));
}
_node = _alive + "@" + _host;
}
public int GetPort()
{
TcpClient client = new TcpClient(_host, 4369);
NetworkStream stream = client.GetStream();
byte[] buf = new Byte[3 + _alive.Length];
// 前两个字节写入本次请求数据长度
buf[0] = 0;
buf[1] = (byte)(_alive.Length + 1);
//第三个字节写入本次请求标识符
buf[2] = 122;
// 写入请求的节点名称
byte[] data = System.Text.Encoding.UTF8.GetBytes(_alive);
for (int i = 0; i < data.Length; i++)
{
buf[i + 3] = data[i];
}
// 向EPMD发送请求数据
stream.Write(buf, 0, buf.Length);
byte[] tmpbuf = new byte[100];
// 获取响应数据
int n = stream.Read(tmpbuf, 0, 100);
// n小于0,则请求失败
if (n < 0)
{
client.Close();
Console.WriteLine("在主机上{0}未获取到EPMD相应", _host);
return 0;
}
MemoryStream mem = new MemoryStream(tmpbuf);
// 第一位,获取响应标识符
int port_resp = mem.ReadByte();
// 第二位,获取响应结果
int result = mem.ReadByte();
// 返回与节点对应的端口信息
if (result == 0)
{
// 读取2个字节数据,获取端口号
byte[] b = new byte[2];
mem.Read(b, 0, b.Length);
return ((((int)b[0] << 8) & 0xff00) + (((int)b[1]) & 0xff));
}
return 0;
}
}
static void Main(string[] args)
{
EPMD test = new EPMD("servernode@liyiqun");
int port = test.GetPort();
Console.ReadKey(true);
}
如果不确定自己获取的端口号是否正确,还可以在erlang shell中通过执行net_adm:names().函数来验证是否一致。