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);
}
原文地址:https://www.cnblogs.com/ukernel/p/9190991.html