TCP/IP实现(十一) UDP用户数据报协议

一.已连接UDP

      我们可以对UDP套接字调用connect进行连接,但与TCP连接的差别很大。UDP进行连接并不进行三次握手,内核只是内核只是检查一些立即可知的错误(如一个显然不可达的目的地),并将对端的IP地址和端口号记录在PCB协议控制块中,之后立即返回到调用进程

1.与未连接UDP的区别

    a) 不能在给输出操作指定目的IP和端口号,否则将返回错误EISCONN。b) 不必再调用rcvfrom以获悉发送者,而改用read,recv,recvmsg。因为在一个已连接的UDP套接字上,由内核为输入操作返回的数据报只有那些来自connnect所指定端的数据报(内核会匹配PCB中的地址)。【注】来自非connect指定端发来的数据报,本端UDP套接字是无法收到的(原因查看《TCP/IP实现(十) 协议控制块》

2.异步错误

       还有一个上面未提到的与未连接UDP的区别就是关于异步错误的处理。异步错误即不是与调用函数同时返回的错误,比如当服务器未开启时,服务器主机会响应一个ICMP端口不可达报文,但是当UDP是未连接时,该ICMP错误不会返回给客户进程,这样一来,客户端将永远不知道该错误,并可能一直等待下去。我们称这种ICMP错误为异步错误,该错误可能由sendto引起,但sendto却成功返回,这是因为UDP输出操作成功返回仅仅表示在接口输出队列中有存放包含该UDP的IP数据报的空间(可参《TCP/IP实现 (三) 以太网的数据收发》),还有一点顺便说一下,即UDP的输出过程中有且仅有一个缓冲队列,即接口层输出队列。下面会对UDP数据报的发送接收流程做一个说明。综上所述,即由已连接的UDP套接字引发的异步错误会返回给它们所在的进程,而未连接套接字不接收任何异步错误。

3.已连接UDP与未连接UDP的性能差异

      要比较两者的性能差异,我们首选要知道当在一个未连接的UDP上调用sendto函数时,会先建立一个临时连接,当发送数据后再断开连接,当调用两次sendto,内核便要复制两次含有目的IP地址和端口号的地址结构,而已连接的UDP只需再connect时复制一次。有人指出,临时连接未连接的UDP套接字大约会耗费每个UDP传输三分之一的开销。

二.UDP的输出

     当进程进行写系统调用时,插口层会直接或间接调用sosend函数,该函数根据协议类型,将数据交付给相应的协议层(可以参考《TCP/IP实现(九) 插口I/O》),而对于UDP而言便是将数据交给udp_output函数进行处理。该函数处理流程如下:

// struct inpcb *inp 指向socket对应的协议控制块,其中含有本地和外部地址信息
// struct mbuf *m 要发送的数据
// struct mbuf *addr 目的地址信息
// struct mbuf *control控制信息(在调用sendmsg时传入的,UDP不适用直接丢弃)
int udp_output(struct inpcb *inp, struct mbuf *m, struct mbuf *addr, struct mbuf *control)
{
    if(control)
        m_free(control);// UDP从不使用控制信息,直接丢弃(因此通过sendmsg传入也没用,但也无妨)

    if(addr){ // 如果指定了要发送的目的地址,说明该UDP本应该是未连接的
        if(inp->inp_faddr.s_addr != INADDR_ANY) { // 若该socket以连接了一个外部地址则,不能再在发送数据时指定目的地址
            error = EISCONN;
            goto release;
        }
        error = in_pcbconnect(inp,addr); //进行临时连接
        if(error)  goto release;
    }// if
    else {
        if(inp->inp_faddr.s_addr == INADDR_ANY) { // 若在未连接的UDP上调用sendto,但又未指定目的地址则返回错误
            error = ENOTCONN;
            goto release;
        }
    }
    进行UDP首部填充和部分IP数据报的首部填充,并计算检验和
    将添加了IP&UDP首部的数据报交付给ip层输出函数ip_output;
}

       UDP协议层判断一个套接字是否已连接不是通过socket结构中的状态字段来判断的,而是根据其对应的PCB表是否设置了外部地址来判断的。当调用了ip_output后又会将数据转交给接口层,接口层处理函数(如以太网的ether_output)将数据放至接口输出队列中,接口输出队列也是UDP输出过程中的唯一队列

三.UDP的输入

      当IP层收到一个完整的数据报(重组后的),则根据IP首部中协议类型查找相应的协议转换表,从而调用相应协议的输入函数(会将IP数据报传入,因为比如UDP协议计算校验码时需要用到IP首部中的某些字段),UDP协议的输入函数为udp_input。下面只对UDP收取数据报的操作进行简要说明:

void udp_input(struct mbuf* m, int iplen)
{
    // 第一步
    进行首部长度检验,和校验和检验

    // 第二部
    如果数据报的目的地址是一个广播或多播地址,则搜索udp的PCB表,将数据报交付给所有匹配地址pcb对应的socket
   
    // 第三部
    //如果数据报的目的地址是一个单播地址则进行以下处理
    查找udp的pcb表,找到最匹配的一个pcb(最少通配原则)。
    if(未找到) {
        //增加UDP未知端口错误的计数,并返回ICMP端口不可达错误
        udpstat.udps_noportbcast++;// 可通过netstat -s查看
        icmp_error(xx,ICMP_UNREACH, ICMP_UNREACH_PORT...);
        return;
    }

    // 找到了
    若找到了匹配的pcb,则将数据报放入该pcb对应套接字的接收缓存中
    唤醒阻塞在该套接字上的进程
}

四.ICMP源站抑制差错

     由于UDP没有流量控制,因此有可能发送方发送数据的速度比接收方的处理速度快,此时便可能产生ICMP源站一直差错。

五.UDP数据报的数据大小原则

UDP数据报的数据大小一般不应该超过512字节,因为要求主机必须至少能够接收576字节的IP数据报,因此许多UDP应用程序限制UDP中的数据大小不应超过512字节

猜你喜欢

转载自blog.csdn.net/qq_34228327/article/details/84313592