• 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);
    }
    
  • 相关阅读:
    Django ORM操作及进阶
    Django ORM字段和字段参数
    Django视图系统
    Django路由系统
    Django模板语言
    Django项目创建及相关配置,在pycharm终端打印SQL语句,在Python脚本中调用Django环境
    SQLALchemy之ORM操作
    SQLALchemy之介绍,基本使用
    SQLAlchemy创建表和删除表
    线程的通信与协作:sleep、wait、notify、yield、join关系与区别
  • 原文地址:https://www.cnblogs.com/ukernel/p/9190991.html
Copyright © 2020-2023  润新知