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