`
shaojiashuai123456
  • 浏览: 256815 次
  • 性别: Icon_minigender_1
  • 来自: 吉林
社区版块
存档分类
最新评论

TCP和UDP在网络层实现的不同--基于linux内核 --转

阅读更多

      由于4层协议实现复杂度的不对称性,导致3层协议实现也不易统一,换句话说就是同样的3层协议比如IP要为不同的4层协议提供不同的实现,这是因为我们熟知的4层协议分为流和数据报两种类型,流式协议比如tcp在4层就处理了大量的逻辑,比如分段等等,而数据报协议比如 udp却不处理这些,因此当它们被交付到3层的时候,针对于分段来讲,3层逻辑对tcp需要作的事就很少了,而对udp就要有大量的工作要做,这就导致了对于tcp来说,只需要调用简单的ip_queue_xmit即可,而对于udp来说,就需要调用更复杂的 udp_push_pending_frames。从名称上看,pending一词表明,该函数并不是即时调用的,可能4层协议逻辑尽可能多的将udp数据报填充之后再发送到3层的,事实确实是这样的,然而对于tcp来讲也有pending一说,意义是一样的。总之,udp的3层实现更复杂一些,理由就是它的4层实现太简单,而3层的复杂逻辑比如分片是怎么也逃不掉的。
     udp的4层发送函数是udp_sendmsg,它进一步又调用了:

err = ip_append_data(sk, ip_generic_getfrag, msg->msg_iov, ulen,
            sizeof(struct udphdr), &ipc, rt,
            corkreq ? msg->msg_flags|MSG_MORE : msg->msg_flags);
if (err)
    udp_flush_pending_frames(sk);
else if (!corkreq)
    err = udp_push_pending_frames(sk, up);

 
     可见,调用udp_push_pending_frames将数据报发往3层是有条件的,要么应用层强制即时发送不缓存(通过setsockopt的CORK命令),要么缓存已经满了,事实就是,如果你不强制,那么就请尊重内核的决定,任何事情总要有个默认方案的。下面看一下ip_append_data的实现逻辑,它非常复杂,复杂之处在于它帮助3层实现了很多它力所能及或者说是举手之劳的事情,那就是预分片操作。虽然它不负责3层的ip分片这件事,但是它却可以做一些事情使得接下来总逃不过的ip分片更加容易,更加有效率,ip_append_data函数首先将用户传进的数据按照查找到的路由出口mtu分成一个个的小段,然后将这些小段组合,组合的方式根据是否启用分散/聚集IO有两种方式,如果不启用分散/聚集IO,那么将所有的小段连接成一个链表,如果启用了,那么就将第二个到最后一个的片段植入到skb的skb_shinfo(skb)->frags数组中。这只是大体上的流程,细节上稍微复杂一些,在分配内存的时候,该函数根据路由出口考虑到了2层的协议头,它预留了协议头大小的空间,并且如果启用了分散/聚集IO的话,它将不再为每一个mtu大小的数据(加上头)分配一个skb,而是将后续的数据填充到当前skb的 frags数组中,这样在最终网卡发送的时候,只要将这些frags映射到设备空间就可以了。
    
现在有一个问题,udp_sendmsg中为何要将ip_append_data和udp_push_pending_frames分开呢?实际上这增加了应用程序对底层的控制,udp套结字有一个UDP_CORK的选项,在这个选项置为1的情况下,应用层的数据是不会被发出去的,只有在这个选项置为0的时候才会发送数据,这就实现了累积的发送,同时这个特性会影响到ip_append_data的内存分配,ip_append_data本质上就是帮ip层忙的,如果UDP_CORK为1的话,ip_append_data的falg参数将会加上MSG_MORE,这样在分配内存的时候就会分配一个mtu的大小而不仅仅是当前数据的大小,因为它知道马上还会有数据来,即使当前的数据长度没有一个mtu的大小,接下来的数据还是可以使用剩余空间的,但是如果启用了分散/聚集IO的话就不能这样了,因为分散 /聚集IO的本质就是“呆在原地”:
if ((flags & MSG_MORE) && !(rt->u.dst.dev->features&NETIF_F_SG))
    alloclen = maxfraglen;
另外的一个特性是,如果路由出口网卡启用了分散/聚集IO,那么就不是往skb的剩余空间塞数据了,而是往page的剩余空间塞数据:

if (!(rt->u.dst.dev->features&NETIF_F_SG)) {
    unsigned int off;
    off = skb->len;
    if (getfrag(from, skb_put(skb, copy), offset, copy, off, skb) < 0) {
        ...
    }
} else {
    int i = skb_shinfo(skb)->nr_frags;
    skb_frag_t *frag = &skb_shinfo(skb)->frags[i-1];
    struct page *page = sk->sk_sndmsg_page;
    int off = sk->sk_sndmsg_off;
    unsigned int left;
    if (page && (left = PAGE_SIZE - off) > 0) {
        if (page != frag->page) {
            get_page(page);
            skb_fill_page_desc(skb, i, page, sk->sk_sndmsg_off, 0);
            frag = &skb_shinfo(skb)->frags[i];
        }
    } else if (i < MAX_SKB_FRAGS) {
        page = alloc_pages(sk->sk_allocation, 0);
        sk->sk_sndmsg_page = page;
        sk->sk_sndmsg_off = 0;
        skb_fill_page_desc(skb, i, page, 0, 0);
        frag = &skb_shinfo(skb)->frags[i];
        skb->truesize += PAGE_SIZE;
        atomic_add(PAGE_SIZE, &sk->sk_wmem_alloc);
    }
    ...
    if (getfrag(from, page_address(frag->page)+frag->page_offset+frag->size, offset, copy, skb->len, skb) < 0)
...

 分散/聚集IO尽可能的不拷贝数据,它尽可能的将数据集中在整个页面内部。
     分离了ip_append_data和udp_push_pending_frames,应用程序可以多次调用ip_append_data,然后一次性发送,以此可以控制数据收发的响应速度和吞吐量。对于tcp来讲,它也有一个TCP_CORK选项,然而这个cork和udp的意义不同,tcp的cork并没有强制性,也就是说就算你设置了cork,数据在一定条件下也是会自动发出去的,道理在于,首先tcp的实现要遵从它的协议标准,然后再考虑效率优化和应用程序定制,而cork就是为了优化和定制而产生的,因此它也就是只能在毫无连接和任何控制机制的udp协议上实施专制和独裁。

 

本文来自CSDN博客,转载请标明出处:http://blog.csdn.net/dog250/archive/2010/10/13/5939241.aspx

分享到:
评论

相关推荐

    嵌入式Linux网络体系结构设计与TCP/IP协议栈.part5

    第8~9章介绍传输层数据收发过程,重点介绍基于套接字的TCP/UDP传输实现。第10章讨论了Linux内核套接字层的实现,以及套接字层与应用层、传输层之间的接口。第11章介绍网络应用软件的开发技术,以及内核对网络应用的...

    嵌入式Linux网络体系结构设计与TCP/IP协议栈.part3.rar

    第8~9章介绍传输层数据收发过程,重点介绍基于套接字的TCP/UDP传输实现。第10章讨论了Linux内核套接字层的实现,以及套接字层与应用层、传输层之间的接口。第11章介绍网络应用软件的开发技术,以及内核对网络应用的...

    嵌入式Linux网络体系结构设计与TCP/IP协议栈.part4.rar

    第8~9章介绍传输层数据收发过程,重点介绍基于套接字的TCP/UDP传输实现。第10章讨论了Linux内核套接字层的实现,以及套接字层与应用层、传输层之间的接口。第11章介绍网络应用软件的开发技术,以及内核对网络应用的...

    嵌入式Linux网络体系结构设计与TCP/IP协议栈.part1

    第8~9章介绍传输层数据收发过程,重点介绍基于套接字的TCP/UDP传输实现。第10章讨论了Linux内核套接字层的实现,以及套接字层与应用层、传输层之间的接口。第11章介绍网络应用软件的开发技术,以及内核对网络应用的...

    嵌入式Linux网络体系结构设计与TCP/IP协议栈.part2.rar

    第8~9章介绍传输层数据收发过程,重点介绍基于套接字的TCP/UDP传输实现。第10章讨论了Linux内核套接字层的实现,以及套接字层与应用层、传输层之间的接口。第11章介绍网络应用软件的开发技术,以及内核对网络应用的...

    linux tcp ip 协议栈内核代码静态分析

    协议栈内核代码静态分析,对linux内核中以太网设备驱动的注册、802.3、网络层、TCP、UDP层双向调用回路有详细注释分析。

    Linux网络编程

    内容包含Linux系统概述、Linux编程环境、Linux文件系统简介、Linux下的进程和线程、TCP/IP协议族、应用层网络服务程序、TCP编程、主机信息获取、数据IO复用、UDP编程、高级套接字、套接字选项、原始套接字、服务器...

    linux网络编程

    内容包含Linux系统概述、Linux编程环境、Linux文件系统简介、Linux下的进程和线程、TCP/IP协议族、应用层网络服务程序、TCP编程、主机信息获取、数据IO复用、UDP编程、高级套接字、套接字选项、原始套接字、服务器...

    基于Linux 的防火墙技术研究

    Linux 在其2.4 内核中内置了一个基于网络层解决方案的防火墙系统—Netfilter/Iptables,它使得 用户能够很方便地在网络边界定制对数据包的各种控制,如有状态或无状态的包过滤、各种类型的网 络地址转换、流量控制及...

    linux 系统源码全面剖析

    Linux网桥工作原理与实现 其他 定时器实现 多路复用I/O GDB原理之ptrace 容器相关 docker实现原理之 - namespace docker实现原理之 - CGroup介绍 docker实现原理之 - CGroup实现原理 docker实现原理之 - OverlayFS...

    Linux网络编程 part2

    内容包含linux系统概述、linux编程环境、linux文件系统简介、linux下的进程和线程、tcp/ip协议族、应用层网络服务程序、tcp编程、主机信息获取、数据io复用、udp编程、高级套接字、套接字选项、原始套接字、服务器...

    Linux网络编程 视频 教程

    Linux网络编程之TCP/IP基础篇 Linux网络编程之socket编程篇 Linux网络编程之进程间通信篇 Linux网络编程之线程篇 Linux网络编程之TCP/IP基础篇 01TCPIP基础(一) ISO/OSI参考模型 TCP/IP四层模型 基本概念...

    linux网络编程-宋敬彬-part1

    1.5.1 Linux内核的主要模块 7 1.5.2 Linux的文件结构 9 1.6 GNU通用公共许可证 10 1.6.1 GPL许可证的历史 10 1.6.2 GPL的自由理念 10 1.6.3 GPL的基本条款 11 1.6.4 关于GPL许可证的争议 12 1.7 Linux...

    网络编程中使用tun虚拟网络接口建立IP隧道的实例

    TUN模拟了网络层设备,操作第三层(网络层)数据包,通常我们使用的TCP/UDP报文在网络层使用的IP协议,所以使用TUN设备,需要自己构建IP报头和TCP/UDP报头; Linux通过TUN/TAP设备向绑定该设备的用户空间的应用程序...

    Linux网络编程 part3

    内容包含linux系统概述、linux编程环境、linux文件系统简介、linux下的进程和线程、tcp/ip协议族、应用层网络服务程序、tcp编程、主机信息获取、数据io复用、udp编程、高级套接字、套接字选项、原始套接字、服务器...

    Linux防火墙.pdf

     2.3.7 Linux内核IGMP攻击 32  2.4 网络层回应 33  2.4.1 网络层过滤回应 33  2.4.2 网络层阈值回应 33  2.4.3 结合多层的回应 34  第3章 传输层的攻击与防御 35  3.1 使用iptables记录传输层首部 35 ...

Global site tag (gtag.js) - Google Analytics