基础UDP套接字编程

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);
}
  • 结构如图
基础UDP套接字编程_第1张图片
结构如图.jpg

可以对比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

你可能感兴趣的:(基础UDP套接字编程)