tcp三次握手讲解协议栈

本文是从其他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); 

}


else 中的部份用于另一个 hash 表,暂时不管它。代表很简单,如果确认是处理的是监听 hash 表。则先根据 sk计算一个 hash 值,在hash 桶中找到入口。再调用__sk_add_node 加入至该 hash 链。 
 
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;


出队,并取得相应的 sk。 否则,就在获取超时时间后,调用 wait_for_connect 等待连接的到来。这也是说,强调“或等待”的原因所在了。 
 
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); 


即 tcp_v4_syn_recv_sock函数,其又调用 tcp_create_openreq_child()来分配的。 
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信号

-> icsk->icsk_af_ops->conn_request
(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_rcv_synsent_state_process
-> 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_rcv_state_process

-> tcp_sequenc

这里将sock设置TCP_ESTABLISHED


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值