TCP三次握手时客户端ACK捎带数据

今天碰到一个奇葩的问题,一直跑的好好的程序,碰到某个服务器就不行了。

 

当前流程:

 

client                      server
SYN ------------------->
<------------------------SYN ACK

 

data with ack bit------>

 

    客户端发送data后服务器一直没回ack,导致客户端重传data with ack。
    显然,如果是服务器用户态丢包,那么必然四层协议栈肯定会回ack,然后因为用户态无法处理data,紧接着发送fin或者rst。     所以基本可以定位是服务器四层丢包,或者服务器死机了,呵呵。
于是改动了自己的程序,让其ack后再发data,果不其然,通了,又呵呵。

查看了一下linux源码,基本确定服务器不是用的linux。

 

 

1:Linux作为服务器

在linux 2.6的实现中,当自己处于SYN_RECV状态下,收到带有ack的报文, 处理流程如下

tcp_rcv_state_process 函数:

if (th->ack)

{

case TCP_SYN_RECV:

.......

tcp_set_state(sk, TCP_ESTABLISHED);

} 



switch (sk->sk_state)

{

case TCP_ESTABLISHED:

      tcp_data_queue(sk, skb);

}

 

即linux作为服务器端,在tcp三次握手时,允许客户端发送的ack带有数据,并且会处理数据。ack和数据是分开处理的。

 

2:Linux作为客户端

在linux 2.6的实现中,当自己处于SYN_SENT状态的时候,收到服务器端发送的syn ack时,处理流程如下:

tcp_rcv_synsent_state_process 函数:............................


/*满足某些条件则不立刻发ack,而是和下次数据一起发ack

有数据pending

开启了defer accept选项

开启的delay ack

*/

if (sk->sk_write_pending ||

    icsk->icsk_accept_queue.rskq_defer_accept ||

    icsk->icsk_ack.pingpong) {

/* Save one ACK. Data will be ready after

* several ticks, if write_pending is set.

*

* It may be deleted, but with this feature tcpdumps

* look so _wonderfully_ clever, that I was not able

* to stand against the temptation 8)     --ANK

*/

inet_csk_schedule_ack(sk);

icsk->icsk_ack.lrcvtime = tcp_time_stamp;

icsk->icsk_ack.ato = TCP_ATO_MIN;

tcp_incr_quickack(sk);

tcp_enter_quickack_mode(sk);

inet_csk_reset_xmit_timer(sk, ICSK_TIME_DACK,

  TCP_DELACK_MAX, TCP_RTO_MAX);



discard:

    __kfree_skb(skb);

    return 0;

}

else

{

    tcp_send_ack(sk);

}

..................

 

即linux作为客户端,在tcp三次握手时,允许自己发送的ack带有数据。


紧接着查看rfc 793 

If the state is SYN-SENT then



      first check the ACK bit



        If the ACK bit is set



          If SEG.ACK =< ISS, or SEG.ACK > SND.NXT, send a reset (unless

          the RST bit is set, if so drop the segment and return)



            <SEQ=SEG.ACK><CTL=RST>



          and discard the segment.  Return.



          If SND.UNA =< SEG.ACK =< SND.NXT then the ACK is acceptable.



      second check the RST bit



[Page 66]                                                               



September 1981                                                          

                                           Transmission Control Protocol

                                                Functional Specification

SEGMENT ARRIVES



        If the RST bit is set



          If the ACK was acceptable then signal the user "error:

          connection reset", drop the segment, enter CLOSED state,

          delete TCB, and return.  Otherwise (no ACK) drop the segment

          and return.



      third check the security and precedence



        If the security/compartment in the segment does not exactly

        match the security/compartment in the TCB, send a reset



          If there is an ACK



            <SEQ=SEG.ACK><CTL=RST>



          Otherwise



            <SEQ=0><ACK=SEG.SEQ+SEG.LEN><CTL=RST,ACK>



        If there is an ACK



          The precedence in the segment must match the precedence in the

          TCB, if not, send a reset



            <SEQ=SEG.ACK><CTL=RST>



        If there is no ACK



          If the precedence in the segment is higher than the precedence

          in the TCB then if allowed by the user and the system raise

          the precedence in the TCB to that in the segment, if not

          allowed to raise the prec then send a reset.



            <SEQ=0><ACK=SEG.SEQ+SEG.LEN><CTL=RST,ACK>



          If the precedence in the segment is lower than the precedence

          in the TCB continue.



        If a reset was sent, discard the segment and return.



      fourth check the SYN bit



        This step should be reached only if the ACK is ok, or there is

        no ACK, and it the segment did not contain a RST.



        If the SYN bit is on and the security/compartment and precedence



                                                               [Page 67]



                                                          September 1981

Transmission Control Protocol

Functional Specification

                                                         SEGMENT ARRIVES



        are acceptable then, RCV.NXT is set to SEG.SEQ+1, IRS is set to

        SEG.SEQ.  SND.UNA should be advanced to equal SEG.ACK (if there

        is an ACK), and any segments on the retransmission queue which

        are thereby acknowledged should be removed.



        If SND.UNA > ISS (our SYN has been ACKed), change the connection

        state to ESTABLISHED, form an ACK segment



          <SEQ=SND.NXT><ACK=RCV.NXT><CTL=ACK>



        and send it.  Data or controls which were queued for

        transmission may be included.  If there are other controls or

        text in the segment then continue processing at the sixth step

        below where the URG bit is checked, otherwise return.



        Otherwise enter SYN-RECEIVED, form a SYN,ACK segment



          <SEQ=ISS><ACK=RCV.NXT><CTL=SYN,ACK>



        and send it.  If there are other controls or text in the

        segment, queue them for processing after the ESTABLISHED state

        has been reached, return.



      fifth, if neither of the SYN or RST bits is set then drop the

      segment and return.

客户端三次握手ack捎带数据程序(客户端开启defer_accept选项)

 

 

int main(int argc,char **argv)
{
        int listenfd,connfd,opt=1;
        pid_t childpid;
        int i;
        socklen_t clilen;
        struct sockaddr_in servaddr;
        struct sockaddr_in realaddr;
                char GET[]="GET / HTTP/1.0\r\n\r\n";
                char msg[1024]={0};
                
        if (argc != 3) {
                printf("usage: %s serverip serverport\n", argv[0]);
                return 0;
        }   
        if ((listenfd=socket(AF_INET,SOCK_STREAM,0))==-1)
                printf("socket error errno=%d", errno);

        opt = 1;
        setsockopt(listenfd, IPPROTO_TCP, TCP_DEFER_ACCEPT, &opt, sizeof(opt));

        bzero(&realaddr,sizeof(realaddr));
        realaddr.sin_family=AF_INET;
        realaddr.sin_port=htons(atoi(argv[2]));
        inet_pton(AF_INET,argv[1],&realaddr.sin_addr);
        if (connect(listenfd,(struct sockaddr*)&realaddr,sizeof(realaddr))==-1)
                printf("connect error errno=%d", errno);
                                
                printf("ready to send\n");
        send(listenfd,GET,sizeof(GET),0);
                printf("Testing server\n");
                if(0 == recv(listenfd,msg,500,0))
                {
                        printf("read fin\n");
                        sleep(1);
                        close(listenfd);
                }
                
                printf("Server is Standard\n");
                
}

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值