本文是从其他blog复制过来的,如有雷同,那就是复制你的。
TCP握手协议
在TCP/ IP协议中,TCP协议 提供可靠的连接 服务,采用三次握手 建立一个连接。第 一次握手:建立连接时, 客户端通过sys_connect系统调用发送syn包(syn=j)到 服务器,并进入SYN_SEND状态,等待服务器确认;
第二次握手:服务器收到syn包, 必须确认客户的SYN(ack=j+1),同时自己也发送一个SYN包(syn=k),即SYN+ACK包,此时服务器进入SYN_RECV状态;
第三次握手:客户端收到服务器的SYN+ACK包,向服务器发送确认包ACK(ack=k+1),此包发送完毕,客户端进入ESTABLISHED状态,服务器接收到ack信号后也进入 ESTABLISHED状态完成三次握手。
服务器通过sys_accept调用,将listen监听列表中ESTABLISHED状态的sock取出,并分配socket于sock相关联,然后将socket至于connected状态。整个链接完成
未连接队列:在三次握手协议中,服务器维护一个未连接队列,该队列为每个客户端的SYN包(syn=j)开设一个条目,该条目表明服务器已收到SYN包,并向客户发出确认,正在等待客户的确认包。这些条目所标识的连接在服务器处于Syn_RECV状态,当服务器收到客户的确认包时,删除该条目,服务器进入ESTABLISHED状态。
Backlog参数:表示未连接队列的最大容纳数目。
SYN-ACK 重传 次数服务器发送完SYN-ACK包,如果未收到客户确认包,服务器进行首次重传,等待一段 时间仍未收到客户确认包,进行第二次重传,如果重传次数超过 系统规定的最大重传次数,系统将该连接信息从半连接队列中删除。 注意,每次重传等待的时间不一定相同。
半连接 存活时间:是指半连接队列的条目存活的最长时间,也即服务从收到SYN包到确认这个报文无效的最长时间,该时间值是所有重传请求包的最长等待时间总和。有时 我们也称半连接存活时间为Timeout时间、SYN_RECV存活时间。
首先从服务器端说起:
1、服务器调用sys_socket 创建套接字
SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
{
……
//创建套接字,
retval = sock_create(family, type, protocol, &sock);
if (retval < 0)
goto out;
//套接字于文件系统相关联。
retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
if (retval < 0)
goto out_release;
}
int __sock_create(struct net *net, int family, int type, int protocol,
struct socket **res, int kern)
{
int err;
struct socket *sock;
const struct net_proto_family *pf
err = security_socket_create(family, type, protocol, kern);
//创建inode节点,和socket结构
sock = sock_alloc();
sock->type = type;
//获取inet层协议操作函数,inet_family_ops
// struct net_proto_family {
// int family;
// int (*create)(struct net *net, struct socket *sock,
// int protocol, int kern);
// struct module *owner;
//};
pf = rcu_dereference(net_families[family]);
//创建sock结构,并初始化。这里于之前的socket创建不同,sock用于inet层以下,socket用于inet层以上。
//这里最终会得到,tcp_sock。tcp_sock包含inet_connect_sock, inet_connect_sock 包含inet_sock, inet_sock包含sock。
err = pf->create(net, sock, protocol, kern);
err = security_socket_post_create(sock, family, type, protocol, kern);
return 0;
}
static int inet_create(struct net *net, struct socket *sock, int protocol,
int kern)
{
//将sock设置SS_UNCONNECTED态
sock->state = SS_UNCONNECTED;
//找到对应tpc层协议
list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {
err = 0;
/* Check the non-wild match. */
if (protocol == answer->protocol) {
if (protocol != IPPROTO_IP)
break;
} else {
/* Check for the two wild cases. */
if (IPPROTO_IP == protocol) {
protocol = answer->protocol;
break;
}
if (IPPROTO_IP == answer->protocol)
break;
}
err = -EPROTONOSUPPORT;
}
if (sock->type == SOCK_RAW && !kern && !capable(CAP_NET_RAW))
goto out_rcu_unlock;
err = -EAFNOSUPPORT;
if (!inet_netns_ok(net, protocol))
goto out_rcu_unlock;
sock->ops = answer->ops;
answer_prot = answer->prot;
answer_no_check = answer->no_check;
answer_flags = answer->flags;
rcu_read_unlock();
WARN_ON(answer_prot->slab == NULL);
err = -ENOBUFS;
//创建sk
sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
if (sk == NULL)
goto out;
err = 0;
sk->sk_no_check = answer_no_check;
if (INET_PROTOSW_REUSE & answer_flags)
sk->sk_reuse = 1;
inet = inet_sk(sk);
inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;
inet->nodefrag = 0;
if (SOCK_RAW == sock->type) {
inet->inet_num = protocol;
if (IPPROTO_RAW == protocol)
inet->hdrincl = 1;
}
if (ipv4_config.no_pmtu_disc)
inet->pmtudisc = IP_PMTUDISC_DONT;
else
inet->pmtudisc = IP_PMTUDISC_WANT;
inet->inet_id = 0;
sock_init_data(sock, sk);
sk->sk_destruct = inet_sock_destruct;
sk->sk_protocol = protocol;
sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;
inet->uc_ttl = -1;
inet->mc_loop = 1;
inet->mc_ttl = 1;
inet->mc_all = 1;
inet->mc_index = 0;
inet->mc_list = NULL;
sk_refcnt_debug_inc(sk);
if (inet->inet_num) {
/* It assumes that any protocol which allows
* the user to assign a number at socket
* creation time automatically
* shares.
*/
inet->inet_sport = htons(inet->inet_num);
/* Add to protocol hash chains. */
sk->sk_prot->hash(sk);
}
if (sk->sk_prot->init) {
err = sk->sk_prot->init(sk);
if (err)
sk_common_release(sk);
}
}
这里sys_socket完成,inode于socket关联,socket于sock关联。这样通过inode节点就可以找到对应的sock。
2、sys_bind调用
sys_socket后,将调用sys_bind绑定ip地址于端口。如果没调用,sys_listen也会选择默认地址和端口。这里就不详讲
3、sys_listen调用
服务器在创建套接字后需调用sys_listen。将套接字设置成监听套接字。
对面向连接的协议,在调用 bind(2)后,进一步调用 listen(2),让套接字进入监听状态:
int listen(int sockfd, int backlog);backlog 表示新建连接请求时,最大的未处理的积压请求数。
这里说到让套接字进入某种状态,也就是说,涉及到套接字的状态变迁,前面 create 和bind 时,也遇到过相应的代码。
sock和 sk 都有相应的状态字段,先来看 sock 的:
typedef enum {
SS_FREE = 0, /* 套接字未分配 */
SS_UNCONNECTED, /* 套接字未连接 */
SS_CONNECTING, /* 套接字正在处理连接 */
SS_CONNECTED, /* 套接字已连接 */
SS_DISCONNECTING /* 套接字正在处理关闭连接 */ } socket_state;
在创建套接字时,被初始化为 SS_UNCONNECTED。
对于面向连接模式的SOCK_TREAM来讲,这样描述状态显然是不够的。 这样, 在sk中, 使用sk_state维护了一个有限状态机来描述套接字的状态:
enum {
TCP_ESTABLISHED = 1,
TCP_SYN_SENT,
TCP_SYN_RECV,
TCP_FIN_WAIT1,
TCP_FIN_WAIT2,
TCP_TIME_WAIT,
TCP_CLOSE,
TCP_CLOSE_WAIT,
TCP_LAST_ACK,
TCP_LISTEN,
TCP_CLOSING, /* now a valid state */
TCP_MAX_STATES /* Leave at the end! */
};
还有一个相应的用来进行状态位运算的枚举结构:
enum {
TCPF_ESTABLISHED = (1 << 1),
TCPF_SYN_SENT = (1 << 2),
TCPF_SYN_RECV = (1 << 3),
TCPF_FIN_WAIT1 = (1 << 4),
TCPF_FIN_WAIT2 = (1 << 5),
TCPF_TIME_WAIT = (1 << 6),
TCPF_CLOSE = (1 << 7),
TCPF_CLOSE_WAIT = (1 << 8),
TCPF_LAST_ACK = (1 << 9),
TCPF_LISTEN = (1 << 10),
TCPF_CLOSING = (1 << 11)
};
值得一提的是,sk 的状态不等于 TCP的状态,虽然 sk 是面向协议栈,但它的状态并不能同 TCP状态一一直接划等号。虽然这些状态值都用 TCP-XXX 来表式,但是只是因为 TCP协议状态非常复杂。sk 结构只是利用它的一个子集来抽像描述而已。
同样地,操作码 SYS_LISTEN的任务会落到 sys_listen()函数身上:
/* Maximum queue length specifiable by listen. */
#define SOMAXCONN 128
int sysctl_somaxconn = SOMAXCONN;asmlinkage long sys_listen(int fd, int backlog)
{
struct socket *sock;
int err;
if ((sock = sockfd_lookup(fd, &err)) != NULL) {
if ((unsigned) backlog > sysctl_somaxconn)
backlog = sysctl_somaxconn;
err = security_socket_listen(sock, backlog);
if (err) {
sockfd_put(sock);
return err;
}
err=sock->ops->listen(sock, backlog);
sockfd_put(sock);
}
return err;
}
同样地,函数会最终转向协议簇的 listen 函数,也就是 inet_listen():
/*
* Move a socket into listening state.
*/
int inet_listen(struct socket *sock, int backlog)
{
struct sock *sk = sock->sk;
unsigned char old_state;
int err;
lock_sock(sk);
err = -EINVAL;
/* 在 listen 之前,sock 必须为未连接状态,之前在sys_socket中讲过将socket设置未链接状态,且只有 SOCK_STREAM 类型,才有 listen(2)*/
if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
goto out;
/* 临时保存状态机状态 */
old_state = sk->sk_state; /* 只有状态机处于 TCP_CLOSE 或者是 TCP_LISTEN 这两种状态时,才可能对其调用listen(2) ,这个判断证明了 listen(2)是可以重复调用地(当然是在转向 TCP_LISTEN 后没有再进行状态变迁*/
if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
goto out;
/* 如果接口已经处理 listen 状态,只修改其 max_backlog,否则先调用 tcp_listen_start,继续设置协议的 listen 状态 */
if (old_state != TCP_LISTEN) {
err = tcp_listen_start(sk);
if (err)
goto out;
}
sk->sk_max_ack_backlog = backlog;
err = 0;
out:
release_sock(sk);
return err;
}
inet_listen 函数在确认 sock->state 和 sk->sk_state 状态后,会进一步调用 tcp_listen_start 函数,最且最后设置 sk_max_ack_backlog 。
tcp 的 tcp_listen_start 函数,完成两个重要的功能,一个是初始化 sk 的一些相关成员变量,另一方面是切换有限状态机的状态。 sk_max_ack_backlog表示监听时最大的 backlog 数量,它由用户空间传递的参数决定。而 sk_ack_backlog表示当前的的 backlog数量。
当 tcp 服务器收到一个 syn 报文时,它表示了一个连接请求的到达。内核使用了一个 hash 表来维护这个连接请求表:
struct tcp_listen_opt
{
u8 max_qlen_log; /* log_2 of maximal queued SYNs */
int qlen;
int qlen_young;
int clock_hand;
u32 hash_rnd;
struct open_request *syn_table[TCP_SYNQ_HSIZE];
};
syn_table, 是open_request结构,就是连接请求表,连中的最大项,也就是最大允许的 syn 报文的数量,由 max_qlen_log 来决定。当套接字进入 listen 状态,也就是说可以接收 syn 报文了,那么在此之前,需要先初始化这个表:
int tcp_listen_start(struct sock *sk)
{
struct inet_sock *inet = inet_sk(sk); //获取 inet结构指针
struct tcp_sock *tp = tcp_sk(sk); //获取协议指针
struct tcp_listen_opt *lopt;
//初始化 sk 相关成员变量
sk->sk_max_ack_backlog = 0;
sk->sk_ack_backlog = 0;
tp->accept_queue = tp->accept_queue_tail = NULL;
rwlock_init(&tp->syn_wait_lock);
tcp_delack_init(tp);
//初始化连接请求 hash 表
lopt = kmalloc(sizeof(struct tcp_listen_opt), GFP_KERNEL);
if (!lopt)
return -ENOMEM;
memset(lopt, 0, sizeof(struct tcp_listen_opt));
//初始化 hash 表容量,最小为 6,其实际值由 sysctl_max_syn_backlog 决定
for (lopt->max_qlen_log = 6; ; lopt->max_qlen_log++)
if ((1 << lopt->max_qlen_log) >= sysctl_max_syn_backlog)
break;
get_random_bytes(&lopt->hash_rnd, 4);
write_lock_bh(&tp->syn_wait_lock);
tp->listen_opt = lopt;
write_unlock_bh(&tp->syn_wait_lock);
/* There is race window here: we announce ourselves listening,
* but this transition is still not validated by get_port().
* It is OK, because this socket enters to hash table only
* after validation is complete.
*/
/* 修改状态机状态,表示进入 listen 状态,根据作者注释,当宣告自己进入 listening 状态后,但是这个状态转换并没有得到 get_port 的确 认。所以需要调用 get_port()函数。但是对于一点,暂时还没有完全搞明白,只有留待后面再来分析它 */
sk->sk_state = TCP_LISTEN;
if (!sk->sk_prot->get_port(sk, inet->num)) {
inet->sport = htons(inet->num);
sk_dst_reset(sk);
sk->sk_prot->hash(sk);
return 0;
}
sk->sk_state = TCP_CLOSE;
write_lock_bh(&tp->syn_wait_lock);
tp->listen_opt = NULL;
write_unlock_bh(&tp->syn_wait_lock);
kfree(lopt);
return -EADDRINUSE;
}
在切换了有限状态机状态后,调用了
sk->sk_prot->hash(sk);
也就是 tcp_v4_hash()函数。这里涉到到另一个 hash 表:TCP监听 hash 表。
二、TCP监听 hash表
所谓 TCP 监听表,指的就内核维护“当前有哪些套接字在监听”的一个表,当一个数据包进入 TCP栈的时候,内核查询这个表中对应的 sk,以找到相应的数据 结构。 (因为 sk 是面向网络栈调用的,找到了 sk,就找到了 tcp_sock,就找到了 inet_sock,就找到了 sock,就找到了 fd……就到了组 织了)。
TCP所有的 hash 表都用了tcp_hashinfo来封装,前面分析 bind已见过它:
extern struct tcp_hashinfo {
……
/* All sockets in TCP_LISTEN state will be in here. This is the only
* table where wildcard'd TCP sockets can exist. Hash function here
* is just local port number.
*/
struct hlist_head __tcp_listening_hash[TCP_LHTABLE_SIZE];
……
spinlock_t __tcp_portalloc_lock;
} tcp_hashinfo;
#define tcp_listening_hash (tcp_hashinfo.__tcp_listening_hash)
函数 tcp_v4_hash 将一个处理监听状态下的 sk 加入至这个 hash 表:
static void tcp_v4_hash(struct sock *sk)
{
if (sk->sk_state != TCP_CLOSE) {
local_bh_disable(); __tcp_v4_hash(sk, 1);
local_bh_enable();
}
}
因为__tcp_v4_hash 不只用于监听 hash 表,它也用于其它 hash 表,其第二个参数 listen_possible 为真的时候,表示处理的是监听 hash表:
static __inline__ void __tcp_v4_hash(struct sock *sk, const int listen_possible)
{
struct hlist_head *list;
rwlock_t *lock;
BUG_TRAP(sk_unhashed(sk));
if (listen_possible && sk->sk_state == TCP_LISTEN) {
list = &tcp_listening_hash[tcp_sk_listen_hashfn(sk)];
lock = &tcp_lhash_lock;
tcp_listen_wlock();
} else {
……
}
__sk_add_node(sk, list);
sock_prot_inc_use(sk->sk_prot);
write_unlock(lock);
if (listen_possible && sk->sk_state == TCP_LISTEN)
wake_up(&tcp_lhash_wait);
}
tcp_sk_listen_hashfn()函数事实上是 tcp_lhashfn 的包裹,前面已经分析过了。
__sk_add_node()函数也就是一个简单的内核 hash处理函数 hlist_add_head()的包裹:
static __inline__ void __sk_add_node(struct sock *sk, struct hlist_head *list)
{
hlist_add_head(&sk->sk_node, list);
}
小结
一个套接字的 listen,主要需要做的工作有以下几件:
1、初始化 sk 相关的成员变量,最重要的是 listen_opt,也就是连接请求 hash 表。
2、将 sk 的有限状态机转换为 TCP_LISTEN,即监听状态;
3、将 sk 加入监听 hash表;
4、设置允许的最大请求积压数,也就是 sk 的成员 sk_max_ack_backlog 的值。
4、sys_accept
在经过3次握手后,监听套接字的哈希链表中就保存这该3次握手后创建的sock。此时sock为ESTABLISHED状态,服务器通过调用sys_accept将sock取出,并创建socket与之 关联,并将socket设置ss_connected状态,然后关联inode,之后就可以通信了
accept()调用,只是针对有连接模式。socket 一旦经过 listen()调用进入监听状态后,就被动地调用accept(),接受来自客 户端的连接请求。accept()调用是阻塞的,也就是说如果没有连接请求到达,它会去睡觉,等到连接请求到来后(或者是超时),才会返回。同样地,操 作码 SYS_ACCEPT 对应的是函数 sys_accept:
asmlinkage long sys_accept(int fd, struct sockaddr __user *upeer_sockaddr, int __user
*upeer_addrlen) {
struct socket *sock, *newsock;
int err, len;
char address[MAX_SOCK_ADDR];
sock = sockfd_lookup(fd, &err);
if (!sock)
goto out;
err = -ENFILE;
//创建socket
if (!(newsock = sock_alloc()))
goto out_put;
newsock->type = sock->type;
newsock->ops = sock->ops;
err = security_socket_accept(sock, newsock);
//socket于sock关联
err = sock->ops->accept(sock, newsock, sock->file->f_flags);
if (err < 0)
goto out_release;
//socket于inode关联
if ((err = sock_map_fd(newsock)) < 0)
goto out_release;
security_socket_post_accept(sock, newsock);
}
代码稍长了点,逐步来分析它。
一个 socket,经过 listen(2)设置成 server 套接字后,就永远不会再与任何客户端套接字建立连接了。因为一旦它接受了一个连接请求,就会 创建出一个新的 socket,新的 socket 用来描述新到达的连接,而原先的 server套接字并无改变,并且还可以通过下一次 accept()调用 再创建一个新的出来,就像母鸡下蛋一样,“只取蛋,不杀鸡”,server 套接字永远保持接受新的连接请求的能力。
函数先通过 sockfd_lookup(),根据 fd,找到对应的 sock,然后通过 sock_alloc分配一个新的 sock。接着就调用协议簇的 accept()函数:
/*
* Accept a pending connection. The TCP layer now gives BSD semantics.
*/
int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{
struct sock *sk1 = sock->sk;
int err = -EINVAL;
struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);
if (!sk2)
goto do_err;
lock_sock(sk2);
BUG_TRAP((1 << sk2->sk_state) &
(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT | TCPF_CLOSE));
sock_graft(sk2, newsock);
newsock->state = SS_CONNECTED;
err = 0;
release_sock(sk2); do_err:
return err;
}
函数第一步工作是调用协议的 accept 函数,然后调用 sock_graft()函数,接下来,设置新的套接字的状态为 SS_CONNECTED。
/*
* This will accept the next outstanding connection.
*/
struct sock *tcp_accept(struct sock *sk, int flags, int *err)
{
struct tcp_sock *tp = tcp_sk(sk);
struct open_request *req;
struct sock *newsk;
int error;
lock_sock(sk);
/* We need to make sure that this socket is listening,
* and that it has something pending.
*/
error = -EINVAL;
if (sk->sk_state != TCP_LISTEN)
goto out;
/* Find already established connection */
if (!tp->accept_queue) {
long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
/* If this is a non blocking socket don't sleep */
error = -EAGAIN;
if (!timeo)
goto out;
error = wait_for_connect(sk, timeo);
if (error)
goto out;
}
}
tcp_accept()函数,当发现 tp->accept_queue 准备就绪后,就直接调用
req = tp->accept_queue;
if ((tp->accept_queue = req->dl_next) == NULL)
tp->accept_queue_tail = NULL;
newsk = req->sk;
OK,继续回到 inet_accept 中来,当取得一个就绪的连接的 sk(sk2)后,先校验其状态,再调用sock_graft()函数。
在 sys_accept 中,已经调用了 sock_alloc,分配了一个新的 socket 结构(即 newsock),但 sock_alloc必竟不是 sock_create,它并不能为 newsock 分配一个对应的 sk。所以这个套接字并不完整。 另一方面,当一个连接到达到,根据客户端的请求,产生了一个新的 sk(即 sk2,但这个分配过程没有深入 tcp 栈去分析其实现,只分析了它对应的 req 入队的代码)。呵呵,将两者一关联,就 OK了,这就是 sock_graft 的任务:
static inline void sock_graft(struct sock *sk, struct socket *parent)
{
write_lock_bh(&sk->sk_callback_lock);
sk->sk_sleep = &parent->wait;
parent->sk = sk;
sk->sk_socket = parent;
write_unlock_bh(&sk->sk_callback_lock);
}
这样,一对一的联系就建立起来了。这个为 accept 分配的新的 socket 也大功告成了。接下来将其状态切换为 SS_CONNECTED,表示已连接就绪,可以来读取数据了——如果有的话。
顺便提一下,新的 sk 的分配,是在:
tcp_v4_rcv
->tcp_v4_do_rcv->tcp_check_req
->tp->af_specific->syn_recv_sock(sk, skb, req, NULL);
struct sock *tcp_create_openreq_child(struct sock *sk, struct open_request *req, struct sk_buff *skb)
{
/* allocate the newsk from the same slab of the master sock,
* if not, at sk_free time we'll try to free it from the wrong
* slabcache (i.e. is it TCPv4 or v6?), this is handled thru sk->sk_prot -acme */
struct sock *newsk = sk_alloc(PF_INET, GFP_ATOMIC, sk->sk_prot, 0);
if(newsk != NULL) {
……
memcpy(newsk, sk, sizeof(struct tcp_sock));
newsk->sk_state = TCP_SYN_RECV;
……
}
等到分析 tcp 栈的实现的时候,再来仔细分析它。但是这里新的 sk 的有限状态机被切换至了 TCP_SYN_RECV(按我的想法,似乎应进入 establshed 才对呀,是不是哪儿看漏了,只有看了后头的代码再来印证了)
sys_accept 的最后一步工作,是将新的 socket 结构,与文件系统挂钩:
if ((err = sock_map_fd(newsock)) < 0)
goto out_release;
函数 sock_map_fd 在创建 socket 中已经见过了。
小结:
accept 有几件事情要做:
1、要 accept,需要三次握手完成,连接请求入 tp->accept_queue 队列(新为客户端分析的 sk,也在其中),其才能出队;
2、为 accept分配一个 sokcet 结构,并将其与新的 sk 关联;3、如果调用时,需要获取客户端地址,即第二个参数不为 NULL,则从新的 sk 中,取得其想的葫芦;
4、将新的 socket 结构与文件系统挂钩;
这里服务器工作就完成里
客户端:
1、sys_sockt
2、sys_bind
3、sys_connect
对于 TCP 协议来说,其连接,实际上就是发送一个 SYN 报文,在服务器的应到到来时,回答它一个 ack 报文,也就是完成三次握手中的第一和第三次
这里不做详细介绍
3次握手协议详解:
最近工作需要修改一套tcp协议栈,简单的看了一下内核tcp协议栈的函数调用过程,只是一个大致的流程,并没有做详细的调查,准备修改的时候在仔细的看.还有就是仅仅看了tcp部分,下面的ip以下的都没有查看.
在这里简单的说一下关于tcp连接开始的三次握手的函数调用关系:
客户端:(发起连接请求)
tcp_v4_connect -> tcp_connect_init
-> tcp_transmit_skb -> icsk->icsk_af_ops->send_check
(tcp_v4_send_check)
-> icsk->icsk_af_ops->queue_xmit
(ip_queue_xmit)
向外发送syn包
-> inet_csk_reset_xmit_timer
这里将sock设置TCP_SYN_SENT
设置从新发送的定时器
如果过一段时间没有接到应答:
tcp_retransmit_timer -> tcp_retransmit_skb -> tcp_transmit_skb
其余操作就跟上面的相同了.
服务器端:(接收syn,并返回syn/ack)
接收中断->ip_local_deliver->tcp_v4_rcv -> tcp_v4_do_rcv
如果是监听套接字
-> tcp_v4_hnd_req
-> tcp_rcv_state_process
如果是受到的syn信号
(tcp_v4_conn_request) ->
-> tcp_v4_init_sequence
-> tcp_v4_send_synack
-> ip_build_and_send_pkt
这里将sock设置TCP_SYN_RECIV.
客户端:(接收syn/ack,并返回ack)
tcp_v4_rcv -> tcp_v4_do_rcv
-> tcp_rcv_state_process
这时为TCP_SYN_SENT,则进入下面
-> tcp_ack
-> tcp_store_ts_recent
-> tcp_initialize_rcv_mss
-> tcp_send_ack
->tcp_transmit_skb
-> tcp_urg
-> tcp_data_snd_check
这里将sock设置TCP_ESTABLISHED
服务器端:(接收ack)
tcp_v4_do_rcv
-> tcp_v4_hnd_req
-> tcp_check_req
以下创建sock
->inet_sk(sk)->icsk_af_ops->syn_recv_sock
tcp_v4_hnd_req返回后调用
->tcp_child_process
-> tcp_sequenc
这里将sock设置TCP_ESTABLISHED