Linux内核分析 - 网络[十六]:TCP三次握手
几步重要的代码如下, tcp_connect_init()中设置了tp->rcv_nxt=0,tcp_transmit_skb()负责发送报文,其中seq=tcb->seq=tp->write_seq ,ack_seq=tp->rcv_nxt。 tcp_connect_init(sk); tp->snd_nxt = tp->write_seq; …… tcp_transmit_skb(sk, buff, 1, sk->sk_allocation); *收到服务端的SYN+ACK,发送ACK 此时已接收到对方的ACK,状态变迁到TCP_ESTABLISHED。最 后发送对方SYN的ACK报文。 tcp_set_state(sk, TCP_ESTABLISHED); tcp_send_ack(sk); 服务端流程 *bind() -> inet_bind() bind操作的主要作用 是将创建的socket与给定的地址相绑定,这样创建的服务才能公开的让外部调用。当然对于socket服务器的创建来说,这一步不 是必须的,在listen()时如果没有绑定地址,系统会选择一个随机可用地址作为服务器地址。 一个socket地址分为ip和 port,inet->inet_saddr赋值了传入的ip,snum是传入的port,对于端口,要检查它是否已被占用,这是由sk->sk_prot ->get_port()完成的(这个函数前面已经分析过,在传入port时它检查是否被占用;传入port=0时它选择未用的端口)。如果 没有被占用,inet->inet_sport被赋值port,因为是服务监听端,不需要远端地址,inet_daddr和inet_dport都置0。 注意bind操作不会改变socket的状态,仍为创建时的TCP_CLOSE。 snum = ntohs (addr->sin_port); …… inet->inet_rcv_saddr = inet->inet_saddr = addr->sin_addr.s_addr; if (sk->sk_prot->get_port(sk, snum)) { inet->inet_saddr = inet->inet_rcv_saddr = 0; err = -EADDRINUSE; goto out_release_sock; } …… inet->inet_sport = htons(inet->inet_num); inet->inet_daddr = 0; inet->inet_dport = 0; listen() -> inet_listen() listen操作开始服务器的监 听,此时服务就可以接受到外部连接了。在开始监听前,要检查状态是否正确,sock->state==SS_UNCONNECTED确保仍是未连 接的socket,sock->type==SOCK_STREAM确保是TCP协议,old_state确保此时状态是TCP_CLOSE或TCP_LISTEN,在其它状态下 进行listen都是错误的。 if (sock->state != SS_UNCONNECTED || sock- >type != SOCK_STREAM) goto out; old_state = sk->sk_state; if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN))) goto out; 如果已是TCP_LISTEN态,则直接跳过,不用再执行listen了,而只是重新设置listen队列长度 sk_max_ack_backlog,改变listen队列长也是多次执行listen的作用。如果还没有执行listen,则还要调用 inet_csk_listen_start()开始监听。 inet_csk_listen_start()变迁状态至TCP_LISTEN,分配监 听队列,如果之前没有调用bind()绑定地址,则这里会分配一个随机地址。 if (old_state != TCP_LISTEN) { err = inet_csk_listen_start(sk, backlog); if (err) goto out; } sk->sk_max_ack_backlog = backlog; accept() accept() -> sys_accept4() -> inet_accept() - > inet_csk_accept() accept()实际要做的事件并不多,它的作用是返回一个已经建立连接的 socket(即经过了三次握手),这个过程是异步的,accept()并不亲自去处理三次握手过程,而只是监听icsk_accept_queue队列 ,当有socket经过了三次握手,它就会被加到icsk_accept_queue中,所以accept要做的就是等待队列中插入socket,然后被唤 醒并返回这个socket。而三次握手的过程完全是协议栈本身去完成的。换句话说,协议栈相当于写者,将socket写入队列, accept()相当于读者,将socket从队列读出。这个过程从listen就已开始,所以即使不调用accept(),客户仍可以和服务器建立 连接,但由于没有处理,队列很快会被占满。 if (reqsk_queue_empty (&icsk->icsk_accept_queue)) { long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); …… error = inet_csk_wait_for_connect(sk, timeo); …… } newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk); (编辑:PHP编程网 - 黄冈站长网) 【声明】本站内容均来自网络,其相关言论仅代表作者个人观点,不代表本站立场。若无意侵犯到您的权利,请及时与联系站长删除相关内容! |