UDP是一个比较常用的传输层协议,erlang标准库中提供了 gen_dup
模块,要掌握UDP协议,我们首先要熟悉这个模块。
函数
首先看这个模块的几个公共函数:
- open <> close
- send <> recv
- connect
- controlling_process
- fdopen
open 和 close 比较好理解,就是在某个端口上打开一个socket,以及关闭某个socket。
send 就是通过某个socket,往某个地址的某个端口发送packet。recv 就是接受某个socket中一定长度的数据。
connect 就有点奇怪了,我们知道UDP协议是无连接的。事实上这个函数并没有文档,不是暴露给普通用户使用的。
controlling_process 是为某个socket赋予一个新的归属进程。在erlang里,有很多的进程,他们之间相互传递消息来通信,socket从外界接收到的消息,自然也要先传递到某个进程里,才可以进入erlang的世界。归属进程的作用就是接收socket的消息。
fdopen 这个函数也没有文档,作用是从某个文件描述符来打开一个socket。
选项
open ,也就是打开一个socket的时候需要传入Option设置,有下列选项:
- list | binary | {mode, list | binary} 以列表还是字符串的形式接收Packet。
- {ip, Address} 当host有多个网络接口的时候,选择其中一个。
- {ifaddr, Address} 和 {ip, Address} 一样的。
- {fd, integer() >= 0} 如果有socket不是使用 gen_udp 来打开的,那么就可能需要设置一个文件描述符。
- inet6 | inet | local 设置socket的类型。
- {udp_module, module()} 覆盖默认的udp模块。
- {multicast_if, Address} 为多播socket设置本地设备。
- {multicast_loop, true | false} 为真时,多播的packets会循环地返回到本地socket。
- {multicast_ttl, Integer} 多播的TTL,默认是1.
- {add_membership, {MultiAddress, InterfaceAddress}} 加入多播群。
- {drop_membership, {MultiAddress, InterfaceAddress}} 离开多播群。
- {active, true | false | once | N} 如果为真,socket收到的所有消息会发送到归属进程。如果为假,就需要显式调用recv;once 是收到消息后,就会发送到进程,但会变为false。数值是指接收多少条数据。
- {buffer, Size} 用户层级的缓冲区大小。
- {delay_send, Boolean} 通常erlang会立刻发出给socket的消息,开启这个选项后,会等待一会儿然后集合发出。
- {deliver, port | term} 发送给归属进程的消息的格式。
- {dontroute, Boolean} 对于发出的消息是否采用路由。
- {exit_on_close, Bloolean} socket 关闭时退出归属进程。
- {header, Size} 只有当binary起作用时才有用,单位是byte。
选项太多了,下列的在之后有用到的话再来解释:
- {high_msgq_watermark, Size}
- {ipv6_v6only, Boolean}
- {linger, {true | false, Seconds}}
- {low_msgq_watermark, Size}
- {netns, Namespace :: file:filename_all()}
- {bind_to_device, Ifname :: binary()}
- {raw, Protocol, OptionNum, ValueBin}
- {read_packets, Integer}
- {recbuf, Size}
- {recvtclass, Boolean}
- {recvtos, Boolean}
- {recvttl, Boolean}
- {reuseaddr, Boolean}
- {send_timeout, Integer}
- {send_timeout_close, Boolean}
- {sndbuf, Size}
- {priority, Integer}
- {tos, Integer}
- {tclass, Integer}
结构
这是erlang和外界通信的大致结构,通过erlang_port来调用系统的API。
实验
我们尝试使用udp通过ipv4来查询DNS信息。
- 在任意端口打开一个udp的socket
{ok, Socket} = gen_udp:open(0, [{active, false}, binary, inet]).
- 使用
inet_db:res_option/1
读取所需要的一些配置信息:
#{alt_nameservers=[], edns=false, inet6=false, nameservers=[{{127,0,0,53},53}], inet6=false, retry=3, timeout=2000, udp_payload_size=1280, usevc=false}
- 用
dns_rec
record 来包装信息,并使用inet_dns:encode/1
来编码。这里我们构造了一个Msg。
Msg=#dns_rec{header=#dns_header{id=999,
opcode='query',
rd=false,
rcode=0},
qdlist=[#dns_query{domain="google.com",
type=a,
class=in}],
arlist=[]}.
Buffer=inet_dns:encode(Msg).