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协议的主要内容,如果未能解决你的问题,请参考以下文章

网络编程之TCP/IP各层详解

TCP协议详解

TCP/IP详解卷一: 协议之第4~6章

即时通讯开发之TCP/IP中的TCP 协议概述

tcp/ip协议详解!!

计算机网络之TCP/UDP协议详解