Libuv UDP

用户数据报协议(User Datagram Protocol)提供无连接的,不可靠的网络通信。因此,libuv不会提供一个stream实现的形式,而是提供了一个uv_udp_t句柄(接收端),和一个uv_udp_send_t句柄(发送端),还有相关的函数。也就是说,实际的读写api与正常的流读取类似。下面的例子展示了一个从DCHP服务器获取ip的例子。

你必须以管理员的权限运行udp-dhcp,因为它的端口号低于1024

udp-dhcp/main.c - Setup and send UDP packets

uv_loop_t *loop;
uv_udp_t send_socket;
uv_udp_t recv_socket;

int main() {
    loop = uv_default_loop();

    uv_udp_init(loop, &recv_socket);
    struct sockaddr_in recv_addr;
    uv_ip4_addr("0.0.0.0", 68, &recv_addr);
    uv_udp_bind(&recv_socket, (const struct sockaddr *)&recv_addr, UV_UDP_REUSEADDR);
    uv_udp_recv_start(&recv_socket, alloc_buffer, on_read);

    uv_udp_init(loop, &send_socket);
    struct sockaddr_in broadcast_addr;
    uv_ip4_addr("0.0.0.0", 0, &broadcast_addr);
    uv_udp_bind(&send_socket, (const struct sockaddr *)&broadcast_addr, 0);
    uv_udp_set_broadcast(&send_socket, 1);

    uv_udp_send_t send_req;
    uv_buf_t discover_msg = make_discover_msg();

    struct sockaddr_in send_addr;
    uv_ip4_addr("255.255.255.255", 67, &send_addr);
    uv_udp_send(&send_req, &send_socket, &discover_msg, 1, (const struct sockaddr *)&send_addr, on_send);

    return uv_run(loop, UV_RUN_DEFAULT);
}

ip地址为0.0.0.0,用来绑定所有的接口。255.255.255.255是一个广播地址,这也意味着数据报将往所有的子网接口中发送。端口号为0代表着由操作系统随机分配一个端口。

首先,我们设置了一个用于接收socket绑定了全部网卡,端口号为68作为DHCP客户端,然后开始从中读取数据。它会接收所有来自DHCP服务器的返回数据。我们设置了UV_UDP_REUSEADDR标记,用来和其他共享端口的 DHCP客户端和平共处。接着,我们设置了一个类似的发送socket,然后使用uv_udp_send向DHCP服务器(在67端口)发送广播。

设置广播发送是非常必要的,否则你会接收到EACCES错误。和此前一样,如果在读写中出错,返回码<0。

因为UDP不会建立连接,因此回调函数会接收到关于发送者的额外的信息。

当没有可读数据后,nread等于0。如果addr是null,它代表了没有可读数据(回调函数不会做任何处理)。如果不为null,则说明了从addr中接收到一个空的数据报。如果flag为UV_UDP_PARTIAL,则代表了内存分配的空间不够存放接收到的数据了,在这种情形下,操作系统会丢弃存不下的数据。

udp-dhcp/main.c - Reading packets

void on_read(uv_udp_t *req, ssize_t nread, const uv_buf_t *buf, const struct sockaddr *addr, unsigned flags) 
{
    if (nread < 0)
    {
        fprintf(stderr, "Read error %s\n", uv_err_name(nread));
        uv_close((uv_handle_t*) req, NULL);
        free(buf->base);
        return;
    }

    char sender[17] = { 0 };
    uv_ip4_name((const struct sockaddr_in*) addr, sender, 16);
    fprintf(stderr, "Recv from %s\n", sender);

    // ... DHCP specific code
    unsigned int *as_integer = (unsigned int*)buf->base;
    unsigned int ipbin = ntohl(as_integer[4]);
    unsigned char ip[4] = {0};
    int i;
    for (i = 0; i < 4; i++)
        ip[i] = (ipbin >> i*8) & 0xff;
    fprintf(stderr, "Offered IP %d.%d.%d.%d\n", ip[3], ip[2], ip[1], ip[0]);

    free(buf->base);
    uv_udp_recv_stop(req);
}

UDP Options

生存时间(Time-to-live)

可以通过uv_udp_set_ttl更改生存时间。

只允许IPV6协议栈

在调用uv_udp_bind时,设置UV_UDP_IPV6ONLY标示,可以强制只使用ipv6。

组播

socket也支持组播,可以这么使用:

UV_EXTERN int uv_udp_set_membership(uv_udp_t* handle,
                                    const char* multicast_addr,
                                    const char* interface_addr,
                                    uv_membership membership);

其中membership可以为UV_JOIN_GROUP和UV_LEAVE_GROUP。 这里有一篇很好的关于组播的文章。 可以使用uv_udp_set_multicast_loop修改本地的组播。 同样可以使用uv_udp_set_multicast_ttl修改组播数据报的生存时间。(设定生存时间可以防止数据报由于环路的原因,会出现无限循环的问题)。