TCP/IP详解V2之TCP协议
Posted ukernel
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了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++; }
以上是关于TCP/IP详解V2之TCP协议的主要内容,如果未能解决你的问题,请参考以下文章