本文原创为freas_1990,转载请标明出处http://blog.csdn.net/freas_1990/article/details/10223581
TCP状态转移的原理并不高深,但是处理逻辑比较复杂,以下是TCP状态转移图。出自《TCP/IP协议详解:卷2》——W.Richard Stevens
这些状态是怎么实现的呢?
我们来看一下内核源代码。(server端部分)
int
tcp_rcv(struct sk_buff *skb, struct device *dev, struct options *opt,
unsigned long daddr, unsigned short len,
unsigned long saddr, int redo, struct inet_protocol * protocol)
{
struct tcphdr *th;
struct sock *sk; if (!skb) {
DPRINTF((DBG_TCP, "tcp.c: tcp_rcv skb = NULL\n"));
return(0);
}
#if 0 /* FIXME: it's ok for protocol to be NULL */
if (!protocol) {
DPRINTF((DBG_TCP, "tcp.c: tcp_rcv protocol = NULL\n"));
return(0);
} if (!opt) { /* FIXME: it's ok for opt to be NULL */
DPRINTF((DBG_TCP, "tcp.c: tcp_rcv opt = NULL\n"));
}
#endif
if (!dev) {
DPRINTF((DBG_TCP, "tcp.c: tcp_rcv dev = NULL\n"));
return(0);
}
th = skb->h.th; /* Find the socket. */
sk = get_sock(&tcp_prot, th->dest, saddr, th->source, daddr);
DPRINTF((DBG_TCP, "<<\n"));
DPRINTF((DBG_TCP, "len = %d, redo = %d, skb=%X\n", len, redo, skb)); /* If this socket has got a reset its to all intents and purposes
really dead */
if (sk!=NULL && sk->zapped)
sk=NULL; if (sk) {
DPRINTF((DBG_TCP, "sk = %X:\n", sk));
} if (!redo) {
if (tcp_check(th, len, saddr, daddr )) {
skb->sk = NULL;
DPRINTF((DBG_TCP, "packet dropped with bad checksum.\n"));
if (inet_debug == DBG_SLIP) printk("\rtcp_rcv: bad checksum\n");
kfree_skb(skb,FREE_READ);
/*
* We don't release the socket because it was
* never marked in use.
*/
return(0);
} th->seq = ntohl(th->seq); /* See if we know about the socket. */
if (sk == NULL) {
if (!th->rst)
tcp_reset(daddr, saddr, th, &tcp_prot, opt,dev,skb->ip_hdr->tos,255);
skb->sk = NULL;
kfree_skb(skb, FREE_READ);
return(0);
} skb->len = len;
skb->sk = sk;
skb->acked = 0;
skb->used = 0;
skb->free = 0;
skb->saddr = daddr;
skb->daddr = saddr; /* We may need to add it to the backlog here. */
cli();
if (sk->inuse) {
if (sk->back_log == NULL) {
sk->back_log = skb;
skb->next = skb;
skb->prev = skb;
} else {
skb->next = sk->back_log;
skb->prev = sk->back_log->prev;
skb->prev->next = skb;
skb->next->prev = skb;
}
sti();
return(0);
}
sk->inuse = 1;
sti();
} else {
if (!sk) {
DPRINTF((DBG_TCP, "tcp.c: tcp_rcv bug sk=NULL redo = 1\n"));
return(0);
}
} if (!sk->prot) {
DPRINTF((DBG_TCP, "tcp.c: tcp_rcv sk->prot = NULL \n"));
return(0);
} /* Charge the memory to the socket. */
if (sk->rmem_alloc + skb->mem_len >= sk->rcvbuf) {
skb->sk = NULL;
DPRINTF((DBG_TCP, "dropping packet due to lack of buffer space.\n"));
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
}
sk->rmem_alloc += skb->mem_len; DPRINTF((DBG_TCP, "About to do switch.\n")); /* Now deal with it. */
switch(sk->state) {
/*
* This should close the system down if it's waiting
* for an ack that is never going to be sent.
*/
case TCP_LAST_ACK:
if (th->rst) {
sk->zapped=1;
sk->err = ECONNRESET;
sk->state = TCP_CLOSE;
sk->shutdown = SHUTDOWN_MASK;
if (!sk->dead) {
sk->state_change(sk);
}
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
} case TCP_ESTABLISHED:
case TCP_CLOSE_WAIT:
case TCP_FIN_WAIT1:
case TCP_FIN_WAIT2:
case TCP_TIME_WAIT:
if (!tcp_sequence(sk, th, len, opt, saddr,dev)) {
if (inet_debug == DBG_SLIP) printk("\rtcp_rcv: not in seq\n");
#ifdef undef
/* nice idea, but tcp_sequence already does this. Maybe it shouldn't?? */
if(!th->rst)
tcp_send_ack(sk->sent_seq, sk->acked_seq,
sk, th, saddr);
#endif
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
} if (th->rst) {
sk->zapped=1;
/* This means the thing should really be closed. */
sk->err = ECONNRESET; if (sk->state == TCP_CLOSE_WAIT) {
sk->err = EPIPE;
} /*
* A reset with a fin just means that
* the data was not all read.
*/
sk->state = TCP_CLOSE;
sk->shutdown = SHUTDOWN_MASK;
if (!sk->dead) {
sk->state_change(sk);
}
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
}
if (
#if 0
if ((opt && (opt->security != 0 ||
opt->compartment != 0)) ||
#endif
th->syn) {
sk->err = ECONNRESET;
sk->state = TCP_CLOSE;
sk->shutdown = SHUTDOWN_MASK;
tcp_reset(daddr, saddr, th, sk->prot, opt,dev, sk->ip_tos,sk->ip_ttl);
if (!sk->dead) {
sk->state_change(sk);
}
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
} if (th->ack && !tcp_ack(sk, th, saddr, len)) {
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
} if (tcp_urg(sk, th, saddr, len)) {
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
} if (tcp_data(skb, sk, saddr, len)) {
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
} /* Moved: you must do data then fin bit */
if (th->fin && tcp_fin(sk, th, saddr, dev)) {
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
} release_sock(sk);
return(0); case TCP_CLOSE:
if (sk->dead || sk->daddr) {
DPRINTF((DBG_TCP, "packet received for closed,dead socket\n"));
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
} if (!th->rst) {
if (!th->ack)
th->ack_seq = 0;
tcp_reset(daddr, saddr, th, sk->prot, opt,dev,sk->ip_tos,sk->ip_ttl);
}
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0); case TCP_LISTEN:
if (th->rst) {
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
}
if (th->ack) {
tcp_reset(daddr, saddr, th, sk->prot, opt,dev,sk->ip_tos,sk->ip_ttl);
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
} if (th->syn) {
#if 0
if (opt->security != 0 || opt->compartment != 0) {
tcp_reset(daddr, saddr, th, prot, opt,dev);
release_sock(sk);
return(0);
}
#endif /*
* Now we just put the whole thing including
* the header and saddr, and protocol pointer
* into the buffer. We can't respond until the
* user tells us to accept the connection.
*/
tcp_conn_request(sk, skb, daddr, saddr, opt, dev);
release_sock(sk);
return(0);
} kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0); default:
if (!tcp_sequence(sk, th, len, opt, saddr,dev)) {
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
} case TCP_SYN_SENT:
if (th->rst) {
sk->err = ECONNREFUSED;
sk->state = TCP_CLOSE;
sk->shutdown = SHUTDOWN_MASK;
sk->zapped = 1;
if (!sk->dead) {
sk->state_change(sk);
}
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
}
#if 0
if (opt->security != 0 || opt->compartment != 0) {
sk->err = ECONNRESET;
sk->state = TCP_CLOSE;
sk->shutdown = SHUTDOWN_MASK;
tcp_reset(daddr, saddr, th, sk->prot, opt, dev);
if (!sk->dead) {
wake_up_interruptible(sk->sleep);
}
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
}
#endif
if (!th->ack) {
if (th->syn) {
sk->state = TCP_SYN_RECV;
} kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
} switch(sk->state) {
case TCP_SYN_SENT:
if (!tcp_ack(sk, th, saddr, len)) {
tcp_reset(daddr, saddr, th,
sk->prot, opt,dev,sk->ip_tos,sk->ip_ttl);
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
} /*
* If the syn bit is also set, switch to
* tcp_syn_recv, and then to established.
*/
if (!th->syn) {
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
} /* Ack the syn and fall through. */
sk->acked_seq = th->seq+1;
sk->fin_seq = th->seq;
tcp_send_ack(sk->sent_seq, th->seq+1,
sk, th, sk->daddr); case TCP_SYN_RECV:
if (!tcp_ack(sk, th, saddr, len)) {
tcp_reset(daddr, saddr, th,
sk->prot, opt, dev,sk->ip_tos,sk->ip_ttl);
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
}
sk->state = TCP_ESTABLISHED; /*
* Now we need to finish filling out
* some of the tcp header.
*/
/* We need to check for mtu info. */
tcp_options(sk, th);
sk->dummy_th.dest = th->source;
sk->copied_seq = sk->acked_seq-1;
if (!sk->dead) {
sk->state_change(sk);
} /*
* We've already processed his first
* ack. In just about all cases that
* will have set max_window. This is
* to protect us against the possibility
* that the initial window he sent was 0.
* This must occur after tcp_options, which
* sets sk->mtu.
*/
if (sk->max_window == 0) {
sk->max_window = 32;
sk->mss = min(sk->max_window, sk->mtu);
} /*
* Now process the rest like we were
* already in the established state.
*/
if (th->urg) {
if (tcp_urg(sk, th, saddr, len)) {
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
}
}
if (tcp_data(skb, sk, saddr, len))
kfree_skb(skb, FREE_READ); if (th->fin) tcp_fin(sk, th, saddr, dev);
release_sock(sk);
return(0);
} if (th->urg) {
if (tcp_urg(sk, th, saddr, len)) {
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
}
} if (tcp_data(skb, sk, saddr, len)) {
kfree_skb(skb, FREE_READ);
release_sock(sk);
return(0);
} if (!th->fin) {
release_sock(sk);
return(0);
}
tcp_fin(sk, th, saddr, dev);
release_sock(sk);
return(0);
}
}