一、tcp_write_xmit()将发送队列上的SBK发送出去,返回值为0表示发送成功。函数执行过程如下:
1、检测拥塞窗口的大小。
2、检测当前报文是否完全处在发送窗口内。
3、检测报文是否使用nagle算法进行发送。
4、通过以上检测后将该SKB发送出去。

5、循环检测发送队列上所有未发送的SKB。

static int tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
int push_one, gfp_t gfp)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb;
unsigned int tso_segs, sent_pkts;
int cwnd_quota;
int result; /*sent_pkts用来统计函数中已发送报文总数。*/
sent_pkts = 0; if (!push_one) {
/* Do MTU probing. */
result = tcp_mtu_probe(sk);
if (!result) {
return 0;
} else if (result > 0) {
sent_pkts = 1;
}
}
/*13~21首先初始化为0,接着发送一个路径MTU探测报文,如果成功则发送报文数加1。*/ /*如果发送队列不为空,则准备开始发送报文*/
while ((skb = tcp_send_head(sk))) {
unsigned int limit; /*设置有关TSO的信息,包括GSO类型,GSO分段的大小等等。这些信息是准备给软件TSO分段使用的。
如果网络设备不支持TSO,但又使用了TSO功能,则报文在提交给网络设备之前,需进行软分段,即由代码实现TSO分段。*/
tso_segs = tcp_init_tso_segs(sk, skb, mss_now);
BUG_ON(!tso_segs); /*检测拥塞窗口的大小,如果为0,则说明拥塞窗口已满,目前不能发送。
拿拥塞窗口和正在网络上传输的包数目相比,如果拥塞窗口还大,则返回拥塞窗口减掉正在网络上传输的包数目剩下的大小。
该函数目的是判断正在网络上传输的包数目是否超过拥塞窗口,如果超过了,则不发送。
tcp_cwnd_test()源代码见段二*/
cwnd_quota = tcp_cwnd_test(tp, skb);
if (!cwnd_quota)
break; /*检测当前报文是否完全处于发送窗口内,如果是则可以发送,否则不能发送*/
if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)))
break; /*tso_segs=1表示无需tso分段*/
if (tso_segs == 1) {
/*根据nagle算法,计算是否需要发送数据*/
if (unlikely(!tcp_nagle_test(tp, skb, mss_now,
(tcp_skb_is_last(sk, skb) ?
nonagle : TCP_NAGLE_PUSH))))
break;
} else {
/*如果需要TSO分段,则检测该报文是否应该延时发送。tcp_tso_should_defer()用来检测GSO段是否需要延时发送。
在段中有FIN标志,或者不处于open拥塞状态,或者TSO段延时超过2个时钟滴答,或者拥塞窗口和发送窗口的最小值大于64K或三倍的当前有效MSS,
在这些情况下会立即发送,而其他情况下会延时发送,这样主要是为了减少软GSO分段的次数,以提高性能。*/
if (!push_one && tcp_tso_should_defer(sk, skb))
break;
} /*limit为再次分段的段长,初始化为当前MSS*/
limit = mss_now;
/*在TSO分片大于1并且不是URG模式下,通过mss_now计算limit的值
以发送窗口和拥塞窗口的最小值作为分段段长*/
if (tso_segs > 1 && !tcp_urg_mode(tp))
limit = tcp_mss_split_point(sk, skb, mss_now,
cwnd_quota); /*如果SKB中的数据长度大于分段段长,则调用tso_fragment()根据该段长进行分段,如果分段失败则暂不发送*/
if (skb->len > limit &&
unlikely(tso_fragment(sk, skb, limit, mss_now, gfp)))
break;
/*line61~71:根据条件,可能需要对SKB中的报文进行分段处理,分段的报文包括两种:一种是普通的用MSS分段的报文,另一种则是TSO分段的报文。
能否发送报文主要取决于两个条件:一是报文需完全在发送窗口中,而是拥塞窗口未满。第一种报文,应该不会再分段了,因为在tcp_sendmsg()中创建报文的SKB时已经根据MSS处理了,
而第二种报文,则一般情况下都会大于MSS,因为通过TSO分段的段有可能大于拥塞窗口的剩余空间,如果是这样,就需要以发送窗口和拥塞窗口的最小值作为段长对报文再次分段。*/ /*更新TCP时间戳,记录此报文发送的时间,
#define tcp_time_stamp ((__u32)(jiffies))*/
TCP_SKB_CB(skb)->when = tcp_time_stamp; /*调用tcp_transmit_skb()发送TCP段,其中第三个参数1表示是否需要克隆被发送的报文,详见后续对此函数的分析*/
if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))
break; /* Advance the send_head. This one is sent out.
* This call will increment packets_out.
*/
/*调用tcp_event_new_data_sent()-->tcp_advance_send_head()更新sk_send_head,即取发送队列中的下一个SKB。
同时更新snd_nxt,即等待发送的下一个TCP段的序号,然后统计发出但未得到确认的数据报个数。
最后如果发送该报文前没有需要确认的报文,则复位重传定时器,对本次发送的报文做重传超时计时。*/
tcp_event_new_data_sent(sk, skb); /*更新struct tcp_sock中的snd_sml字段。snd_sml表示最近发送的小包(小于MSS的段)的最后一个字节序号,
在发送成功后,如果报文小于MSS,即更新该字段,主要用来判断是否启动nagle算法*/
tcp_minshall_update(tp, mss_now, skb);
sent_pkts++;//更新已发送报文总数 if (push_one)
break;
} /*如果本次有数据发送,则对TCP拥塞窗口进行检查确认。*/
if (likely(sent_pkts)) {
tcp_cwnd_validate(sk);
return 0;
} /*如果本次没有数据发送,则根据已发送但未确认的报文数packets_out和sk_send_head返回,packets_out不为零或sk_send_head为空都视为有数据发出,因此返回成功。*/
return !tp->packets_out && tcp_send_head(sk);
}

二、tcp_init_tso_segs()函数
该函数根据当前mss的值重新设置数据包中的struct skb_shared_info内的关于GSO的内容项。

static int tcp_init_tso_segs(struct sock *sk, struct sk_buff *skb,
unsigned int mss_now)
{
int tso_segs = tcp_skb_pcount(skb); if (!tso_segs || (tso_segs > 1 && tcp_skb_mss(skb) != mss_now)) {
tcp_set_skb_tso_segs(sk, skb, mss_now);
tso_segs = tcp_skb_pcount(skb);
}
return tso_segs;
}

三、tcp_cwnd_test()函数

static inline unsigned int tcp_cwnd_test(struct tcp_sock *tp,
struct sk_buff *skb)
{
u32 in_flight, cwnd; /* Don't be strict about the congestion window for the final FIN. */
/*对FIN包不检测,让他通过*/
if ((TCP_SKB_CB(skb)->flags & TCPHDR_FIN) && tcp_skb_pcount(skb) == 1)
return 1; /*计算正在网络上传输的包数目*/
in_flight = tcp_packets_in_flight(tp);
/*获取当前拥塞窗口的大小,snd_cwnd表示当前拥塞窗口的大小*/
cwnd = tp->snd_cwnd;
if (in_flight < cwnd)
return (cwnd - in_flight); return 0;
}
static inline unsigned int tcp_packets_in_flight(const struct tcp_sock *tp)
{
return tp->packets_out - tcp_left_out(tp) + tp->retrans_out;
}
static inline unsigned int tcp_left_out(const struct tcp_sock *tp)
{
return tp->sacked_out + tp->lost_out;
}

这是通过使用tcp的sock中的几个计数器运算得出的。
packets_out:从发送队列发出而未得到确认的TCP段的数目,该值是动态的,当有新的段发出或有新的确认收到都会增加或减少该值。
retrans_out:重传还未得到确认的TCP段数目。
tcp_left_out:已离开主机在网络中且未确认的TCP段数,包含两种情况,一是通过SACK确认的段,即sacked_out,而是已丢失的段,即lost_out。
所以left_out = sacked_out + lost_out。
left_out需要与packets_out进行区分,packets_out只是离开发送队列(不一定已离开主机),而left_out则必定离开了主机。所以packets_out必定大于或等于left_out。

四、tcp_snd_wnd_test()函数

static inline int tcp_snd_wnd_test(struct tcp_sock *tp, struct sk_buff *skb,
unsigned int cur_mss)
{
u32 end_seq = TCP_SKB_CB(skb)->end_seq; if (skb->len > cur_mss)
end_seq = TCP_SKB_CB(skb)->seq + cur_mss; return !after(end_seq, tcp_wnd_end(tp));
}

#define TCP_SKB_CB(__skb)    ((struct tcp_skb_cb *)&((__skb)->cb[0]))

TCP层在SKB区块有个私有信息控制块,即skb_buff结构的cb成员,TCP利用这个字段存储了一个tcp_skb_cb结构。在TCP层,用宏TCP_SKB_CB实现访问该信息控制块,以增强代码的可读性。

struct tcp_skb_cb {
union {
struct inet_skb_parm h4;
#if defined(CONFIG_IPV6) || defined (CONFIG_IPV6_MODULE)
struct inet6_skb_parm h6;
#endif
} header; /* For incoming frames */
__u32 seq; /* Starting sequence number */
__u32 end_seq; /* SEQ + FIN + SYN + datalen */
__u32 when; /* used to compute rtt's */
__u8 flags; /* TCP header flags. */
__u8 sacked; /* State flags for SACK/FACK. */
#define TCPCB_SACKED_ACKED 0x01 /* SKB ACK'd by a SACK block */
#define TCPCB_SACKED_RETRANS 0x02 /* SKB retransmitted */
#define TCPCB_LOST 0x04 /* SKB is lost */
#define TCPCB_TAGBITS 0x07 /* All tag bits */ #define TCPCB_EVER_RETRANS 0x80 /* Ever retransmitted frame */
#define TCPCB_RETRANS (TCPCB_SACKED_RETRANS|TCPCB_EVER_RETRANS) __u32 ack_seq; /* Sequence number ACK'd */
}

union {...} header:
   在TCP处理接收到的TCP段之前,下层协议(IPv4或IPv6)会先处理该段,且会利用SKB中的控制块来记录每一个包中的信息,例如IPv4会记录从IP首部中解析出来的IP首部选项。
为了不破坏三层协议层私有数据,在SKB中TCP控制块的前部定义了这个结构,这包括IPv4和IPv6。
__u32 seq
__u32 end_seq
   seq为当前段开始序号,而end_seq为当前段开始序号加上当前段数据长度,如果标志域中存在SYN或FIN标志,则还需加1,因为SYN和FIN标志都会消耗一个序号,利用end_seq、seq和标志,很容易得到数据长度。
__u32 when
   段发送时间及段发送时记录的当前jffies值。必要时,此值也用来计算RTT。
__u8 flags
   记录原始TCP首部标志。发送过程中,tcp_transmit_skb()在发送TCP段之前会根据此标志来填充发送段的TCP首部的标志域;接收过程中,会提取接收段的TCP首部标志到该字段中。
__u8 sacked
   主要用来描述段的重传状态,同时标识包是否包含紧急数据。检查接收到的SACK,根据需要更新TCPCB_TAGBITS,重传引擎会根据该标志位来确定是否需要重传。一旦重传超时发生,所有的SACK状态标志将被清除,因为无须再关心其状态。无论通过哪种方式重传了包,重传超时或快速重传,都会设置TCPCB_EVER_RETRANS标志位。tcp_restransmit_skb()中设置TCPCB_EVER_RETRANS和TCPCB_SACKED_RETRANS标志位,tcp_enter_loss()中则清除TCPCB_SACKED_RETRANS标志位。
sacked的取值如下:
TCPCB_SACKED_ACKED:该段通过SACK被确认。
TCPCB_SACKED_RETRANS:该段已经重传。
TCPCB_LOST:该段在传输过程中已丢失。
__u32 ack_seq
    接收到的TCP段首部中的确认序号。
from:http://sunjiangang.blog.chinaunix.net/uid-9543173-id-3543419.html

TCP发送源码学习(2)--tcp_write_xmit的更多相关文章

  1. TCP发送源码学习(1)--tcp_sendmsg

    一.tcp_sendmsg()函数分析: int tcp_sendmsg(struct kiocb *iocb, struct sock *sk, struct msghdr *msg, size_t ...

  2. TCP发送源码学习(3)--tcp_transmit_skb

    一.tcp_transmit_skb static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, g ...

  3. RocketMQ 源码学习笔记————Producer 是怎么将消息发送至 Broker 的?

    目录 RocketMQ 源码学习笔记----Producer 是怎么将消息发送至 Broker 的? 前言 项目结构 rocketmq-client 模块 DefaultMQProducerTest ...

  4. RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的?

    目录 RocketMQ 源码学习笔记 Producer 是怎么将消息发送至 Broker 的? 前言 项目结构 rocketmq-client 模块 DefaultMQProducerTest Roc ...

  5. Netty 源码学习——EventLoop

    Netty 源码学习--EventLoop 在前面 Netty 源码学习--客户端流程分析中我们已经知道了一个 EventLoop 大概的流程,这一章我们来详细的看一看. NioEventLoopGr ...

  6. MVC系列——MVC源码学习:打造自己的MVC框架(一:核心原理)

    前言:最近一段时间在学习MVC源码,说实话,研读源码真是一个痛苦的过程,好多晦涩的语法搞得人晕晕乎乎.这两天算是理解了一小部分,这里先记录下来,也给需要的园友一个参考,奈何博主技术有限,如有理解不妥之 ...

  7. igmpproxy源码学习——igmpProxyInit()

    igmpproxy源码学习--igmpProxyInit()函数具体解释.igmpproxy初始化 在执行igmpproxy的主程序igmpproxyRun()之前须要对igmpproxy进行一些配置 ...

  8. 源码学习之ASP.NET MVC Application Using Entity Framework

    源码学习的重要性,再一次让人信服. ASP.NET MVC Application Using Entity Framework Code First 做MVC已经有段时间了,但看了一些CodePle ...

  9. 【Spark2.0源码学习】-1.概述

          Spark作为当前主流的分布式计算框架,其高效性.通用性.易用性使其得到广泛的关注,本系列博客不会介绍其原理.安装与使用相关知识,将会从源码角度进行深度分析,理解其背后的设计精髓,以便后续 ...

随机推荐

  1. building a new horizon

    昨天是4月14日,也是我的23岁生日.正好去参加GDG举办的WTM,这次的主题是building a new horizon. 写一下印象深刻的分享者和她们的闪光点. 1.羡辙-从灵感到落地 羡辙是在 ...

  2. eclipse下maven插件搭建springmvc之helloworld

    这几天学习了怎样使用maven,最终还是要回归web项目嘛,所以主要还是使用eclipse插件. 1 下载eclipse maven插件. 其实新版的eclipse已经集成了maven:lunar.m ...

  3. How to preview html file in our browser at sublime text?

    sublime preview html.md open In Browser what should we do if we want to preview html file in our bro ...

  4. [LeetCode] Circular Array Loop 环形数组循环

    You are given an array of positive and negative integers. If a number n at an index is positive, the ...

  5. Mlecms 反射型xss && 后台任意文件下载

    应该算0day吧,自己分析出来的,有点鸡肋,不过小cms分析确实比较简单. xss地址:search.php?word=a><img+src=1+onerror=alert`1`>a ...

  6. python学习记录 - python3.x中如何实现print不换行

    python3.x中如何实现print不换行   大家应该知道python中print之后是默认换行的, 那如何我们不想换行,且不想讲输出内容用一个print函数输出时,就需要改变print默认换行的 ...

  7. ●BZOJ 4596 [Shoi2016]黑暗前的幻想乡

    题链: http://www.lydsy.com/JudgeOnline/problem.php?id=4596 题解: 容斥,矩阵树定理,矩阵行列式 先说说容斥:(一共有 N-1个公司) 令 f[i ...

  8. 计蒜客NOIP2017提高组模拟赛(五)day1-机智的 AmyZhi

    传送门 很水的题目啦QAQ #include<cstdio> #include<cstdlib> #include<algorithm> #include<c ...

  9. [HOJ2634] How to earn more 最大权闭合子图

    Xiao Ming is an expert in computer science and technology, so he can get a lot of projects every mon ...

  10. hdu 5532(最长上升子序列)

    Input The first line contains an integer T indicating the total number of test cases. Each test case ...