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++;
}