TCP滑动窗口协议

TCP滑动窗口 一、 数据结构 1、 发送窗口

struct tcpcb {

......

/* send sequence variables */

/*最早的未确认过的序号*/

tcp_seq snd_una; /* send unacknowledged */

/*下一个发送序号*/

tcp_seq snd_nxt; /* send next */

tcp_seq snd_up; /* send urgent pointer */

/*记录最后接收报文段的序号,用于更新发送窗口*/

tcp_seq snd_wl1; /* window update seg seq number */

/*记录最后接收报文段的确认序号,用于更新发送窗口*/

tcp_seq snd_wl2; /* window update seg ack number */

/*发送方初始化序号*/

tcp_seq iss; /* initial send sequence number */

/*接收方提供的窗口*/

u_long snd_wnd; /* send window */

/*最大发送序号*/

tcp_seq snd_max; /* highest sequence number sent;

 

2、 接收窗口

/* receive sequence variables */

/*接收窗口,向发送方通告*/

u_long rcv_wnd; /* receive window */

/*下一个接收序号*/

tcp_seq rcv_nxt; /* receive next */

tcp_seq rcv_up; /* receive urgent pointer */

/*接收方初始化序号*/

tcp_seq irs; /* initial receive sequence number */

/*通告序号最大值加1*/

tcp_seq rcv_adv; /* advertised window */

......

}

二、窗口变量的初始化 1、 主动连接窗口变量的初始化

int

tcp_usrreq(so, req, m, nam, control)

struct socket *so;

int req;

struct mbuf *m, *nam, *control;

{

switch (req) {

......

case PRU_CONNECT:

if (inp->inp_lport == 0) {

error = in_pcbbind(inp, (struct mbuf *)0);

if (error)

break;

}

error = in_pcbconnect(inp, nam);

if (error)

break;

tp->t_template = tcp_template(tp);

if (tp->t_template == 0) {

in_pcbdisconnect(inp);

error = ENOBUFS;

break;

}

/* Compute window scaling to request.  */

while (tp->request_r_scale < TCP_MAX_WINSHIFT &&

    (TCP_MAXWIN << tp->request_r_scale) < so->so_rcv.sb_hiwat)

tp->request_r_scale++;

soisconnecting(so);

tcpstat.tcps_connattempt++;

tp->t_state = TCPS_SYN_SENT;

tp->t_timer[TCPT_KEEP] = TCPTV_KEEP_INIT;

发送窗口变量的初始化

/*在用户的connect请求中,初始化tp->iss(发送序号的初始值)*/

tp->iss = tcp_iss; 

tcp_iss += TCP_ISSINCR/2;

tcp_sendseqinit(tp);

#define tcp_sendseqinit(tp) \

(tp)->snd_una = (tp)->snd_nxt = (tp)->snd_max = (tp)->snd_up = \

(tp)->iss

初始化发送窗口的变量snd_unasnd_nxtsnd_maxsnd_up

error = tcp_output(tp);

break;

......

}

接收窗口变量的初始化(TCP_INPUT)

case TCPS_SYN_SENT:

if ((tiflags & TH_ACK) &&

    (SEQ_LEQ(ti->ti_ack, tp->iss) ||

     SEQ_GT(ti->ti_ack, tp->snd_max)))

goto dropwithreset;

if (tiflags & TH_RST) {

if (tiflags & TH_ACK)

tp = tcp_drop(tp, ECONNREFUSED);

goto drop;

}

if ((tiflags & TH_SYN) == 0)

goto drop;

if (tiflags & TH_ACK) {

tp->snd_una = ti->ti_ack;

if (SEQ_LT(tp->snd_nxt, tp->snd_una))

tp->snd_nxt = tp->snd_una;

}

tp->t_timer[TCPT_REXMT] = 0;

tp->irs = ti->ti_seq;

初始接收序号i r s复制自S Y N报文段中的序号字段

tcp_rcvseqinit(tp);

#define tcp_rcvseqinit(tp) \

(tp)->rcv_adv = (tp)->rcv_nxt = (tp)->irs + 1

初始化接受窗口的变量:rcv_advrcv_nxt

因为S Y N占据一个序号,所以第宏表达式需加1

tp->t_flags |= TF_ACKNOW;

......

......

ti->ti_seq++;

/*报文段序号加1,以计入S Y N*/

if (ti->ti_len > tp->rcv_wnd) {

todrop = ti->ti_len - tp->rcv_wnd;

m_adj(m, -todrop);

ti->ti_len = tp->rcv_wnd;

tiflags &= ~TH_FIN;

tcpstat.tcps_rcvpackafterwin++;

tcpstat.tcps_rcvbyteafterwin += todrop;

}

/*snd_wl1等于接收序号减1,这将强制更新3个窗口变量*/

tp->snd_wl1 = ti->ti_seq - 1;

/*接收紧急指针(r c v u p)等于接收序号*/

tp->rcv_up = ti->ti_seq;

2、 被动连接窗口变量的初始化

case TCPS_LISTEN: {

struct mbuf *am;

register struct sockaddr_in *sin;

if (tiflags & TH_RST)

goto drop;

if (tiflags & TH_ACK)

goto dropwithreset;

if ((tiflags & TH_SYN) == 0)

goto drop;

......

if (iss)

tp->iss = iss;

else

tp->iss = tcp_iss;

tcp_iss += TCP_ISSINCR/2;

/*被动连接过程中初始化tcp_iss*/

tp->irs = ti->ti_seq;

初始接收序号i r s复制自S Y N报文段中的序号字段

tcp_sendseqinit(tp);

#define tcp_sendseqinit(tp) \

(tp)->snd_una = (tp)->snd_nxt = (tp)->snd_max = (tp)->snd_up = \

(tp)->iss

初始化发送窗口的变量snd_unasnd_nxtsnd_maxsnd_up

tcp_rcvseqinit(tp);

#define tcp_rcvseqinit(tp) \

(tp)->rcv_adv = (tp)->rcv_nxt = (tp)->irs + 1

初始化接收窗口的变量rcv_advrcv_nxt

tp->t_flags |= TF_ACKNOW;

三、 接收窗口变量更新 1TCP向对端通告的窗口大小的计算(TCP_OUTPUT)

1) 计算本地缓存的可用空间

win = sbspace(&so->so_rcv);

win设定为本地接收缓存中可用空间的大小,即TCP向对端通告的接收窗口可能大小

2) 确定是否需要窗口更新

if (win > 0) {

/* 

 * "adv" is the amount we can increase the window,

 * taking into account that we are limited by

 * TCP_MAXWIN << tp->rcv_scale.

 */

long adv = min(win, (long)TCP_MAXWIN << tp->rcv_scale) -

(tp->rcv_adv - tp->rcv_nxt);

min(win, (long)TCP_MAXWIN << tp->rcv_scale)等于插口接收缓存可用空间大小(w i n)连接上所允许的最大窗口大小之间最小值,即T C P当前能够向对端发送的接收窗口的最大值(tp->rcv_adv - tp->rcv_nxt)等于T C P最后一次通告的接收窗口中剩余空间的大小以字节为单位两者相减得到adv,窗口已打开的字节数。tcpinput顺序接收数据时,递增rcv_nxt。tcp_output在通告窗口边界向右移动时,递增rcv_adv

if (adv >= (long) (2 * tp->t_maxseg))

goto send;

如果剩余的接收空间能够容纳两个或两个以上的报文段,则发送窗口更新报文

if (2 * adv >= (long) so->so_rcv.sb_hiwat)

如果可用空间大于插口接收缓存的一半,则发送窗口更新报文goto send;

}

3)计算通告窗口

if (win < (long)(so->so_rcv.sb_hiwat / 4) && win < (long)tp->t_maxseg)

win = 0;

计算向对端通告的窗口大小(t i w i n)时,应考虑如何避免糊涂窗口综合症。 w i n 等于插口的接收缓存大小。如果w i n 小于接收缓存大小的1 / 4 (s o r c v . s b h i w a t),并且小于一个最大报文段长度,则通告的窗口大小设为0,从而在后续测试中防止窗口缩小。也就是说,如果可用空间已达到接收缓存大小的1 / 4,或者等于最大报文段长度,将向对端发送窗口更新通告。

if (win > (long)TCP_MAXWIN << tp->rcv_scale)

win = (long)TCP_MAXWIN << tp->rcv_scale;

如果win大于连接规定的最大值,应将其减少为最大值

if (win < (long)(tp->rcv_adv - tp->rcv_nxt))

win = (long)(tp->rcv_adv - tp->rcv_nxt);

不要缩小窗口,r c v a d v减去r c v n x t等于最近一次向发送方通告的窗口大小中的剩余空间。如果w i n小于它,应将其设定为该值,因为不允许缩小窗口。有时尽管剩余的可用空间小于最大报文段长度(因此,w i n在代码起始处被置为0 ),但还可以容纳一些数据,就会出现这种情况

ti->ti_win = htons((u_short) (win>>tp->rcv_scale));

4)更新rcv_adv

if (win > 0 && SEQ_GT(tp->rcv_nxt+win, tp->rcv_adv))

tp->rcv_adv = tp->rcv_nxt + win;

如果报文段中通告的最高序号(r c v n x t加上w i n)大于r c v a d v,则保存新的值。

2、 rcv_wndrcv_nxt更新(TCP_INPUT)

1)计算rcv_wnd

{

 int win;

win = sbspace(&so->so_rcv);

if (win < 0)

win = 0;

tp->rcv_wnd = max(win, (int)(tp->rcv_adv - tp->rcv_nxt));

}

w i n等于插口接收缓存中可用的字节数, r c v a d v减去r c v n x t等于当前通告的窗口大小,接收窗口等于上述两个值中较大的一个,这是为了保证接收窗口不小于当前通告窗口的大小。此外,如果最后一次窗口更新后,应用进程从插口接收缓存中取走了数据,w i n可能大于通告窗口,因此, T C P最多能够接收w i n字节的数据(即使对端不会发送超过通告窗口大小的数据)

因为函数后面的代码必须确定通告窗口中能放入多少数据(如果有),所以现在必须计算通告窗口的大小。落在通告窗口之外的接收数据被丢弃:落在窗口左侧的数据是已接收并确认过的数据,落在窗口右侧的数据是暂不允许对端发送的数据。

2)更新rcv_nxt

#define TCP_REASS(tp, ti, m, so, flags) { \

if ((ti)->ti_seq == (tp)->rcv_nxt && \

    (tp)->seg_next == (struct tcpiphdr *)(tp) && \

    (tp)->t_state == TCPS_ESTABLISHED) { \

tp->t_flags |= TF_DELACK; \

(tp)->rcv_nxt += (ti)->ti_len; \

tcp_input在接收数据时会调用此函数,rcv_nxt在接收数据后更新

flags = (ti)->ti_flags & TH_FIN; \

tcpstat.tcps_rcvpack++;\

tcpstat.tcps_rcvbyte += (ti)->ti_len;\

sbappend(&(so)->so_rcv, (m)); \

sorwakeup(so); \

} else { \

(flags) = tcp_reass((tp), (ti), (m)); \

tp->t_flags |= TF_ACKNOW; \

} \

}

#ifndef TUBA_INCLUDE

if (tiflags & TH_FIN) {

if (TCPS_HAVERCVDFIN(tp->t_state) == 0) {

socantrcvmore(so);

tp->t_flags |= TF_ACKNOW;

tp->rcv_nxt++;

如果接收报文段F I N置位,并且是连接上收到的第一个F I N,则调用s o c a n t r c v m o r e,把插口设为只读,置位T F _ A C K N O W,从而立即发送A C K (无延迟)r c v n x t1,越过F I N占用的序号。

}

...

}

3、 irs(初始接收序号)的设定(TCP_INPUT)

1)主动连接中的更新

case TCPS_SYN_SENT:

if ((tiflags & TH_ACK) &&

    (SEQ_LEQ(ti->ti_ack, tp->iss) ||

     SEQ_GT(ti->ti_ack, tp->snd_max)))

goto dropwithreset;

if (tiflags & TH_RST) {

if (tiflags & TH_ACK)

tp = tcp_drop(tp, ECONNREFUSED);

goto drop;

}

if ((tiflags & TH_SYN) == 0)

goto drop;

if (tiflags & TH_ACK) {

tp->snd_una = ti->ti_ack;

if (SEQ_LT(tp->snd_nxt, tp->snd_una))

tp->snd_nxt = tp->snd_una;

}

tp->t_timer[TCPT_REXMT] = 0;

tp->irs = ti->ti_seq;

初始接收序号i r s复制自S Y N报文段中的序号字段

tcp_rcvseqinit(tp);

#define tcp_rcvseqinit(tp) \

(tp)->rcv_adv = (tp)->rcv_nxt = (tp)->irs + 1

因为S Y N占据一个序号,所以第宏表达式需加1

tp->t_flags |= TF_ACKNOW;

......

......

ti->ti_seq++;

/*报文段序号加1,以计入S Y N*/

if (ti->ti_len > tp->rcv_wnd) {

todrop = ti->ti_len - tp->rcv_wnd;

m_adj(m, -todrop);

ti->ti_len = tp->rcv_wnd;

tiflags &= ~TH_FIN;

tcpstat.tcps_rcvpackafterwin++;

tcpstat.tcps_rcvbyteafterwin += todrop;

}

/*snd_wl1等于接收序号减1,这将强制更新3个窗口变量*/

tp->snd_wl1 = ti->ti_seq - 1;

/*接收紧急指针(r c v u p)等于接收序号*/

tp->rcv_up = ti->ti_seq;

2)被动连接中的更新

case TCPS_LISTEN: {

struct mbuf *am;

register struct sockaddr_in *sin;

if (tiflags & TH_RST)

goto drop;

if (tiflags & TH_ACK)

goto dropwithreset;

if ((tiflags & TH_SYN) == 0)

goto drop;

......

if (iss)

tp->iss = iss;

else

tp->iss = tcp_iss;

tcp_iss += TCP_ISSINCR/2;

/*被动连接过程中初始化tcp_iss*/

tp->irs = ti->ti_seq;

初始接收序号i r s复制自S Y N报文段中的序号字段

tcp_sendseqinit(tp);

#define tcp_sendseqinit(tp) \

(tp)->snd_una = (tp)->snd_nxt = (tp)->snd_max = (tp)->snd_up = \

(tp)->iss

初始化发送窗口的变量snd_unasnd_nxtsnd_maxsnd_up

tcp_rcvseqinit(tp);

#define tcp_rcvseqinit(tp) \

(tp)->rcv_adv = (tp)->rcv_nxt = (tp)->irs + 1

初始化接收窗口的变量rcv_advrcv_nxt

tp->t_flags |= TF_ACKNOW;

4、 rcv_up的处理

/*

 * Process segments with URG.

 */

/*只有满足下列条件的报文段才会被处理: U R G标志置位,紧急数据偏移量

( t i _ u r p ) 非零,连接还未收到F I N 。只有当连接的状态等于T I M E _ WA I T 时,宏T C P S H A V E R C V D F I N才会为真,因此,连接处于任何其他状态时, U R G都会被处理。在后面的注释中提到,连接处于C L O S E WA I TC L O S I N GL A S T A C KT I M E WA I T等几个状态时,U R G标志会被忽略,这种说法是错误的*/

if ((tiflags & TH_URG) && ti->ti_urp &&

    TCPS_HAVERCVDFIN(tp->t_state) == 0) {

/*

 * This is a kludge, but if we receive and accept

 * random urgent pointers, we'll crash in

 * soreceive.  It's hard to imagine someone

 * actually wanting to send this much urgent data.

 */

if (ti->ti_urp + so->so_rcv.sb_cc > sb_max) {

ti->ti_urp = 0; /* XXX */

tiflags &= ~TH_URG; /* XXX */

goto dodata; /* XXX */

}

/*如果紧急数据偏移量加上接收缓存中已有的数据超过了插口缓存可容纳的数据量,则忽略紧急标志。紧急数据偏移量被清零, U R G标志被清除,剩余的紧急方式处理逻辑被忽略。*/

if (SEQ_GT(ti->ti_seq+ti->ti_urp, tp->rcv_up)) {

tp->rcv_up = ti->ti_seq + ti->ti_urp;

so->so_oobmark = so->so_rcv.sb_cc +

    (tp->rcv_up - tp->rcv_nxt) - 1;

if (so->so_oobmark == 0)

so->so_state |= SS_RCVATMARK;

sohasoutofband(so);

调用s o h a s o u t o f b a n d告知应用进程有带外数据到达了插口,清除两个标志接收报文段紧急数据偏移量29-18 2 6 - 3 0中发送的报文段到达接收方T C P O O B H A V E D A T AT C P O O B H A D D A T A

tp->t_oobflags &= ~(TCPOOB_HAVEDATA | TCPOOB_HADDATA);

/*计算插口接收缓存中带外数据的分界点,应计入接收缓存中已有的数据

(s o r c v . s b c c)。在上面的例子中,假定接收缓存为空,s o o o b m a r k等于2:序号为6的字节被认为是带外数据。如果这个带外数据标记等于0,说明插口正处在带外数据分界点上。如果发送带外数据的s e n d系统调用给定长度为1,并且这个报文段到达对端时接收缓存为空,就会发生这一现象,同时也再次重申了B e r k e l e y系统认为紧急指针应指向带外数据后的第一字节。*/

}

/*如果接收报文段的起始序号加上紧急数据偏移量超过了当前接收紧急指针,说

明已收到了一个新的紧急指针*/

/*

 * Remove out of band data so doesn't get presented to user.

 * This can happen independent of advancing the URG pointer,

 * but if two URG's are pending at once, some out-of-band

 * data may creep in... ick.

 */

if (ti->ti_urp <= ti->ti_len

#ifdef SO_OOBINLINE

     && (so->so_options & SO_OOBINLINE) == 0

#endif

     )

tcp_pulloutofband(so, ti, m);

从正常的数据流中提取带外数据

如果紧急数据偏移量小于等于接收报文段中的字节数,说明带外数据包含在报文段中。T C P 的紧急方式允许紧急数据偏移量指向尚未收到的数据。如果定义了S O O O B I N L I N E常量(正常情况下, N e t / 3定义了此常量),而且未选用对应的插口选项,则接收进程将从正常的数据流中提取带外数据,并保存在t i o b c变量中。完成这一功能的函数,是我们将在下一节介绍的t c p p u l l o u t o f b a n d。注意,无论紧急指针指向的字节是否可读, T C P都将通知接收进程发送方已进入紧急方式。这是T C P紧急方式的一个特性。

} else

/*

 * If no out of band data is expected,

 * pull receive urgent pointer along

 * with the receive window.

 */

if (SEQ_GT(tp->rcv_nxt, tp->rcv_up))

tp->rcv_up = tp->rcv_nxt;

如果不处于紧急方式,调整接收紧急指针

在接收方未处理紧急指针时,如果rcv_nxt大于接收紧急指针,则rcv_up向右移动,并等于rcv_nxt。这使接收紧急指针一直指向接收窗口的左侧,确保在收到U R G标志时,宏SETGT能够得出正确的结果。

dodata: /* XXX */

应用进程读取紧急数据

int

tcp_usrreq(so, req, m, nam, control)

struct socket *so;

int req;

struct mbuf *m, *nam, *control;

{

switch (req) {

/*

 * TCP attaches to socket via PRU_ATTACH, reserving space,

 * and an internet control block.

case PRU_RCVOOB:

能否读取带外数据

 如果下列3个条件有一个为真,应用进程读取带外数据的努力就会失败。

1) 如果插口的带外数据分界点(s o o o b m a r k)等于0,并且插口的S S R C V     A T M A R K标志未设定;或者

2) 如果S O O O B I N L I N E插口选项设定;或者

3) 如果连接的T C P O O B H A D D A T A标志设定(例如,连接的带外数据已 被读取)

如果上述3个条件中任何一个为真,则返回差错代码E I N VA L

if ((so->so_oobmark == 0 &&

    (so->so_state & SS_RCVATMARK) == 0) ||

    so->so_options & SO_OOBINLINE ||

    tp->t_oobflags & TCPOOB_HADDATA) {

error = EINVAL;

break;

}

如果上述3个条件全假,但T C P O O B H A V E D A T A标志置位,说明尽管T C P已收到了对端发送的紧急方式通告,但尚未收到序号等于紧急指针减1的字节,此时返回差错代码E W O U L D B L O C K,有可能因为发送方发送紧急数据通告时,紧急数据偏移量指向了尚未发送的字节。

if ((tp->t_oobflags & TCPOOB_HAVEDATA) == 0) {

error = EWOULDBLOCK;

break;

}

m->m_len = 1;

*mtod(m, caddr_t) = tp->t_iobc;

if (((int)nam & MSG_PEEK) == 0)

tp->t_oobflags ^= (TCPOOB_HAVEDATA | TCPOOB_HADDATA);

break;

四、发送 窗口变量更新 1、 更新snd_wndsnd_wl1snd_wl2TCP_INPUT)

T C P控制块中还有两个窗口变量我们未曾提及: snd_wl1snd_wl2

snd_wl1记录最后接收报文段的序号,用于更新发送窗口(snd_wnd)

snd_wl2记录最后接收报文段的确认序号,用于更新发送窗口。

到目前为止,只在连接建立时(主动打开、被动打开或同时打开)遇到过这两个变量,

snd_wl1 被设定为t i _ s e q 1。当时说是为了保证窗口更新,下面的代码将证明这一点。

如果下列3个条件中的任一个被满足,则应根据接收报文段中的通告窗口值(ti_win)更新发送窗口(snd_wnd);

1) 报文段携带了新数据。因为snd_wl1保存了用于更新窗口的最后接收报文段的起始序号,如果snd_wl1< tiseq,说明此条件为真。

2) 报文段未携带新数据(snd_wl1等于t i s e q),但报文段确认了新数据。因为snd_wl2保存了用于更新窗口的最后接收报文段的确认序号,如果snd_wl2< tiack,说明此条件为真。

3) 报文段未携带新数据,也未确认新数据,但通告窗口大于当前发送窗口。

/*

 * Update window information.

 * Don't look at window if no ACK: TAC's send garbage on first SYN.

 */

if ((tiflags & TH_ACK) &&

    (SEQ_LT(tp->snd_wl1, ti->ti_seq) || tp->snd_wl1 == ti->ti_seq &&

    (SEQ_LT(tp->snd_wl2, ti->ti_ack) ||

     tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd))) {

/* keep track of pure window updates */

if (ti->ti_len == 0 &&

    tp->snd_wl2 == ti->ti_ack && tiwin > tp->snd_wnd)

tcpstat.tcps_rcvwinupd++;

tp->snd_wnd = tiwin;

tp->snd_wl1 = ti->ti_seq;

tp->snd_wl2 = ti->ti_ack;

if (tp->snd_wnd > tp->max_sndwnd)

tp->max_sndwnd = tp->snd_wnd;

needoutput = 1;

更新发送窗口,保存新的snd_wl1snd_wl2。此外,如果新的通告窗口是TCP从对端收到的所有窗口通告中的最大值,则新值被保存在max_sndwnd中。这是为了猜测对端接收缓存的大小。更新snd_wnd后,发送窗口可用空间增加,从而能够发送新的报文段,因此,needoutput标志置位。

这些测试条件的目的是为了防止旧的报文段影响发送窗口,因为发送窗口并非绝对的序

号序列,而是从snd_una算起的偏移量。

2、 snd_una的更新(TCP_INPUT)

1)首部预测

if (SEQ_GT(ti->ti_ack, tp->snd_una) &&

    SEQ_LEQ(ti->ti_ack, tp->snd_max) &&

    tp->snd_cwnd >= tp->snd_wnd) {

/*

 * this is a pure ack for outstanding data.

 */

++tcpstat.tcps_predack;

if (ts_present)

tcp_xmit_timer(tp, tcp_now-ts_ecr+1);

else if (tp->t_rtt &&

    SEQ_GT(ti->ti_ack, tp->t_rtseq))

tcp_xmit_timer(tp, tp->t_rtt);

acked = ti->ti_ack - tp->snd_una;

tcpstat.tcps_rcvackpack++;

tcpstat.tcps_rcvackbyte += acked;

sbdrop(&so->so_snd, acked);

tp->snd_una = ti->ti_ack;

acked 等于接收报文段确认字段所确认的字节数,调用sbdrop从发送缓存中删除这些字节。更新最大的未确认过的序号(s n d u n a)为报文段的确认字段值。

2)syn_sent

case TCPS_SYN_SENT:

if ((tiflags & TH_ACK) &&

    (SEQ_LEQ(ti->ti_ack, tp->iss) ||

     SEQ_GT(ti->ti_ack, tp->snd_max)))

goto dropwithreset;

if (tiflags & TH_RST) {

if (tiflags & TH_ACK)

tp = tcp_drop(tp, ECONNREFUSED);

goto drop;

}

if ((tiflags & TH_SYN) == 0)

goto drop;

if (tiflags & TH_ACK) {

tp->snd_una = ti->ti_ack;

if (SEQ_LT(tp->snd_nxt, tp->snd_una))

tp->snd_nxt = tp->snd_una;

如果报文段中有A C K,令s n d _ u n a等于报文段的确认字段。 如果snd_nxt 小于snd_una,令snd_nxt等于snd_una

3)其它情况

register u_int cw = tp->snd_cwnd;

register u_int incr = tp->t_maxseg;

if (cw > tp->snd_ssthresh)

incr = incr * incr / cw + incr / 8;

tp->snd_cwnd = min(cw + incr, TCP_MAXWIN<<tp->snd_scale);

}

if (acked > so->so_snd.sb_cc) {

tp->snd_wnd -= so->so_snd.sb_cc;

如果确认字节数超过发送缓存中的字节数,则从snd_wnd中减去发送缓存中的字节数,并且可知本地发送的F I N已被确认

sbdrop(&so->so_snd, (int)so->so_snd.sb_cc);

ourfinisacked = 1;

调用s b d r o p从发送缓存中删除所有字节。能够以这种方式检查对F I N报文段的确认,是因为F I N在序号空间中只占一个字节

} else {

sbdrop(&so->so_snd, acked);

tp->snd_wnd -= acked;

ourfinisacked = 0;

如果确认字节数小于或等于发送缓存中的字节数, o u r f i n i s a c k e d等于0,并从发送缓存中丢弃a c k e d字节的数据

}

if (so->so_snd.sb_flags & SB_NOTIFY)

sowwakeup(so);

tp->snd_una = ti->ti_ack;

if (SEQ_LT(tp->snd_nxt, tp->snd_una))

tp->snd_nxt = tp->snd_una;

  唤醒等待发送缓存的进程 调用s o w w a k e u p唤醒所有等待发送缓存的应用进 程,更新snd_una保存最老的未被确认的序号。如果snd_una的新值超过了snd_nxt 则更新后者

3、 snd_nxt

send:

/*

 * Before ESTABLISHED, force sending of initial options

 * unless TCP set not to do any options.

 * NOTE: we assume that the IP/TCP header plus TCP options

 * always fit in a single mbuf, leaving room for a maximum

 * link header, i.e.

 * max_linkhdr + sizeof (struct tcpiphdr) + optlen <= MHLEN

 */

optlen = 0;

hdrlen = sizeof (struct tcpiphdr);

if (flags & TH_SYN) {

tp->snd_nxt = tp->iss;

如果SYN标志置位,snd_nxt复位为初始发送序号iss

if (flags & TH_FIN && tp->t_flags & TF_SENTFIN && 

    tp->snd_nxt == tp->snd_max)

tp->snd_nxt--;

如果TCP已经发送过FIN,因此,如果THFIN置位,则TF_SENTFIN也置位,并且snd_nxt等于snd_max,可知FIN等待重传;发送FIN时,snd_nxt会递增1 (由于FIN也要占用一个序号),因此,这里的代码递减snd_nxt。

if (tp->t_force == 0 || tp->t_timer[TCPT_PERSIST] == 0) {

tcp_seq startseq = tp->snd_nxt;

/*

 * Advance snd_nxt over sequence space of this segment.

 */

if (flags & (TH_SYN|TH_FIN)) {

if (flags & TH_SYN)

tp->snd_nxt++;

if (flags & TH_FIN) {

tp->snd_nxt++;

tp->t_flags |= TF_SENTFIN;

}

}

tp->snd_nxt += len;

if (SEQ_GT(tp->snd_nxt, tp->snd_max)) {

tp->snd_max = tp->snd_nxt;

/*

 * Time this transmission if not a retransmission and

 * not currently timing anything.

 */

if (tp->t_rtt == 0) {

tp->t_rtt = 1;

tp->t_rtseq = startseq;

tcpstat.tcps_segstimed++;

}

}

如果TCP不处于持续状态,则起始序号保存在startseq由于SYN和FIN都占用一个序号,其中任一标志置位,snd_nxt都必须增加。FIN发送过后,TFSENDFIN将置位。之后,snd_nxt增加发送的数据字节数(len),可以为0。

如果snd_nxt的最新值大于snd_max,则不是重传报文Snd_max值被更新。如果连接目前还没有RTT(trtt= 0 ),则定时器启动(trtt= 1 ),计时报文段的起始序号保存在trtseq中。tcpinput利用它确定计时报文段ACK的到达时间,从而更新RTT

4snd_max

if (tp->t_force == 0 || tp->t_timer[TCPT_PERSIST] == 0) {

tcp_seq startseq = tp->snd_nxt;

/*

 * Advance snd_nxt over sequence space of this segment.

 */

if (flags & (TH_SYN|TH_FIN)) {

if (flags & TH_SYN)

tp->snd_nxt++;

if (flags & TH_FIN) {

tp->snd_nxt++;

tp->t_flags |= TF_SENTFIN;

}

}

tp->snd_nxt += len;

if (SEQ_GT(tp->snd_nxt, tp->snd_max)) {

tp->snd_max = tp->snd_nxt;

如果snd_nxt的最新值大于snd_max,则不是重传报文。snd_max值被更新

5、 snd_up更新

int

tcp_usrreq(so, req, m, nam, control)

struct socket *so;

int req;

struct mbuf *m, *nam, *control;

{

switch (req) {

case PRU_SENDOOB:

if (sbspace(&so->so_snd) < -512) {

m_freem(m);

error = ENOBUFS;

break;

}

发送带外数据时,允许应用进程写入数据后,待发送数据量超过发送缓存大小,超出量最多为5 1 2字节。插口层的限制要宽松一些,写入带外数据后,最多可超出发送缓存1 0 2 4字节。调用sbappend向发送缓存末端添加数据

/*

 * According to RFC961 (Assigned Protocols),

 * the urgent pointer points to the last octet

 * of urgent data.  We continue, however,

 * to consider it to indicate the first octet

 * of data past the urgent section.

 * Otherwise, snd_up should be one lower.

 */

sbappend(&so->so_snd, m);

/*紧急指针(snd_up)指向写入的最后一个字节之后的字节*/

tp->snd_up = tp->snd_una + so->so_snd.sb_cc;

tp->t_force = 1;

error = tcp_output(tp);

tp->t_force = 0;

/*令t_force等于1,并调用tcp_output。即使收到了对端的零窗口通告,TCP也会发送报文段,URG标志置位,紧急指针偏移量非零*/

break;


  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值