爱上香烟
分类: C/C++
2015-06-04 15:16:00
传输层常见的两大协议TCP和UDP,TCP太复杂,涉及到拥塞控制的很多内容,在《Linux内核源码剖析-TCP/IP实现》下册中也花费了大量的笔墨来讲述。
咋们先来看看一个简单的UDP。
每篇文章肯定有一个定位,不可能面面俱到,如果这篇的定位是你需要的,祝你能够学到一些新的知识
(1)UDP数据发送和接收的简要流程
(2)不涉及太多细节。
(3)力求了解UDP在协议栈中的框架以及与其他层之间的衔接
(1)《Understand Linux Kernel Internel》
(2)《Linux内核源码剖析-TCP/IP实现》
(3)linux内核源码--我使用的版本是3.2.4
注:《Understand Linux Kernel Internel》中没有关于UDP和TCP的章节,但是有很多知识还是需要的。
下图见《Linux内核源码剖析-TCP/IP实现》图22-1
图 1-1
今天讨论的内容在圈圈5下方(也就是proto_ops)下方。这个图信息很多,不过和今天的内容联系的却不多,贴出的原因是希望能够让大家心里有个数。
这边简要介绍下,如果想更全面的理解,最好去看看套接口层(见《Linux内核源码剖析-TCP/IP实现》第22~24章)。这里先默认你已经对套接口层有所理解了。
这里我们只要注意1个参数,struct sock *sk;
注2:所有的套接字都是使用该结构,那这里就会有一个当深入看的时候让人觉得奇怪的地方。就是这个 struct sock结构
先讨论有一点会比较让人觉得混乱的地方,UDP的传输控制块是struct udp_sock,但是在1、中却使用的是struct sock 结构。
要说明这点,我们先来看看下面这个图(见《Linux内核源码剖析-TCP/IP实现》图25-1)
图2-1
这个图有一个隐含的信息--这里的内存空间是连续内存,这意味着什么?
就我们先来看看udp_sock的结构体。这里需要关注的是第一个注释,它说struct inet_sock必须在第一个元素。另外需要注意的是这里在声明变量的时候用的不是指针。意味着inet_sock是和后面的参数是连续的内存的。
通过这两个说明就容易懂为什么图中的画法为什么是指连续内存了吧。
那这样做又有什么用处呢?又和struct socket中的声明有什么关系呢?原因如下:
我们会经常在代码中见到这样的语句。
明明struct inet_sock和struct sock是不同的结构,为什么他们又能够强制类型转换?这就可以去观察图2-1了,原因就在于他们是同一段连续的缓存,这样sk同时是struct sock、struct inet_sock、struct udp_sk的指针。这样也就说的了
咋们只关注UDP协议,所以看结构就从大到小看,和书上的相反,这样可以看的更清晰一些。
注:如果是IPV6协议,那代表正在处理调用sendmsg的状态标志就是AF6_INET
之所以关注这个标志,主要是因为它对后面理解ip_append_data函数的功能有很大的帮助。
这个结构关注两个参数,struct sock和struct inet_cork_full cork。cork参数中存放了UDP发送报文时候需要的一些信息。
注:如果是看IPV6的协议,那另外需要关注struct ipv6_pinfo 结构。
注2:这个源代码是3.2.4中的代码,和书上的不一样。它里面包含的内容更多。
注3:inet_sock是为IP协议设计使用的传输控制块,书上说是IPv4专用有些说的不对。
UDP和TCP在组织发送队列的方式上不同。
上面的都是一些预热,现在才能转入正题。
见《Linux内核源码剖析-TCP/IP实现》图33-3
图3-1
这个图其实就很完整的画出了发送和接收流程的调用图。
注:发送流程中这个图画的有些容易让人误解,而且信息不全。
注2:首先udp_sendmsg最后是先调用ip_append_data函数对数据进行处理,然后调用udp_push_pending_frames进行发送, 图中画法容易让人一位发送函数有两个。
注3:udp_push_pending_frames之后不是直接就到IP层,而过程却是 udp_push_pending_frames >> udp_send_skb >> ip_send_skb >> ip_local_out >> ip_push_pending_data(这个是3.2.4中的流程)
注4:IPV6的udp协议是调用udp_v6_push_pending_frames >> ip6_push_pending_frames >> ip6_local_out。(这个是3.2.4的流程)
先看下图,见《Linux内核源码剖析-TCP/IP实现》图33-9:
图3-2
可以返回去看图1-1,我们平时写程序发送一个udp报文常用sendto函数,这个函数是库函数中提供的,进入内核后会调用sys_send函数,这时候才算进入了内核。
这里最后两行需要对套接口层的结构有所了解才能看懂。
暂时记着UDP协议到最后调用的是udp_sendmsg就可以了。
注:如果是ipv6,入口就是udpv6_sendmsg
udp_sendmsg流程图如图3-3:见《Linux内核源码剖析-TCP/IP实现》图33-10
流程图很复杂,代码也很多,咋们抓重点,以及和后面会存在关系的部分类看,但是需要注意的是,这个图画的也不全对,有些地方也给画错了
(A)注意图中判断条件"通过connect()连接过"这个条件。在编程的时候我们有时候也会对udp套接字使用connect函数进行连接,这个就是这里的由来。它会产生什么效果呢?看下相关的说明吧
说白了就是,通过connect函数进行连接的UDP套接字,它可能已经自带了相应的路由项
注意:这里是“可能自带”,不意味着带着的路由项是正确的!!!
所以图中这里的分支是错的。从代码看就更清晰了:以下是udp_sendmsg中相对应的代码
另外经过路由子系统后,对于调用connect函数的套接字就会更新路由项。
所以图中正确的画法应该是,"通过connect()连接过"之后出来的“否“的线应该连接到”从路由子系统中获取目的路由缓存项“。
(B)图3-3最底下部分内容看着也让人混乱,其实说的是两个流程:
一个是,如果设置了corkreq标志,要经过 ip_append_data进行处理后,然后经过udp_push_pending_frames进行发送。
另一个是:如果没有设置corkreq标志,则直接发送。
代码如下
这个函数很复杂,请参考http://blog.csdn.net/wearenoth/article/details/7836920
注:这个函数需要认真看看,这样就可以很好的将UDP和IP层之间的数据结构给衔接起来了。
早先从流程是:填充第四层报头 -->> 计算校验和 -->> 调用 ip_push_pending_frames发送。如下图(见《Understand Linux Kernel Internel》图21-13):
图3-4
然后在ip_push_pending_frames中实现以下两点。
从发送队列中取下skb,如图3-5所示(见《Understand Linux Kernel Internel》图21-12)
图3-5
发送skb
可以总结为如下流程:
(A)填充第四层报头
(B)计算校验和
(C)从发送队列中取下skb
(D)发送取下的skb
在3.2.4内核中,这个过程进行了新的排序,并且不再使用ip_push_pending_frames函数
udp_send_msg对应(A)(B)(D)
这样一看就简单了。
最后报文都会流向dst_output函数,之后的内容可以参考http://blog.csdn.net/wearenoth/article/details/7819925
整体上,UDP协议发送的流程就是这样,其中大量细节都没说明,一是因为有书比我说的好,另外是真写下来内容就太多了。
累了,过几天再写