Unix网络编程——chap8

#基本UDP套接字编程

概述

与TCP不一样,UDP是无连接不可靠的数据报协议。在有些应用程序上会用到UDP,比如DNS, NFS, SNMP。

由于UDP不等待连接,所以客户端只管调用sendto函数,而服务端也只管调用recvfrom函数。

recvfrom和sendto函数

1
2
3
ssize_t recvfrom(int sockfd, void *buff, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen);

ssize_t sendto(int sockfd, const void *buff, size_t nbytes, int flags, const struct sockaddr *to, socklen_t addrlen);

在UDP的情况下,写一个长度为0的数据报是可行的,那样会形成一个包含一个IP首部和一个8字节的UDP首部而没有数据的IP数据报。这也意味着,与TCP不同的是,UDP中recvfrom返回0是可以接受的。

验证接收到的响应

由于recvfrom中最后两个参数是关于数据来源的地址等信息,如果设置为0则意味着我们忽略了该信息,但这样会带来的一个问题是,这就意味着任何一个端都可以给该客户端发送信息。

因此往往,我们需要验证接收到的响应

服务器未运行

考虑这样的情况,在服务端没有启动的情况下,启用客户端:

这种情况下,会返回一个ICMP的错误,并且是异步的。为什么要异步,因为如果一个客户端连续向三个服务端发送数据报,但其中一个对端的服务端没有开启,这种情况下,UDP还能继续工作。

但也要考虑到的一个问题是,这样我们如何区分是哪个服务端没有开启,但errno只有一个,不能区分是哪个IP地址和目的端口号。因此会有这样的规则,仅仅在进程将其UDP套接字连接到恰恰一个对端的的时候,这些异步错误才会返回进程。

小结

客户端在使用sendto时必须指定服务器的ip地址和端口号,但自身的ip地址和端口号则可以由内核自动选择,当然客户端也可以使用bind来指定它们。另外在内核指定的情况下,端口号是固定不变的,而ip地址则可能随着发送数据报的改变而改变。

如果我们使用了bind,但内核决定从另外一个数据链路发出,那么ip数据包仍然包含我们使用bind指定的ip地址,但是实际发送的链路ip地址即便不一样也无所谓。

UDP的connect函数

之前我们提到过,除非UDP的套接字已连接,否则异步错误是不会返回到UDP的套接字的。

我们可以在UDP的套接字上使用connect,但与TCP不同的是,这里没有三次握手的过程,只是检测是否存在立即可以感知的错误,记录对端的IP地址和端口号,并返回调用进程。

对于使用了connect的UDP套接字,接受和发送数据不能再使用sendto和recvfrom,而是read、write等。一个以连接的套接字仅仅与一个IP地址交换数据报。

给一个UDP套接字多次使用connect

这样做有两个目的:

  • 指定新的IP地址和端口号;
  • 断开连接,通过把套接字地址结构的的地址族成员设置为AF_UNSPEC即可;

性能

在一个未连接的套接字上调用sendto,会进行以下的步骤:

  • 连接套接字;
  • 输出第一个数据报;
  • 断开套接字连接;
  • 连接套接字;
  • 输出第二个数据报;
  • 断开套接字连接;

而如果使用了connect之后再调用两次write:

  • 连接套接字;
  • 输出第一个数据报;
  • 输出第二个数据报;

UDP套接字接收缓冲区

由UDP给某个特定的套接字排队的UDP数据报数目受限于该套接字接收缓冲区的大小。如果我们要改变其缓冲区大小,可以使用SO_RCVBUF进行修改;