CWYAlpha

Just another WordPress.com site

Thought this was cool: libuv 中文编程指南(四)网络

leave a comment »


网络

libuv 的网络接口与 BSD 套接字接口存在很大的不同, 某些事情在 libuv 下变得更简单了, 并且所有接口都是都是非阻塞的, 但是原则上还是一致的. 另外 libuv 也提供了一些工具类的函数抽象了一些让人生厌的, 重复而底层的任务,比如使用 BSD 套接字结构来建立套接字, DNS 查询, 或者其他各种参数的设置.

libuv 中在网络 I/O 中使用了uv_tcp_t和uv_udp_t两个结构体.

TCP

TCP 是一种面向连接的流式协议, 因此是基于 libuv 的流式基础架构上的.

服务器(Server)

服务器端的 sockets 处理流程如下:

  1. uv_tcp_init初始化 TCP 监视器.
  2. uv_tcp_bind绑定.
  3. 在指定的监视器上调用uv_listen来设置回调函数, 当有新的客户端连接到来时, libuv 就会调用设置的回调函数.
  4. uv_accept接受连接.
  5. 使用stream operations与客户端进行通信.

以下是一个简单的 echo 服务器的例子:

intmain() {

loop=uv_default_loop();

uv_tcp_t server;

uv_tcp_init(loop,&server);

structsockaddr_in bind_addr = uv_ip4_addr(“0.0.0.0”,7000);

uv_tcp_bind(&server, bind_addr);

intr = uv_listen((uv_stream_t*) &server,128, on_new_connection);

if(r) {

fprintf(stderr,”Listen error %s\n”, uv_err_name(uv_last_error(loop)));

return1;

}

returnuv_run(loop, UV_RUN_DEFAULT);

}

你可以看到辅助函数uv_ip4_addr用来将人为可读的字符串类型的 IP 地址和端口号转换成 BSD 套接字 API 所需要的structsockaddr_in类型的结构. 逆变换可以使用uv_ip4_name来完成.

对于 IPv6 来说应该使用uv_ip6_*形式的函数.

大部分的设置(setup)函数都是普通函数, 因为他们都是计算密集型(CPU-bound), 直到调用了uv_listen我们才回到 libuv 中回调函数风格.uv_listen的第二个参数 backlog 队列长度 – 即连接队列最大长度.

当客户端发起了新的连接时, 回调函数需要为客户端套接字设置一个监视器, 并调用uv_accept函数将客户端套接字与新的监视器在关联一起. 在例子中我们将从流中读取数据.

voidon_new_connection(uv_stream_t *server,intstatus) {

if(status == -1) {

//error!

return;

}

uv_tcp_t*client = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));

uv_tcp_init(loop, client);

if(uv_accept(server, (uv_stream_t*) client) ==0) {

uv_read_start((uv_stream_t*) client, alloc_buffer, echo_read);

}

else{

uv_close((uv_handle_t*) client, NULL);

}

}

剩余部分的函数与上一节流式例子中的代码相似, 你可以在例子程序中找到具体代码, 如果套接字不再使用记得调用uv_close关闭该套接字. 如果你不再接受连接, 你可以在uv_listen的回调函数中关闭套接字.

客户端(Client)

在服务器端你需要调用 bind/listen/accept, 而在客户端你只需要调用uv_tcp_connect.uv_tcp_connect使用了与uv_listen风格相似的回调函数uv_connect_cb如下:

uv_tcp_t socket;

uv_tcp_init(loop,&socket);

uv_connect_t connect;

structsockaddr_in dest = uv_ip4_addr(“127.0.0.1”,80);

uv_tcp_connect(&connect, &socket, dest, on_connect);

建立连接后会调用on_connect.

UDP

User Datagram Protocol提供了无连接, 不可靠网络通信协议, 因此 libuv 并不提供流式 UDP 服务, 而是通过uv_udp_t结构体(用于接收)和uv_udp_send_t结构体(用于发送)以及相关的函数给开发人员提供了非阻塞的 UDP 服务. 所以, 真正读写 UDP 的函数与普通的流式读写非常相似.为了示范如何使用 UDP, 下面提供了一个简单的例子用来从DHCP获取 IP 地址. – DHCP 发现.

Note

你应该以root
用户运行udp-dhcp, 因为该程序使用了端口号低于 1024 的端口.

uv_loop_t *loop;

uv_udp_t send_socket;

uv_udp_t recv_socket;

intmain() {

loop=uv_default_loop();

uv_udp_init(loop,&recv_socket);

structsockaddr_in recv_addr = uv_ip4_addr(“0.0.0.0”,68);

uv_udp_bind(&recv_socket, recv_addr,0);

uv_udp_recv_start(&recv_socket, alloc_buffer, on_read);

uv_udp_init(loop,&send_socket);

uv_udp_bind(&send_socket, uv_ip4_addr(“0.0.0.0”,0),0);

uv_udp_set_broadcast(&send_socket,1);

uv_udp_send_t send_req;

uv_buf_t discover_msg= make_discover_msg(&send_req);

structsockaddr_in send_addr = uv_ip4_addr(“255.255.255.255”,67);

uv_udp_send(&send_req, &send_socket, &discover_msg,1, send_addr, on_send);

returnuv_run(loop, UV_RUN_DEFAULT);

}

0.0.0.0地址可以绑定本机所有网口.255.255.255.255是广播地址, 意味着网络包可以发送给子网中所有网口, 端口0说明操作系统可以任意指定端口进行绑定.

首先我们在 68 号端口上设置了绑定本机所有网口的接收套接字(DHCP 客户端), 并且设置了读监视器. 然后我们利用相同的方法设置了一个用于发送消息的套接字. 并使用uv_udp_send在 67 号端口上(DHCP 服务器)发送广播消息.

设置广播标志也是必要
的, 不然你会得到EACCES错误[1]. 发送的具体消息与本书无关, 如果你对此感兴趣, 可以参考源码. 若出错, 则读写回调函数会收到 -1 状态码.

由于 UDP 套接字并不和特定的对等方保持连接, 所以 read 回调函数中将会收到用于标识发送者的额外信息. 如果缓冲区是由你自己的分配的, 并且不够容纳接收的数据, 则“flags“ 标志位可能是UV_UDP_PARTIAL.在这种情况下, 操作系统会丢弃不能容纳的数据.(这也是 UDP 为你提供的特性).

voidon_read(uv_udp_t *req, ssize_t nread, uv_buf_t buf,structsockaddr *addr, unsigned flags) {

if(nread == -1) {

fprintf(stderr,”Read error %s\n”, uv_err_name(uv_last_error(loop)));

uv_close((uv_handle_t*) req, NULL);

free(buf.base);

return;

}

charsender[17] = {0};

uv_ip4_name((structsockaddr_in*) addr, sender,16);

fprintf(stderr,”Recv from %s\n”, sender);

//… DHCP specific code

free(buf.base);

uv_udp_recv_stop(req);

}

UDP 选项(UDP Options)

生存时间TTL(Time-to-live)

可以通过uv_udp_set_ttl来设置网络数据包的生存时间(TTL).

仅使用 IPv6 协议

IPv6 套接字可以同时在 IPv4 和 IPv6 协议下进行通信. 如果你只想使用 IPv6 套接字, 在调用uv_udp_bind6[2]时请传递UV_UDP_IPV6ONLY参数.

多播(Multicast)

套接字可以使用如下函数订阅(取消订阅)一个多播组:

UV_EXTERNintuv_udp_set_membership(uv_udp_t*handle,

constchar* multicast_addr,constchar*interface_addr,

uv_membership membership);

membership取值可以是UV_JOIN_GROUP或UV_LEAVE_GROUP.

多播包的本地回路是默认开启的[3], 可以使用uv_udp_set_multicast_loop来开启/关闭该特性.

多播包的生存时间可以使用uv_udp_set_multicast_ttl来设置.

DNS 查询(Querying DNS)

libuv 提供了异步解析 DNS 的功能, 用于替代getaddrinfo[4]. 在回调函数中, 你可以在获得的 IP 地址上执行普通的套接字操作. 让我们通过一个简单的 DNS 解析的例子来看看怎么连接Freenode吧:

intmain() {

loop=uv_default_loop();

structaddrinfo hints;

hints.ai_family=PF_INET;

hints.ai_socktype=SOCK_STREAM;

hints.ai_protocol=IPPROTO_TCP;

hints.ai_flags=0;

uv_getaddrinfo_t resolver;

fprintf(stderr,”irc.freenode.net is…”);

intr = uv_getaddrinfo(loop, &resolver, on_resolved,”irc.freenode.net”,”6667″, &hints);

if(r) {

fprintf(stderr,”getaddrinfo call error %s\n”, uv_err_name(uv_last_error(loop)));

return1;

}

returnuv_run(loop, UV_RUN_DEFAULT);

}

如果uv_getaddrinfo返回非零, 表示在建立连接时出错, 你设置的回调函数不会被调用, 所有的参数将会在uv_getaddrinfo返回后被立即释放. 有关hostname,servname和hints结构体的文档可以在getaddrinfo帮助页面中找到.

在解析回调函数中, 你可以在structaddrinfo(s)结构的链表中任取一个 IP. 这个例子也演示了如何使用uv_tcp_connect. 你在回调函数中有必要调用uv_freeaddrinfo.

voidon_resolved(uv_getaddrinfo_t *resolver,intstatus,structaddrinfo *res) {

if(status == -1) {

fprintf(stderr,”getaddrinfo callback error %s\n”, uv_err_name(uv_last_error(loop)));

return;

}

charaddr[17] = {”};

uv_ip4_name((structsockaddr_in*) res->ai_addr, addr,16);

fprintf(stderr,”%s\n”, addr);

uv_connect_t*connect_req = (uv_connect_t*) malloc(sizeof(uv_connect_t));

uv_tcp_t*socket = (uv_tcp_t*) malloc(sizeof(uv_tcp_t));

uv_tcp_init(loop, socket);

connect_req->data = (void*) socket;

uv_tcp_connect(connect_req, socket,*(structsockaddr_in*) res->ai_addr, on_connect);

uv_freeaddrinfo(res);

}

网络接口(Network interfaces)

系统网络接口信息可以通过调用uv_interface_addresses来获得, 下面的示例程序将打印出机器上所有网络接口的细节信息, 因此你可以获知网口的哪些域的信息是可以得到的, 这在你的程序启动时绑定 IP 很方便.

#include <stdio.h>

#include<uv.h>

intmain() {

charbuf[512];

uv_interface_address_t*info;

intcount, i;

uv_interface_addresses(&info, &count);

i=count;

printf(“Number of interfaces: %d\n”, count);

while(i–) {

uv_interface_address_tinterface=info[i];

printf(“Name: %s\n”,interface.name);

printf(“Internal? %s\n”,interface.is_internal ?”Yes”:”No”);

if(interface.address.address4.sin_family ==AF_INET) {

uv_ip4_name(&interface.address.address4, buf,sizeof(buf));

printf(“IPv4 address: %s\n”, buf);

}

elseif(interface.address.address4.sin_family ==AF_INET6) {

uv_ip6_name(&interface.address.address6, buf,sizeof(buf));

printf(“IPv6 address: %s\n”, buf);

}

printf(“\n”);

}

uv_free_interface_addresses(info, count);

return0;

}

is_internal对于回环接口来说为 true. 请注意如果物理网口使用了多个 IPv4/IPv6 地址, 那么它的名称将会被多次报告, 因为每个地址都会报告一次.

本文链接


0

   

0

udpwork.com 聚合
|
评论: 0
|
要! 要! 即刻! Now!

from IT牛人博客聚合网站: http://www.udpwork.com/item/9469.html

Written by cwyalpha

四月 11, 2013 在 11:08 上午

发表在 Uncategorized

发表评论

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / 更改 )

Twitter picture

You are commenting using your Twitter account. Log Out / 更改 )

Facebook photo

You are commenting using your Facebook account. Log Out / 更改 )

Google+ photo

You are commenting using your Google+ account. Log Out / 更改 )

Connecting to %s

%d 博主赞过: