典型的UDP客户/服务器程序的函数调用如下:
1、缓冲区
发送缓冲区用虚线表示,任何UDP套接字都有发送缓冲区,不过该缓冲区仅能表示写到该套接字的UDP数据报的上限。如果应用进程写一个大于套接字缓冲区大小的数据报,将会返回该进程一个EMSGSIZE错误。从写一个UDP套接字的write调用成功返回,表示所写的数据报或其所有的片段已被加入到数据链路层的输出队列。如果该队列没有足够的空间存放该数据报或它的某个片段,内核通常会返回一个ENOBUFS错误给它的应用进程。
2、相关函数
- recvfrom()和sendto()
ssize_t sendto (int sockfd, void *buf, size_t bytes, int flags, const struct sockaddr* to, socklen_t *addrlen); ssize_t recvfrom (int sockfd, void *buf, size_t nbytes, int flags, struct sockaddr* from, socklen_t *addrlen);
- recvfrom():
- from:指向一个将由该函数在返回时填写数据报发送者协议地址的套接字地址结构
- addrlen:在from指向的套接字结构中的填写的字节数。
- sendto():
- to:指向一个含有数据报接收者的协议地址的套接字地址结构
- addrlen:to指向的套接字地址结构大小。
recvfrom()的from参数可以是一个空指针,那么相应的长度参数也应该是一个空指针,表示不在意数据发送者的协议地址。写一个长度为0的数据报是可行的,在UDP情况下,这会形成一个只包含一个IP首部(对于IPv4是20字节,对于IPv6而言是40字节)和一个8字节UDP首部而没有数据的IP数据报。意味着对于数据报协议,recvfrom返回0值是可以接受的,不像TCP套接字上read返回0值那样表示对端已关闭连接。既然UDP是无连接的,因此也就没有诸如关闭一个UDP连接之类的事情。
接收缓冲:UDP中隐有排队现象发生。事实上每个UDP套接字都有一个接受缓冲区,到达该套接字的每个数据报都进入这个套接字接受缓冲区 。当进程调用recvfrom的时候,缓冲区的下一个数据报以FIFO的顺序回传给进程。这样,在进程能够读该套接字中任何已排好队的数据报之前,如果有多个数据报到达该套接字,那么相继到达的数据报仅仅加到该套接字的接收缓冲区中。这个缓冲区的大小是有限的。
对于一个UDP套接字,如果其进程首次调用sendto的时候,它没有绑定一个本地端口,那么内核就在此时为它选择一个临时端口。
上图中的圆点指明了客户发送UDP数据报的时候,必须指定或选择的四个值
- 客户必须给sendto调用指定的服务器的IP地址和端口
- 客户的IP地址和端口号可以指定也可以不指定。
- 如果客户没有捆绑具体的IP和端口号,内核会自动选择:
- IP地址却可以随客户发送的每个UDP数据报而变动
- 临时端口是在第一次调用sendto时一次性选定,不能改变
- 如果客户绑定了一个IP地址
- 在这种情况下,如果内核决定外出数据报必须从另一个数据链路发出,IP数据报将会包含一个不同于外出链路IP地址的源IP地址
在一个未绑定端口号和IP地址的UDP套接字上调用connect的时候,会给套接字指派一个IP地址和临时端口
TCP和UDP服务器指定获取源IP,源端口号,目的IP和目的端口号的方法:
来自客户端的IP数据报 | TCP服务端 | UDP服务端 |
源IP地址 | accept | recvfrom |
源端口号 | accept | recvfrom |
目的IP地址 | getsockname | recvmsg |
目的端口号 | getsockname | getsockname |
- 非连接状态下,同一套接字可以给多个服务器发送数据报
- 服务器上的同一个套接字可以从若干不同客户端接收数据
2、连接的UDP套接字
#include <sys/socket.h> itn connect(itn sockfd, const struct sockaddr*servaddr,socklen_t addrlen);
- sockfd,客户端套接字描述符
- servaddr,包含服务器IP地址和端口号的套接字地址结构
- addrlen,套接字地址结构的大小
给UDP套接字调用connect并不会像TCP一样出发三次握手,内核只是检查是否存在立即可知的错误,记录对端的IP地址和端口号,然后立即返回
已连接的UDP套接字和未连接的UDP套接字存在3个不同:
- 限制了一个已连接套接字能且仅能与一个对端交换数据
- 不能再给输出操作指定目的IP地址和端口号,即不使用sendto而改用write或send
- 不必使用recvfrom以获悉数据报的发送者,而改用read、recv或recvmsg
- 接收异步错误
- 由已连接UDP套接字引发的异步错误会返回给它们所在的进程。未连接时不接收任何异步错误
一个拥有已连接UDP套接字的进程,可出于下列2个目的再次调用connect:
- 指定新的IP地址和端口号
- 断开套接字:调用connect时,把套接字地址结构的地址族成员设置为AF_UNSPEC。
TCP要再次调用connect必须先close套接字然后再重新调用socket创建套接字描述符。
连接与不连接的性能:当应用进程在一个未连接的UDP套接字上调用sendto时,源自Berkeley的内核暂时连接该套接字,发送数据报,然后断开该连接。因此,当应用进程要给同一目的地址发送多个数据报时,使用连接套接字可以获得更高的效率