• TCP/IP详解V2(三)之TCP协议


    TCP

    TCP是一种面向连接的传输协议,为两端的应用程序提供可靠的端到端的数据流传输服务。

    数据结构

    struct tcphdr {
    	u_short	th_sport;		//源端口
    	u_short	th_dport;		//目的端口
    	tcp_seq	th_seq;			//数据序列号
    	tcp_seq	th_ack;			//确认序列号
    
    	u_char	th_off:4,		//TCP头部长度,以4字节计算
    		th_x2:4;		/* (unused) */
    	u_char	th_flags;    //标志
    #define	TH_FIN	0x01        //发送方字节流结束标志
    #define	TH_SYN	0x02        //建立连接的同步标志
    #define	TH_RST	0x04        //连接复位
    #define	TH_PUSH	0x08        //接收方应该立刻将数据交给用户
    #define	TH_ACK	0x10        //确认序号
    #define	TH_URG	0x20        //紧急数据
    	u_short	th_win;			//窗口大小
    	u_short	th_sum;			//校验和=TCP header + data
    	u_short	th_urp;			//紧急数据的偏移量
    };
    
    struct tcpiphdr {
    	struct 	ipovly ti_i;		/* overlaid ip structure */
    	struct	tcphdr ti_t;		/* tcp header */
    };
    
    struct ipovly {
    	caddr_t	ih_next, ih_prev;	/* for protocol sequence q's */
    	u_char	ih_x1;			/* (unused) */
    	u_char	ih_pr;			        //协议域
    	short	ih_len;			        //这个相当于IP头部,len = data Len + udp HeaderLen + ip header
    	struct	in_addr ih_src;		//源地址
    	struct	in_addr ih_dst;		//目标地址
    };
    

    TCP专用控制块:

    struct tcpcb {
    	struct	tcpiphdr *seg_next;	        //对乱序到达数据报进行排队
    	struct	tcpiphdr *seg_prev;
    	short	t_state;		//TCP的状态
    	short	t_timer[TCPT_NTIMERS];	//TCP用到的计数器,作为定时器使用,默认为4
    	short	t_rxtshift;		//当前的指数,用于指数退避,最大为12
    	short	t_rxtcur;		//当前的重传值
    	short	t_dupacks;		//重复的ACK计数
    	u_short	t_maxseg;		//MSS
    	char	t_force;		/* 1 if forcing out a byte */
    	u_short	t_flags;
    #define	TF_ACKNOW	0x0001		//立即发送ACK
    #define	TF_DELACK	0x0002		//延迟发送ACK
    #define	TF_NODELAY	0x0004		//立即发送用户数据,不等待形成最大报文段(禁止Nagle算法)
    #define	TF_NOOPT	0x0008		//不使用TCP选项
    #define	TF_SENTFIN	0x0010		//FIN已发送
    #define	TF_REQ_SCALE	0x0020		/* have/will request window scaling */
    #define	TF_RCVD_SCALE	0x0040		/* other side has requested scaling */
    #define	TF_REQ_TSTMP	0x0080		/* have/will request timestamps */
    #define	TF_RCVD_TSTMP	0x0100		/* a timestamp was received in SYN */
    #define	TF_SACK_PERMIT	0x0200		/* other side said I could SACK */
    
    	struct	tcpiphdr *t_template;	//保存一个TCP/IP首部模板
    	struct	inpcb *t_inpcb;		//指向Internet PCB
    /*
     * The following fields are used as in the protocol specification.
     * See RFC783, Dec. 1981, page 21.
     */
    /* send sequence variables */
    	tcp_seq	snd_una;		//最早的未确认过的seq
    	tcp_seq	snd_nxt;		//下一个将要发送的seq
    	tcp_seq	snd_up;		//发送紧急数据的指针
    	tcp_seq	snd_wl1;		//用于记录最后接收的报文段的序号,用于更新发送窗口
    	tcp_seq	snd_wl2;		//用于记录最后接收的报文段的确认序号,用于更新发送窗口
    	tcp_seq	iss;			//初始化发送ISS number
    	u_long	snd_wnd;		//发送窗口大小
    /* receive sequence variables */
    	u_long	rcv_wnd;		//接收窗口大小
    	tcp_seq	rcv_nxt;		//预计下一个接收的seq
    	tcp_seq	rcv_up;		//接收紧急数据的指针
    	tcp_seq	irs;			//初始化接收ISS number
    /*
     * Additional variables for this implementation.
     */
    /* receive variables */
    	tcp_seq	rcv_adv;		//通告窗口最大值+1
    /* retransmit variables */
    	tcp_seq	snd_max;		//最大发送序号
    /* congestion control (for slow start, source quench, retransmit after loss) */
    	u_long	snd_cwnd;		//拥塞窗口
    	u_long	snd_ssthresh;		//慢启动门限
    /*
     * transmit timing stuff.  See below for scale of srtt and rttvar.
     * "Variance" is actually smoothed difference.
     */
    	short	t_idle;			//空闲时间
    	short	t_rtt;			//RTT
    	tcp_seq	t_rtseq;		//被定时计算RTT的序列号
    	short	t_srtt;			//平滑往返时间
    	short	t_rttvar;		//RTT方差
    	u_short	t_rttmin;		//允许的最小 的RTT
    	u_long	max_sndwnd;		//对端提供的最大的窗口
    
    /* out-of-band data */
    	char	t_oobflags;		/* have some */
    	char	t_iobc;			/* input character */
    #define	TCPOOB_HAVEDATA	0x01
    #define	TCPOOB_HADDATA	0x02
    	short	t_softerror;		/* possible error not yet reported */
    
    /* RFC 1323 variables */
    	u_char	snd_scale;		/* window scaling for send window */
    	u_char	rcv_scale;		/* window scaling for recv window */
    	u_char	request_r_scale;	/* pending window scaling */
    	u_char	requested_s_scale;
    	u_long	ts_recent;		/* timestamp echo data */
    	u_long	ts_recent_age;		/* when last updated */
    	tcp_seq	last_ack_sent;
    
    /* TUBA stuff */
    	caddr_t	t_tuba_pcb;		/* next level down pcb for TCP over z */
    };
    
    

    TCP数据报图示:

    TCP状态变迁图:


    TCP的定时器

    TCP为了每条连接维护了七个定时器,从三个角度描述:连接建立,数据传输以及连接终止

    • 连接建立:
      • 连接建立定时器:定时器在发送SYN报文段的时候启动,如果没有在75S内收到响应,连接建立终止。
    • 数据传输:
      • 重传定时器:定时器在数据发送时建立。如果定时器已经超时但依旧没有收到对端对数据的确定,TCP将重传数据。重传定时器的取值依赖于RTO(动态计算)。
      • 延迟ACK定时器:定时器在TCP收到必须被确认的数据时,延迟ACK的发送,期待在等待期间有数据传输,即数据可以携带ACK一起发送。
      • 持续定时器:定时器在对端窗口通知为0,阻止TCP发送数据时启动。在超时后向对端发送1字节的数据,判断对端的接收窗口是否打开。与重传定时器的值类似,超时的值依赖于动态计算的RTO。
      • 保活定时器:定时器在应用进程设置了Keep-Alive时生效,如果连续的空闲时间超过了2小时,保活定时器向对端发送连续探测报文,强迫对端响应。
    • 连接终止:
      • FIN_WAIT_2定时器:主动关闭的一端需要这个定时器,防止对端一直不发送FIN,状态一直处于FIN_WAIT_2
      • TIME_WAIT定时器:主动关闭的一端需要这个定时器,防止收到错误的报文段,影响新的连接。

    因为其中有一些定时器是互斥的,不可能同时出现,比如说:连接建立定时器与保活定时器,FIN_WAIT_2定时器与TIME_WAIT定时器。因此,TCP使用4个500ms精度的计数器描述除了延迟ACK定时器除外的6个定时器。
    而且,重传定时器和持续定时器都有最大值与最小值的限制,因为他们的取值都是给予测量的动态计算得到的,其他定时器都是常值。

    定时器的取值范围:

    重传定时器的计算

    重传定时器与持续定时器依赖于连接上测算的RTT。TCP计算重传时限不仅需要测量RTT,还需要计算已平滑的RTT估计器(SRTT)以及已平滑的RTT平均方差估计器(RTTVAR)。

    DELTA = RTT - SRTT        //计算新测量的往返时间(RTT)与已经平滑的SRTT之间的差值
    SRTT = SRTT + g × DELTA        //g = 1/8,更新RTT
    RTTVAL = RTTVAL + h × (|DELTA| - RTTVAR)        //h=1/4,更新RTTVAL
    RTO = SRTT + 4 × RTTVAR        //更新计算RTO
    

    tcp_canneltimers

    • 功能A:进入TIME_WAIT状态时,tcp_input在设定2MSL定时器之前会将所有的定时器清零。
    void
    tcp_canceltimers(tp)
    	struct tcpcb *tp;
    {
    	register int i;
    
    	for (i = 0; i < TCPT_NTIMERS; i++)
    		tp->t_timer[i] = 0;
    }
    

    tcp_fasttimo

    • 功能A:每200ms调用一次,用于操作ACK延迟定时器操作
    void
    tcp_fasttimo()
    {
    	register struct inpcb *inp;
    	register struct tcpcb *tp;
    	int s = splnet();
    
    	inp = tcb.inp_next;    //获取TCP的Internet PCB
    	if (inp)
    	for (; inp != &tcb; inp = inp->inp_next)
    		if ((tp = (struct tcpcb *)inp->inp_ppcb) &&
    		    (tp->t_flags & TF_DELACK)) {        //如果TCP PCB存在,且DELAY已经置位
    			tp->t_flags &= ~TF_DELACK;    //清楚DELAY标志
    			tp->t_flags |= TF_ACKNOW;    //置位ACK标志
    			tcpstat.tcps_delack++;    //记录全局变量
    			(void) tcp_output(tp);    //立刻发送ACK以及数据
    		}
    	splx(s);
    }
    

    tcp_slowtimo

    • 功能A:每500ms调用一次,用于操作其他六个定时器
    void
    tcp_slowtimo()
    {
    	register struct inpcb *ip, *ipnxt;
    	register struct tcpcb *tp;
    	int s = splnet();
    	register int i;
    
    	tcp_maxidle = TCPTV_KEEPCNT * tcp_keepintvl;    //初始化为10min,这是TCP向对端发送报文后,用于等待的最长时间。FIN_WAIT_2定时器也使用了这个变量
    	/*
    	 * Search through tcb's and update active timers.
    	 */
    	ip = tcb.inp_next;    //如果TCP协议层没有合适的PCB存在,直接返回
    	if (ip == 0) {
    		splx(s);
    		return;
    	}
    	for (; ip != &tcb; ip = ipnxt) {    //遍历协议层所有的PCB
    		ipnxt = ip->inp_next;
    		tp = intotcpcb(ip);    //从Internet PCB获取TCP PCB
    		if (tp == 0)    //如果TCP PCB为空,直接返回
    			continue;
    		for (i = 0; i < TCPT_NTIMERS; i++) {    //遍历TIMERS,总共遍历四次
    			if (tp->t_timer[i] && --tp->t_timer[i] == 0) {    //如果在某一次的遍历过程中发现有超时存在,调用相应的函数进行处理,注意倒数第二个参数,使用i标记是那个定时器超时了
    				(void) tcp_usrreq(tp->t_inpcb->inp_socket,
    				    PRU_SLOWTIMO, (struct mbuf *)0,
    				    (struct mbuf *)i, (struct mbuf *)0);
    				if (ipnxt->inp_prev != ip)    //在返回之前检查这个Internet PCB是否存在(可能在某一次的操作之后,TCP放弃了连接,比如说2MSL超时等)
    					goto tpgone;
    			}
    		}
    		tp->t_idle++;    //记录空闲时间,如果有数据报文到达,就会清理这个计数器。
    		if (tp->t_rtt)    //增加RTT计数器
    			tp->t_rtt++;
    tpgone:
    		;
    	}
    	tcp_iss += TCP_ISSINCR/PR_SLOWHZ;		//递增ISS
    	tcp_now++;					//递增时间
    	splx(s);
    }
    

    tcp_timers

    • 功能A:处理超时请求
    struct tcpcb *
    tcp_timers(tp, timer)
    	register struct tcpcb *tp;
    	int timer;
    {
    	register int rexmt;
    
    	switch (timer) {
    	case TCPT_2MSL:    //使用这个变量实现了两个定时器:FIN_WAIT_2和2MSL
    		if (tp->t_state != TCPS_TIME_WAIT &&
    		    tp->t_idle <= tcp_maxidle)    //如果当前的状态处于FIN_WAIT_2期间,并且空闲时间小于10min,那么会再次将定时器设置为75s;
    			tp->t_timer[TCPT_2MSL] = tcp_keepintvl;
    		else    //如果此时处于TIME_WAIT状态,定时器超时意味着TIME_WAIT定时器超期,直接关闭TCP PCB。或者空闲的时间>10min(处于FIN_WAIT_2),也会引发关闭
    			tp = tcp_close(tp);
    		break;
    
    	case TCPT_REXMT:    //重传定时器超时
    		if (++tp->t_rxtshift > TCP_MAXRXTSHIFT) {    //如果重传的次数超过了12次,TCP将丢弃连接,直接退出
    			tp->t_rxtshift = TCP_MAXRXTSHIFT;
    			tcpstat.tcps_timeoutdrop++;
    			tp = tcp_drop(tp, tp->t_softerror ?
    			    tp->t_softerror : ETIMEDOUT);
    			break;
    		}
    		tcpstat.tcps_rexmttimeo++;    //修改全局变量
    		rexmt = TCP_REXMTVAL(tp) * tcp_backoff[tp->t_rxtshift];    //通过指数退避,计算新的RTO值
    		TCPT_RANGESET(tp->t_rxtcur, rexmt,
    		    tp->t_rttmin, TCPTV_REXMTMAX);
    		tp->t_timer[TCPT_REXMT] = tp->t_rxtcur;    //填充新的超时值
    
    		if (tp->t_rxtshift > TCP_MAXRXTSHIFT / 4) {    //如果报文段已经重传4次以上
    			in_losing(tp->t_inpcb);    //释放缓存中的路由
    			tp->t_rttvar += (tp->t_srtt >> TCP_RTT_SHIFT);    //计算RTT方差
    			tp->t_srtt = 0;    //并清除RTT估计器
    		}
    		tp->snd_nxt = tp->snd_una;    //将即将发送的报文段位置调整为未被确认的位置
    		tp->t_rtt = 0;    //将RTT置为0
    
    		{      //超时重传引发的拥塞避免。如果最后收到了对方发送的ACK,就进行慢启动
    		u_int win = min(tp->snd_wnd, tp->snd_cwnd) / 2 / tp->t_maxseg;      //先将窗口设置为(接收方的通告窗口和发送方的拥塞窗口中较小值的一半)
    		if (win < 2)    //如果窗口小于2
    			win = 2;    //将窗口置为2
    		tp->snd_cwnd = tp->t_maxseg;    //将拥塞窗口设置为一个报文段
    		tp->snd_ssthresh = win * tp->t_maxseg;    //将慢启动门限设置为WIN个报文段,最小为2
    		tp->t_dupacks = 0;    //将重复的ACK计数置为0
    		}
    		(void) tcp_output(tp);    //发送最早的未经过确认的报文段
    		break;
    
    	case TCPT_PERSIST:    //如果持续定时器超时
    		tcpstat.tcps_persisttimeo++;    //修改全局变量
    		tcp_setpersist(tp);    //计算定时器的下一个超时值
    		tp->t_force = 1;    //并强制定时器发送1字节的数据
    		(void) tcp_output(tp);
    		tp->t_force = 0;
    		break;
    
    	case TCPT_KEEP:    //描述了两种定时器,keep-alive和SYN_SEND状态    
    		tcpstat.tcps_keeptimeo++;    //记录全局变量
    		if (tp->t_state < TCPS_ESTABLISHED)    //如果在75s内没有成功的建立连接。连接建立定时器和重传定时器会保证对端在没有相应的时候重传SYN
    			goto dropit;
    		if (tp->t_inpcb->inp_socket->so_options & SO_KEEPALIVE &&
    		    tp->t_state <= TCPS_CLOSE_WAIT) {    //如果设置了Keep-Alive的选项并且状态<CLOSE_WAIT
    		    	if (tp->t_idle >= tcp_keepidle + tcp_maxidle)    //如果空闲的时间超过了限制,就丢弃连接
    				goto dropit;
    			tcpstat.tcps_keepprobe++;
    
    			tcp_respond(tp, tp->t_template, (struct mbuf *)NULL,
    			    tp->rcv_nxt, tp->snd_una - 1, 0);    //否则话发送一个探测报文并重置计数器
    			tp->t_timer[TCPT_KEEP] = tcp_keepintvl;
    		} else
    			tp->t_timer[TCPT_KEEP] = tcp_keepidle;
    		break;
    	dropit:
    		tcpstat.tcps_keepdrops++;    //从TCP PCBs的链表中移除这个TCP PCB,并返回连接超时的错误
    		tp = tcp_drop(tp, ETIMEDOUT);
    		break;
    	}
    	return (tp);
    }
    

    tcp_newtcpcb

    • 功能A:新分配一个TCP PCB并完成初始化
    struct tcpcb *
    tcp_newtcpcb(inp)
    	struct inpcb *inp;
    {
    	register struct tcpcb *tp;
    
    	tp = malloc(sizeof(*tp), M_PCB, M_NOWAIT);    //分配一个TCP PCB
    	if (tp == NULL)
    		return ((struct tcpcb *)0);
    	bzero((char *) tp, sizeof(struct tcpcb));
    	tp->seg_next = tp->seg_prev = (struct tcpiphdr *)tp;        //将两个指针都指向自身(转换为TCP/IP Header)
    	tp->t_maxseg = tcp_mssdflt;    //设置MSS
    
    	tp->t_flags = tcp_do_rfc1323 ? (TF_REQ_SCALE|TF_REQ_TSTMP) : 0;    
    	tp->t_inpcb = inp;        //指向Internet PCB
    	
    	tp->t_srtt = TCPTV_SRTTBASE;        //将SRTT初始化为0
    	tp->t_rttvar = tcp_rttdflt * PR_SLOWHZ << 2;        //将RTTVAR初始化为3s
    	tp->t_rttmin = TCPTV_MIN;    //将RTT_MIN初始化为500ms
    	TCPT_RANGESET(tp->t_rxtcur, 
    	    ((TCPTV_SRTTBASE >> 2) + (TCPTV_SRTTDFLT << 2)) >> 1,
    	    TCPTV_MIN, TCPTV_REXMTMAX);        //将RTO初始化为6s
    	tp->snd_cwnd = TCP_MAXWIN << TCP_MAX_WINSHIFT;        //将cwnd和ssthresh初始化为1G
    	tp->snd_ssthresh = TCP_MAXWIN << TCP_MAX_WINSHIFT;
    	inp->inp_ip.ip_ttl = ip_defttl;    //初始化Internet PCB中的TLL
    	inp->inp_ppcb = (caddr_t)tp;    //确定Internet PCB中的TCP PCB的指向
    	return (tp);
    }
    

    tcp_setpersist

    • 功能A:持续定时器到期后调用这个函数,根据RTO的值计算下一次的超时时间
    void
    tcp_setpersist(tp)
    	register struct tcpcb *tp;
    {
    	register t = ((tp->t_srtt >> 2) + tp->t_rttvar) >> 1;    //计算RTO
    
    	if (tp->t_timer[TCPT_REXMT])    //判断这个定时器是否超时
    		panic("tcp_output REXMT");    
    	/*
    	 * Start/restart persistance timer.
    	 */
    	TCPT_RANGESET(tp->t_timer[TCPT_PERSIST],
    	    t * tcp_backoff[tp->t_rxtshift],
    	    TCPTV_PERSMIN, TCPTV_PERSMAX);        //根据指数退避设置下一次的超时时间
    	if (tp->t_rxtshift < TCP_MAXRXTSHIFT)    //递增当前的指数
    		tp->t_rxtshift++;
    }
    
  • 相关阅读:
    Java中只有按值传递,没有按引用传递!(两种参数情况下都是值传递)
    最简单的struts实例介绍
    Spring中bean的五个作用域简介(转载)
    Spring配置文件
    轻松搞定面试中的二叉树题目 (转)
    二叉树
    稳定排序与非稳定排序判别方法
    Yii的缓存机制之动态缓存
    Yii的缓存机制之数据缓存
    Yii的缓存机制之页面缓存
  • 原文地址:https://www.cnblogs.com/ukernel/p/9190965.html
Copyright © 2020-2023  润新知