使用TCP编写的应用程序和使用UDP编写的应用程序之间存在一些本质差别,其缘由在于这两个传输层之间的差异:UDP是无链接不可靠的数据报协议,很是不一样于TCP提供的面向链接的可靠字节流。然而相比TCP,有些场合更适合UDP。使用UDP编写的一些常见应用程序有:DNS(域名系统)、NFS(网络文件系统)和SNMP(简单网络管理协议)。缓存
下图给出了典型的UDP客户/服务器程序的函数调用。客户没必要与服务器创建链接,而是只管使用sendto函数给服务器发送数据报,其中必须指定目的地(即服务器)的地址做为参数。相似的,服务器不接受来自客户的链接,而是只管调用recvfrom函数,等待来自某个客户的数据到达。recvfrom将于所接受的数据报一道返回客户的协议地址,所以服务器能够把响应发送给正确的客户。服务器
这个函数相似于标准的read和write函数,不过须要三个额外的参数:网络
#include<sys/socket.h> ssize_t recvfrom(int sockfd, void *buf, size_t nbytes, int flags, struct sockaddr *from, socklen_t *addrlen); ssize_t sendto(int sockfd, const void* buf, size_t nbytes, int flags, const struct sockaddr* to, sockelen_t addrlen);
前三个参数sockfd,buf和nbytes等同于read和write函数的三个参数:描述符、指向读入或写出缓冲区的指针和读写字节数。并发
sendto的to参数指向一个含有数据报接收者的协议地址(如IP地址和端口号)的套接字地址结构,其大小是由addrlen参数指定。recvfrom的from参数指向一个将由该函数在返回时填写的数据报发送者的协议地址的套接字地址结构,而在该套接字结构中填写的字节数则在addrlen参数所指的整数中返回给调用者。注意,sendto的最后一个参数是一个整数值,而recvfrom的最后一个参数是一个指向整数值的指针(即值-结果参数)异步
recvfrom的最后两个参数相似于accept的最后两个参数:返回时其中套接字的地址结构告诉咱们是谁发送了数据报(UDP状况下)或是谁发起了链接(TCP状况下)。sendto的最后两个参数相似于connect的最后两个参数:调用时其中套接字的地址结构被咱们填入数据报将发往(UDP状况下)或与之创建链接(TCP状况下)的协议地址。socket
这两个函数都把所读写的数据的长度做为函数返回值。在recvfrom使用数据报协议的典型用途中,返回值就是所接收数据报中的用户数据量。函数
写一个长度为0的数据报是可行的。在UDP状况下,这会造成一个只包含一个IP首部(对于IPV4一般为20字节,对于IPV6一般是40字节)和一个8字节的UDP首部而没有数据的IP数据报。这也意味着对于数据报协议,recvfrom返回0值是能够接受的:它不像TCP套接字上read返回0值那样表示对端已关闭链接。既然UDP是无链接的,所以也就没有诸如关闭一个UDP链接之类的事情。性能
若是recvfrom的from是一个空指针,那么相应的长度参数(addrlen)也必须是一个空指针,表示咱们并不关心数据报发送者的协议地址。3d
通常来讲,大多数TCP服务器是并发的,而大多数UDP服务器是迭代的。指针
事实上每一个UDP套接字都有一个接收缓冲区,到达该套接字的每一个数据报都进入这个套接字的接收缓冲区。这样,在进程可以读该套接字任何已排好队的数据报以前,若是有多个数据报到达该套接字,那么相继到达的数据报仅仅加到该套接字的接收缓冲区中。然而这个缓冲区的大小是由限制的
能够给UDP套接字调用connect,然而这样作的结果与TCP链接截然不同:没有三路握手过程。内核只是检查是否存在当即可知的错误(例如一个显然不可达的目的地),记录对端的IP地址和端口号(取自传递给connect的套接字地址结构),而后当即返回到调用进程。
对于已链接UDP套接字(调用connect后),与默认的未链接UDP套接字相比,发生了三个变化:
咱们不再能给输出操做指定目的IP地址和端口号。也就是说,咱们不使用sento,而改用write或send。写到已链接UDP套接字上的任何内容都自动发送到由connect指定的协议地址(如IP地址和端口号)。
其实咱们能够给已链接UDP套接字调用sendto,可是不能指定目的地址。sendto的第五个参数(指向指明目的地址的套接字地址结构的指针)必须为空指针,第六个参数(该套接字地址结构的大小)应该为0。POSIX规范指出当第五个参数为空指针时,第六个参数的取值就再也不考虑。
咱们没必要使用recvfrom以获悉数据报的发送者,而改用read、recv或recvmsg。在一个已链接UDP套接字上,由内核为输入操做返回的数据报只有那些来自connect所指定协议地址的数据报。目的地为这个已链接UDP套接字的本地协议地址(如IP地址和端口号),发源地却不是该套接字早先connect到的协议地址的数据报,不会投递到该套接字。这样就限制一个已链接UDP套接字能且仅能与一个对端交换数据报。
确切地说,一个已链接UDP套接字仅仅与一个IP地址交换数据报,由于connect到多播或广播地址是可能的。
由已链接UDP套接字引起的异步错误会返回给它们所在的进程,而未链接UDP套接字不接收任何异步错误。
应用进程首先调用connect指定对端的IP地址和端口号,而后使用read和write与对端进程交换数据。
来自任何其余IP地址或端口的数据报(上图中的"???"表示)不投递给这个已链接套接字,由于它们要么源IP地址要么源UDP端口不与该套接字connect到的协议地址相匹配。这些数据报可能投递给同一个主机上的其余某个UDP套接字。若是没有相匹配的其余套接字,UDP将丢弃它们并生成相应的ICMP端口不可达错误。
拥有一个已链接UDP套接字的进程可出于下列两个目的之一再次调用connect:
第一个目的(即给一个已链接UDP套接字指定新的对端)不一样于TCP套接字中connect的使用:对于TCP套接字,connect只能调用一次。
为了断开一个已链接UDP套接字,咱们再次调用connect时把套接字地址结构的地址族成员(对于IPv4为sin_family,对于IPv6为sin6_family)设置为AF_UNSPEC。这么作可能会返回一个EAFNOSUPPORT错误,不过没有关系。使套接字断开链接的是在已链接UDP套接字上调用connect的进程。
当应用进程在一个为链接的UDP套接字上调用sendto时,源自Berkeley的内核暂时链接该套接字,发送数据报,而后断开该链接。在一个未链接UDP套接字上给两个数据报调用sendto函数因而涉及内核执行下列6个步骤:
断开套接字链接
另外一个考虑是搜索路由表的次数。第一次临时链接需为目的IP地址搜索路由表并高速缓存这条信息。第二次临时链接注意到目的地址等于已高速缓存的路由表信息的目的地(咱们假设这两个sendto调用相同的目的地址),因而就没必要再次查找路由表。
当应用进程知道本身要给同一目的地址发送多个数据报时,显示链接套接字的效率更高。调用connect后调用两次write涉及内核执行如下步骤:
在这种状况下,内核只复制一次含有目的IP地址和端口号的套接字地址结构,相反当调用sendto时,须要赋值两次。