TCP/IP详解V2(四)之TCP协议

TCP输出

tcp_output

  • 功能A:用于处理TCP的输出
    ```
    int
    tcp_output(tp)
    register struct tcpcb tp;
    {
    register struct socket
    so = tp->t_inpcb->inp_socket; //从TCP PCB中获取VFS中的SOCKET
    register long len, win;
    int off, flags, error;
    register struct mbuf m;
    register struct tcpiphdr
    ti;
    u_char opt[MAX_TCPOPTLEN];
    unsigned optlen, hdrlen;
    int idle, sendalot;

    idle = (tp->snd_max == tp->snd_una); //判断是否在等待ACK
    if (idle && tp->t_idle >= tp->t_rxtcur) //如果没有在等待ACK并且并且在一个往返时间内没有收到对端发送的数据报(空闲的时间>RTO)
    tp->snd_cwnd = tp->t_maxseg; //将拥塞窗口设置为1个报文段
    again: //从这块开始是发送报文段的部分
    sendalot = 0; //如果有多个报文段需要发送将sendalot置为1
    off = tp->snd_nxt - tp->snd_una; //表示即将发送的数据报与已发送但是未确认的数据报之间的偏移量
    win = min(tp->snd_wnd, tp->snd_cwnd); //获取接收方接收窗口与发送方的拥塞窗口之间的较小值

    flags = tcp_outflags[tp->t_state]; //获取当前数据报的flag
    if (tp->t_force) { //判断是否需要强制发送,比如说持续定时器的超时会导致强制发送
    if (win == 0) { //持续定时器到期,发送探测数据报文(1个字节)探测对方的窗口
    if (off < so->so_snd.sb_cc) //如果发送缓存中有数据存在,将FIN置位
    flags &= ~TH_FIN;
    win = 1; //说明有一个数据需要发送
    } else {
    tp->t_timer[TCPT_PERSIST] = 0; //如果win非零,即有带外数据需要发送,持续定时器复位,将指数退避算法的索引置0
    tp->t_rxtshift = 0;
    }
    }

    len = min(so->so_snd.sb_cc, win) - off; //获取还没有发送的缓存大小与win中的较小值

    if (len < 0) { //如果待发送的len < 0,造成len<0的情况是:接收方收缩了窗口
    len = 0; //调整len = 0
    if (win == 0) { //如果对端通知的接收窗口的大小为0
    tp->t_timer[TCPT_REXMT] = 0; //将重传定时器置为0,任何等待的重传将被取消
    tp->snd_nxt = tp->snd_una; //并将下一次发送的数据序号调整为未被确认的数据序号
    }
    }
    if (len > tp->t_maxseg) { //如果待待发送的数据报长度>MSS
    len = tp->t_maxseg; //将待发送的len调整为MSS
    sendalot = 1; //并置位需要发送更多数据报的标志
    }
    if (SEQ_LT(tp->snd_nxt + len, tp->snd_una + so->so_snd.sb_cc)) //如果本地发送没有清空发送缓存,则需要清理flag中的FIN标志
    flags &= ~TH_FIN;

    win = sbspace(&so->so_rcv); //从这块开始,win表示本地向对端通告的接收窗口的大小

    if (len) {
    if (len == tp->t_maxseg) //如果即将发送的长度==MSS,直接去发送
    goto send;
    if ((idle || tp->t_flags & TF_NODELAY) &&
    len + off >= so->so_snd.sb_cc) //如果(无需等待对方的ACK,或者NODELAY被取消) 并且TCP正在清空发送缓存,跳转去发送
    goto send;
    if (tp->t_force) //如果设置了强制发送,跳转区发送
    goto send;
    if (len >= tp->max_sndwnd / 2) //如果待发送的数据 > 发送窗口/2,立刻发送数据
    goto send;
    if (SEQ_LT(tp->snd_nxt, tp->snd_max)) //如果nxt小于max,必须发送报文
    goto send;
    }

    if (win > 0) { //如果接收方的窗口>0

    long adv = min(win, (long)TCP_MAXWIN << tp->rcv_scale) -
        (tp->rcv_adv - tp->rcv_nxt);    //获取接收方接收窗口与连接上允许的最大窗口大小之间的最小值
    
    if (adv >= (long) (2 * tp->t_maxseg))    //如果这个最小值 > 2MSS,转去发送
        goto send;
    if (2 * adv >= (long) so->so_rcv.sb_hiwat)    //如果可用空间大于插口接收缓存的一半,转去发送
        goto send;

    }

    if (tp->t_flags & TF_ACKNOW) //如果置位了ACK,转去发送
    goto send;
    if (flags & (TH_SYN|TH_RST)) //如果置位了SYN或者RST,转去发送
    goto send;
    if (SEQ_GT(tp->snd_up, tp->snd_una))
    goto send;
    if (flags & TH_FIN &&
    ((tp->t_flags & TF_SENTFIN) == 0 || tp->snd_nxt == tp->snd_una)) //如果置位了FIN,那么满足以下条件就转去发送:还没有发送过FIN或者FIN等待重传
    goto send;

    if (so->so_snd.sb_cc && tp->t_timer[TCPT_REXMT] == 0 &&
    tp->t_timer[TCPT_PERSIST] == 0) { //如果发送缓存存在,并且重传定时器和持续定时器都没有设定,根据指数退避的参数与RTO启动持续定时器
    tp->t_rxtshift = 0;
    tcp_setpersist(tp);
    }

    return (0);

send:
optlen = 0; //选项长度为0
hdrlen = sizeof (struct tcpiphdr); //获取TCP/IP的长度
if (flags & TH_SYN) { //如果置位了SYN,构造SYN的报文头部
tp->snd_nxt = tp->iss; //填充ISS
if ((tp->t_flags & TF_NOOPT) == 0) { //永远成功
u_short mss;

        opt[0] = TCPOPT_MAXSEG;    //填充MSS
        opt[1] = 4;
        mss = htons((u_short) tcp_mss(tp, 0));
        bcopy((caddr_t)&mss, (caddr_t)(opt + 2), sizeof(mss));
        optlen = 4;
 
        if ((tp->t_flags & TF_REQ_SCALE) &&
            ((flags & TH_ACK) == 0 ||
            (tp->t_flags & TF_RCVD_SCALE))) {        //构造窗口大小选项
            *((u_long *) (opt + optlen)) = htonl(
                TCPOPT_NOP << 24 |
                TCPOPT_WINDOW << 16 |
                TCPOLEN_WINDOW << 8 |
                tp->request_r_scale);
            optlen += 4;    //再次递增opt选项
        }
    }
}

if ((tp->t_flags & (TF_REQ_TSTMP|TF_NOOPT)) == TF_REQ_TSTMP &&
     (flags & TH_RST) == 0 &&
    ((flags & (TH_SYN|TH_ACK)) == TH_SYN ||
     (tp->t_flags & TF_RCVD_TSTMP))) {        //判断是否需要构造时间戳选项,如果需要的话,就构造时间戳选项
    u_long *lp = (u_long *)(opt + optlen);

    /* Form timestamp option as shown in appendix A of RFC 1323. */
    *lp++ = htonl(TCPOPT_TSTAMP_HDR);
    *lp++ = htonl(tcp_now);
    *lp   = htonl(tp->ts_recent);
    optlen += TCPOLEN_TSTAMP_APPA;
}

hdrlen += optlen;    //将头部长度加上opt length

 if (len > tp->t_maxseg - optlen) {    //如果待发送数据长度>MSS-选项长度,则需要相应的减少数据量,并判断是否需要再次发送
    len = tp->t_maxseg - optlen;
    sendalot = 1;
 }

if (len) {        //根据发送的数据量更新全局统计值
    if (tp->t_force && len == 1)
        tcpstat.tcps_sndprobe++;
    else if (SEQ_LT(tp->snd_nxt, tp->snd_max)) {
        tcpstat.tcps_sndrexmitpack++;
        tcpstat.tcps_sndrexmitbyte += len;
    } else {
        tcpstat.tcps_sndpack++;
        tcpstat.tcps_sndbyte += len;
    }

    MGETHDR(m, M_DONTWAIT, MT_HEADER);        //为IP和TCP首部分配mbuf
    if (m == NULL) {
        error = ENOBUFS;
        goto out;
    }
    m->m_data += max_linkhdr;    //为链路层头部空出16字节
    m->m_len = hdrlen;    //将mbuf中的长度更新为TCP和IP头部的长度
    if (len <= MHLEN - hdrlen - max_linkhdr) {        //如果数据长度小于44(100 - 40 - 16)字节
        m_copydata(so->so_snd.sb_mb, off, (int) len,
            mtod(m, caddr_t) + hdrlen);        //数据直接copy进首部mbuf中,然后调整首部mbuf中的数据长度
        m->m_len += len;
    } else {
        m->m_next = m_copy(so->so_snd.sb_mb, off, (int) len);    //如果数据量较大,创建新的mbuf,并将数据copy进mbuf,顺带调整mbuf链
        if (m->m_next == 0)
            len = 0;
    }

endif

    if (off + len == so->so_snd.sb_cc)        //这个PUSH的定位有点模糊
        flags |= TH_PUSH;
} else {
    if (tp->t_flags & TF_ACKNOW)    //这是一个纯ACK报文段,更新统计量
        tcpstat.tcps_sndacks++;
    else if (flags & (TH_SYN|TH_FIN|TH_RST))
        tcpstat.tcps_sndctrl++;
    else if (SEQ_GT(tp->snd_up, tp->snd_una))
        tcpstat.tcps_sndurg++;
    else
        tcpstat.tcps_sndwinup++;

    MGETHDR(m, M_DONTWAIT, MT_HEADER);    //获取一个mbuf,以存放TCP/IP头部
    if (m == NULL) {
        error = ENOBUFS;
        goto out;
    }
    m->m_data += max_linkhdr;        //为链路层协议空16字节的空间
    m->m_len = hdrlen;
}
m->m_pkthdr.rcvif = (struct ifnet *)0;
ti = mtod(m, struct tcpiphdr *);
if (tp->t_template == 0)
    panic("tcp_output");
bcopy((caddr_t)tp->t_template, (caddr_t)ti, sizeof (struct tcpiphdr));        //将TCP PCB中的TCP/IP Header存放到mbuf中

if (flags & TH_FIN && tp->t_flags & TF_SENTFIN && 
    tp->snd_nxt == tp->snd_max)        //这里是应对FIN重传的现象,在发送FIN的时候,会递增snd_nxt,所以在重传的时候会递减snd_nxt
    tp->snd_nxt--;

if (len || (flags & (TH_SYN|TH_FIN)) || tp->t_timer[TCPT_PERSIST])        //如果packet中存在数据,或者发送SYN|FIN,或者持续定时器置位,序列号填充为正常
    ti->ti_seq = htonl(tp->snd_nxt);        //填充TCP报文段中的发送序列号
else    //不正常情况,意味发送了异常情况
    ti->ti_seq = htonl(tp->snd_max);
ti->ti_ack = htonl(tp->rcv_nxt);    //填充TCP报文段中的ACK序列号,就是说已经在recvbuf中经过确认的序列号,期待接收的下一个字节
if (optlen) {    //如果存在TCP首部,将TCP首部填充进TCP数据报中,调整TCP Header中的字节
    bcopy((caddr_t)opt, (caddr_t)(ti + 1), optlen);
    ti->ti_off = (sizeof (struct tcphdr) + optlen) >> 2;
}
ti->ti_flags = flags;    //填充TCP的flag

if (win < (long)(so->so_rcv.sb_hiwat / 4) && win < (long)tp->t_maxseg)        //如果win小于接收窗口的1/4并且win<MSS,向对端通告的窗口大小为0
    win = 0;
if (win > (long)TCP_MAXWIN << tp->rcv_scale)    //如果win>连接规定的最大值,应该将其减少为最大值
    win = (long)TCP_MAXWIN << tp->rcv_scale;
if (win < (long)(tp->rcv_adv - tp->rcv_nxt))    //如果win小于最近一次对端通告的窗口大小中的剩余空间,将win设置为其值,不允许窗口缩小
    win = (long)(tp->rcv_adv - tp->rcv_nxt);
ti->ti_win = htons((u_short) (win>>tp->rcv_scale));        //设置TCP中的窗口大小
if (SEQ_GT(tp->snd_up, tp->snd_nxt)) {        //设置紧急数据的偏移并标记URG选项
    ti->ti_urp = htons((u_short)(tp->snd_up - tp->snd_nxt));
    ti->ti_flags |= TH_URG;
} else
    tp->snd_up = tp->snd_una;       /* drag it along */

if (len + optlen)
    ti->ti_len = htons((u_short)(sizeof (struct tcphdr) +
        optlen + len));        //调整TCP中的len为:data len + optlen + tcp len
ti->ti_sum = in_cksum(m, (int)(hdrlen + len));        //对TCP的数据进行校验

if (tp->t_force == 0 || tp->t_timer[TCPT_PERSIST] == 0) {        //如果不处于持续状态
    tcp_seq startseq = tp->snd_nxt;        //记录开始发送的数据seq

    if (flags & (TH_SYN|TH_FIN)) {    //如果标记了SYN|FIN
        if (flags & TH_SYN)
            tp->snd_nxt++;    //地怎发送序列号
        if (flags & TH_FIN) {
            tp->snd_nxt++;
            tp->t_flags |= TF_SENTFIN;        //并在发送之后置位SENTFIN标记
        }
    }
    tp->snd_nxt += len;        //增加发送seq为发送的数据长度,仅仅是data len,不包括header之类的
    if (SEQ_GT(tp->snd_nxt, tp->snd_max)) {        //如果snd_nxt > snd_max,不是重传报文,相应的更新snd_max的seq
        tp->snd_max = tp->snd_nxt;
        if (tp->t_rtt == 0) {
            tp->t_rtt = 1;        
            tp->t_rtseq = startseq;
            tcpstat.tcps_segstimed++;
        }
    }

    if (tp->t_timer[TCPT_REXMT] == 0 &&
        tp->snd_nxt != tp->snd_una) {        //如果重传定时器没有启动并且报文段中有数据,即启动重传定时器
        tp->t_timer[TCPT_REXMT] = tp->t_rxtcur;
        if (tp->t_timer[TCPT_PERSIST]) {        //如果持续定时器已经启动,关闭持续,并调整指数退避的index = 0
            tp->t_timer[TCPT_PERSIST] = 0;
            tp->t_rxtshift = 0;
        }
    }
} else
    if (SEQ_GT(tp->snd_nxt + len, tp->snd_max))    //如果发送的数据大于max
        tp->snd_max = tp->snd_nxt + len;    //调整max的seq

if (so->so_options & SO_DEBUG)
    tcp_trace(TA_OUTPUT, tp->t_state, tp, ti, 0);

m->m_pkthdr.len = hdrlen + len;        //调整首部mbuf中全部的数据量

{
((struct ip *)ti)->ip_len = m->m_pkthdr.len;        //填充tcp中的信息
((struct ip *)ti)->ip_ttl = tp->t_inpcb->inp_ip.ip_ttl; 
((struct ip *)ti)->ip_tos = tp->t_inpcb->inp_ip.ip_tos; 

if BSD >= 43

error = ip_output(m, tp->t_inpcb->inp_options, &tp->t_inpcb->inp_route,
    so->so_options & SO_DONTROUTE, 0);        //将数据包交给IP层进行发送

else

error = ip_output(m, (struct mbuf *)0, &tp->t_inpcb->inp_route, 
    so->so_options & SO_DONTROUTE);

endif

}
if (error) {

out:
if (error == ENOBUFS) { //如果在分配mbuf的时候失败
tcp_quench(tp->t_inpcb, 0); //将拥塞窗口设置为只能接收一个packet,强迫执行慢启动,丢弃当前需要发送的数据报,通过重传定时器超时来处理
return (0);
}
if ((error == EHOSTUNREACH || error == ENETDOWN) //处理收到了一个SYN但是找不到到达目的地的路由,则在记录的连接上出现了一个软错误
&& TCPS_HAVERCVDSYN(tp->t_state)) {
tp->t_softerror = error;
return (0);
}
return (error);
}
tcpstat.tcps_sndtotal++;

if (win > 0 && SEQ_GT(tp->rcv_nxt+win, tp->rcv_adv))        //保存发送过程中最新通告的值
    tp->rcv_adv = tp->rcv_nxt + win;
tp->last_ack_sent = tp->rcv_nxt;        //记录上一次发送的ACK,就是recv到的最新的值
tp->t_flags &= ~(TF_ACKNOW|TF_DELACK);    //取消ACK和NODELAY置位
if (sendalot)        //判断是否还有数据等待发送
    goto again;
return (0);

}
```

猜你喜欢

转载自www.cnblogs.com/ukernel/p/9190991.html