今天碰到一个奇葩的问题,一直跑的好好的程序,碰到某个服务器就不行了。
当前流程:
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");
}