TCP输出
tcp_output
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);
}