基本UDP套接字编程

图8-1给出了典型的UDP客户/服务器程序的函数调用。
在这里插入图片描述

recvfrom和sendto函数

#include<sys/socket.h>

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);
	均返回:若成功则为读或写的字节数,若出错则为-1

前三个参数sockfd、buff和nbytes等同于read和write函数的三个参数:描述符、指向读入或写出缓冲区的指针和读写字节数。flags通常置为0。

写一个长度为0的数据报是可行的。在UDP情况下,这会形成一个只包含一个IP首部(对于 IPv4通常为20字节,对于IPv6通常为40字节)和一个8字节UDP首部而没有数据的IP数据报。这 也意味着对于数据报协议,recvfrom®回0值是可接受的:它并不像TCP套接字上read返回0值 那样表示对端已关闭连接。既然UDP是无连接的,因此也就没有诸如关闭一个UDP连接之类事情。
如果recvfromM/row参数是一个空指针,那么相应的长度参(addrlen)也必须是一个空指针,表示我们并不关心数据发送者的协议地址。
recvfrom和sendto都可以用于TCP,尽管通常没有理由这样做。

UDP的connect函数

除非套接字已连接,否则异步错误是不会返回到UDP套接字的。我们确实可以给UDP套接字调用connect,然而这样做的结果却与TCP连接大相径庭:没有三路握手过程。内核只是检查是否存在立即可知的错误(例如一个显然不可达的目的地),记 录对端的IP地址和端口号(取自传递给connect的套接字地址结构),然后立即返回到调用进程。

有了这个能力后,我们必须区分:
•未连接UDP套接字(unconnectedUDP socket),新创建UDP套接字默认如此;
•已连接UDP套接字(connected UDP socket),对UDP套接字调用connect的结果。 对于巳连接UDP套接字,与默认的未连接UDP套接字相比,发生了三个变化。
(1)我们再也不能给输出操作指定目的IP地址和端口号。也就是说,我们不使用sendto, 而改用write或send。写到已连接UDP套接字上的任何内容都自动发送到由connect指定的协议地址(如IP地址和端口号)。
其实我们可以给已连接UDP套接字调用sendto,但是不能指定目的地址。sendto的第五个参数(指向指明目的地址的套接字地址结构的指针)必须为空指针,第六个参数(该套接字地址结构的大小)应该为0。POSIX规范指出当第五个参数是空指针时,第六个参数的取值就不再考虑。
(2)我们不必使用recvfrom以获悉数据报的发送者,而改用read、recrecvmsgo在一 个己连接UDP套接字上,由内核为输入操作返回的数据报只有那些来自connect所指定协议地 址的数据报。目的地为这个已连接UDP套接字的本地协议地址(如IP地址和端口号),发源地却 不是该套接字早先connect到的协议地址的数据报,不会投递到该套接字。这样就限制一个己 连接UDP套接字能且仅能与一个对端交换数据报。
确切地说,一个已连接UDP套接字仅仅与一个IP地址交换数据报,因为connect到多播或广播地址是可能的。
(3)由己连接UDP套接字引发的异步错误会返回给它们所在的进程,而未连接UDP套接字不[252] 接收任何异步错误。

图8-14就4.4BSD总结了上列第一点。
在这里插入图片描述
POSfX规范指出,在未连接UDP套接字上不指定目的地址的输出操作应该返回 ENOTCONN, 而不是EDESTADDRREQ。

图8-15总结了我们给已连接UDP套接字归纳的三点。
在这里插入图片描述
我们可以说UDP客户进程或服务器进程只在使用自己的UDP套接字与确定的唯一对端进行通信时,才可以调用connect。调用connect的通常是UDP客户,不过有些网络应用 中的UDP服务器会与单个客户长时间通信(如TFTP),这种情况下,客户和服务器都可能调用connect。

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

拥有一个已连接UDP套接字的进程可出于下列两个目的之一再次调用connect:
•指定新的IP地址和端口号;
•断开套接字。
第一个目的(即给一个已连接UDP套接字指定新的对端)不同于TCP套接字中connect的 使用:对于TCP套接字,connect只能调用一次。

为了断开一个已连接UDP套接字,我们再次调用connect时把套接字地址结构的地址族成员(对于IPv4为sin_family,对于IPv6为sin6_family)设置为AF_UNSPEC。这么做可能会返回一个EAFNOSUPPORT错误,不过没有关系。使套接字断开连接的是在己连接 UDP套接字上调用connect的进程。
各种Unix变体断开套接字上连接的方式存在差异,同样的方法可能适合某些系统而不适 合其他系统。举例来说,以空的套接字地址结构指针调用connect的方法仅仅适合某些系统 (而在另一些系统上,要求第三个参数即套接字地址结构长度为非0)。POSIX规范和BSD手册页面在此帮助不大,只是提到必须使用一个空地址(null address ),而根本没有提到出错返回值(甚至成功返回值也没有提到)。最便于移植的解决办法就是清零一个地址结构后把它的地 址族成员设置为AF_UNSPEC,再把它传递给connect。
另一个存在差异的地方是断开连接前后套接字本地绑定地址的取值。AIX保留被选中的 本地IP地址和端口号,即使它们起源于隐式捆绑。FreeBSD和Linux把本地IP地址设置回全0, 即使早先调用过bind,端口号也保持不变。Solaris在隐式捆绑时把本地IP地址设置回全0,在显式调用过bind时保持IP地址不变。

性能

当应用进程在一个未连接的UDP套接字上调用sendto时,源自Berkeley的内核暂时连接该 套接字,发送数据报,然后断开该连接(TCPv2第762〜763页)。在一个未连接的UDP套接字上 给两个数据报调用sendt。函数于是涉及内核执行下列6个步骤:
•连接套接字;
•输出第一个数据报;
•断开套接字连接;
•连接套接字;
•输出第二个数据报;
•断开套接字连接。

当应用进程知道自己要给同一目的地址发送多个数据报时,显式连接套接字效率更高。调 用connect后调用两次write涉及内核执行如下步骤:
•连接套接字;
•输出第一个数据报;
•输出第二个数据报。

在这种情况下,内核只复制一次含有目的IP地址和端口号的套接字地址结构,相反当调用两次sendto时,需复制两次。[Partridge和Pink 1993]指出,临时连接未连接的UDP套接字大约会耗费每个UDP传输三分之一的开销。

UDP中的外出接口的确定

己连接UDP套接字还可用来确定用于某个特定目的地的外出接口。这是由connect函数应用到UDP套接字时的一个副作用造成的:内核选择本地IP地址(假设其进程未曾调用bind显式指派它)。这个本地IP地址通过为目的IP地址搜索路由表得到外出接口,然后选用该接口的主IP 地址而选定。

下面给出了一个简单的UDP程序,它connect到一个指定的IP地址后调用getsockname 得到本地IP地址和端口号并显示输出。
在多宿主机freebsd上运行该程序,我们得到如下输出:

freebsd % udpcli09 206.168.112.96
local address 12.106.32.254:52329
freebsd % udpcli09 192.168.42.2
local address 192.168.42.1:52330
freebsd % udpcli09 127.0.0.1
local address 127.0.0.1:52331

第一次运行该程序时所用命令行参数是一个遵循默认路径的IP地址。内核把本地IP地址指 派成默认路径所指接口的主IP地址。第二次运行该程序时所用命令行参数是连接到另一个以太 网接口的一个系统的IP地址,因此内核把本地IP地址指派成该接口的主地址。在UDP套接字上 调用connect并不给对端主机发送任何信息,它完全是一个本地操作,只是保存对端的IP地址 和端口号。我们还看到,在一个未绑定端口号的UDP套接字上调用connect同时也给该套接字指派一个临时端口。

#include	"unp.h"
int main(int argc, char **argv)
{
int sockfd;
socklen_t len;
struct sockaddr_in cliaddr, servaddr;
if (argc != 2)
	err_quit("usage: udpcli <IPaddress>");
sockfd = Socket (AF.INET, SOCK_DGRAM,	0);
bzero(&servaddr, sizeof(servaddr));
servaddr. sin_f amily = AF_INET;
servaddr.sin_port = htons(SERV_PORT);	-
Inet_pton(AF_INET, argv[l] , &servaddr.sin_addr);
Connect(sockfd, (SA *) &servaddr, sizeof(servaddr));
len = sizeof(cliaddr)Getsockname(sockfd, (SA *) &cliaddr, &len)printf (" local address %s\n", Sock_ntop ((SA *) Scdiaddr, len))exit (0);
} 

来源:UNIX网络编程卷一

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值