recvfrom 和 sendto函数
#include
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
UDP回射服务器
- 客户
if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
err_quit("socket error");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8080);
inet_pton(AF_INET, argv[1], &servaddr.sin_addr);
dg_cli(stdin, sockfd, (SA*)&servaddr, sizeof(servaddr));
// dg_cli
while( fgets(sendline, MAXLINE, fp) != NULL){
sendto(sockfd, sendline, strlen(sendline), 0, pservaddr, servlen);
n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL);
recvline[n] = '\0';
fputs(recvline, stdout);
}
- 服务器
if ( (sockfd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
err_quit("socket error");
bzero(&servaddr, sizeof(servaddr));
servaddr.sin_family = AF_INET;
servaddr.sin_port = htons(8080);
servaddr.sin_addr.s_addr = htonl(INADDR_ANY);
bind(sockfd, (SA*)&servaddr, sizeof(servaddr));
dg_echo(sockfd, (SA*)&cliaddr, sizeof(cliaddr));
// dg_echo
for ( ; ; ) {
len = clilen;
n = recvfrom(sockfd, mesg, MAXLINE, 0, pcliaddr, &len);
sendto(sockfd, mesg, n, 0, pcliaddr, len);
}
- 结构如图
可以对比TCP回射客户/服务器模型 基本TCP套接字编程
事实上,每一个UDP套接字都有一个接收缓冲区,到达该套接字的每个数据报都进入这个套接字缓冲区。当进程调用recvfrom时,缓冲区中下一个数据报以FIFO顺序返回给进程。这个缓冲区可以通过SO_RECVBUF套接字选项设置。
-
上面问题1:
在客户recvfrom中第5, 6个参数为空。这意味着我们不关心应答数据报由谁发送,这样做存在一个风险,任何进程无论是在本客户进程相同的主机上还是在不同的主机上,都可以向本客户的IP地址和端口发送数据报。n = recvfrom(sockfd, recvline, MAXLINE, 0, NULL, NULL); 改进:记录下IP与之前的目的IP作比对。 n = recvfrom(sockfd, recvline, MAXLINE, 0, preply_addr, &len); if (len != servlen || memcmp(pservaddr, preply_addr, len) != 0) { ....... }
问题2:
目前这个会回射服务器设计是不可靠的。如果一个客户数据报丢失(比如说被客户主机和服务器主机之间的某个路由器丢弃),客户将永远阻塞于dg_cli函数中的recvfrom调用,等待一个永远不会到达服务器应答。类似的,如果客户数据报到达服务器,但是服务器的应答丢失了,客户也将永远阻塞于recvfrom调用。
- 问题3:
客户永远阻塞于它的recvfrom调用,等待一个永远不会出现的应答。
这种情况下,服务器主机会响应一个"端口不可达"的ICMP报文消息。不过这个ICMP错误不会返回给进程。
因为UDP输出操作成功返回仅仅表示在接口输出队列中具有存放所形成IP数据报的空间。该ICMP错误直到后来才返回,我们一般将这个ICMP错误称之为异步错误。
对于一个UDP套接字,由于它引发的异步错误却并不返回给它,除非它已连接。
UDP的connect函数
对于上文我们已经提到对于异步错误,除非套接字已经连接,否则,是不会返回到UDP套接字的。我们可以给UDP套接字调用connect,然而这样做的结果与TCP连接大相径庭:没有3路握手过程,内核只是检查是否存在立即可知的错误(例如ICMP),记录对端的IP地址和端口号,然后立即返回给调用进程。
有了这个能力后,我们必须区分:
- 未连接的UDP套接字,新创建的UDP套接字默认如此
- 已连接的UDP套接字,对UDP套接字调用connect的结果
对于已连接的套接字:
我们再也不能给输出操作指定目的IP地址和端口号,我们不再使用write 或 send。写到已连接UDP套接字上的任何内容都自动发送到由connect指定的协议地址(如IP地址和端口号)
不必使用recvfrom以获悉数据报的发送者,而改用read,recv或recvmsg。内核返回的数据报只有那些来自connectc所指定协议地址的数据报
已连接UDP套接字引发的异步错误会返回给它们所在的进程
性能
当应用进程在一个未连接的UDP套接字上调用sendto时,内核会暂时连接该套接字,发送数据报,然后断开该连接。涉及到下面6个步骤:
- 连接套接字
- 输出第一个数据报
- 断开套接字连接
- 连接套接字
- 输出第二个数据报
- 断开套接字连接
当应用进程知道自己要给同意目的地址发送多个数据报时,显式连接套接字效率更高。临时连接未连接的UDP套接字大约会耗费每个UDP传输的3分之一开销
- 连接套接字
- 输出第一个套接字
- 输出第二个套接字
UDP缺乏流量控制
参考资料
《UNIX 网络编程》3th [美] W.Richard Stevens,Bill Fenner,Andrew M. Rudoff