• 深入理解TCP协议及其源代码


    我选择的问题是:connect及bind、listen、accept背后的三次握手

    1.TCP建立连接的三次握手过程

    1. 第一次握手:客户端尝试连接服务器,向服务器发送syn(全称是同步序列编号)报文,syn=i,客户端进入SYN_SEND状态等待服务器确认
    2. 第二次握手:服务器接收客户端syn报文并确认(ack=i+1),同时向客户端发送一个新的SYN报文(syn=j),即SYN+ACK报文,此时服务器进入SYN_RECV状态
    3. 第三次握手:客户端收到服务器的SYN+ACK报文,向服务器发送确认报文ACK(ack=j+1),此报文发送并被客户端接收后,客户端和服务器进入ESTABLISHED状态,完成三次握手

    2.探究使用Linux Socket api建立TCP连接的过程

     

     从创建socket,到建立连接接收数据,最后关闭socket的过程如上图所示。其中,和建立连接有关系的socket api主要是:connect、bind、listen和accept

    为了探究建立连接时发生了什么,和TCP三次握手有什么关系,我们使用之前实验所写的hello/hi程序,用gdb为这四个函数打上断点,并使用wireshark监视相应端口,抓取数据包

    当服务端运行bind,listen后,并没有捕获到任何数据包

    直到客户端运行connect后,才捕获到TCP三次握手发送的数据包,如下图所示

    可以通过抓取的数据包信息看到Socket是如何建立TCP连接的

    1. 由客户端(44434端口)发送SYN数据报给服务端(65432端口),其中seq=0(这里和后面的seq,都是显示的相对seq,实际并不是0)
    2. 服务端返回SYN+ACK数据报给客户端,其中ack=1,seq=0
    3. 客户端返回ACK数据报,其中ack=1

    通过这个实践可以推测,TCP的三次握手是在connect和accept之间完成的,bind和listen只是完成绑定和监听的功能

    3.从源码角度分析TCP三次握手的过程

    在上一个实验探究Socket底层是如何实现多态机制的时候,我们发现socket结构体中有一个名为ops的结构体指针,结构体中又通过函数指针绑定了具体的底层函数,完成了connect、accept的实现。在struct proto tcp_prot的初始化中我们可以找到对应的绑定函数。

    struct proto tcp_prot = {
        .name            = "TCP",
        .owner            = THIS_MODULE,
        .close            = tcp_close,
        .pre_connect    = tcp_v4_pre_connect,
        .connect        = tcp_v4_connect,
        .disconnect        = tcp_disconnect,
        .accept            = inet_csk_accept,
    
        ...
    };

    可以看到,socket->ops->connect绑定了函数tcp_v4_connect,socket->ops->accept绑定了inet_csk_accept

    对tcp_v4_connect的部分源码分析

    ...
     
        //设置套接字状态,从CLOSE变为TCP_SYN_SENT,对应客户端从CLOSED->SYN_SENT这一过程
        tcp_set_state(sk, TCP_SYN_SENT);
        //将套接字sk放入TCP连接管理哈希链表中
        err = inet_hash_connect(&tcp_death_row, sk);
        if (err)
            goto failure;
       //为连接分配一个随机的空闲端口
        err = ip_route_newports(&rt, IPPROTO_TCP,
                    inet->inet_sport, inet->inet_dport, sk);
        if (err)
            goto failure;
     
    ...
        
    ...
     
    if (!tp->write_seq)
            //初始化报文内容
            tp->write_seq = secure_tcp_sequence_number(inet->inet_saddr,
                                   inet->inet_daddr,
                                   inet->inet_sport,
                                   usin->sin_port);
     
        inet->inet_id = tp->write_seq ^ jiffies;
        //构建并发送SYN数据报
        err = tcp_connect(sk);
        rt = NULL;
        if (err)
            goto failure;
     
    ...

    对inet_csk_accept的部分源码分析

    在分析代码前我们需要了解,套接字有监听套接字和具体通信的套接字(accept返回的那个)。监听套接字的扩展结构inet_connection_sock中存在icsk_accept_queue成员,此成员中有两个队列,一个用于完全建立连接(完成三次握手)的队列,此队列项中会包含新建的
    用于通信的sock结构,在进程不在阻塞获得此sock结构后会把此队列项从完全建立连接的队列删除.此队列的最大长度即是listen(int s, int backlog)中第二个参数指定的;另一个队列是半连接队列,即还没有完成三次握手的队列项会加入到此队列,此队列项中的sock完成三次握手后会从此队列中移除,添加到完全建立连接的队列中

    ...
    //检查套接字是否处于监听状态(应该是在调用listen时设置的)
        error = -EINVAL;
        if (sk->sk_state != TCP_LISTEN)
            goto out_err;
     
        //在监听套接字上的连接队列如果为空(没有任何连接完成)
        if (reqsk_queue_empty(&icsk->icsk_accept_queue)) {
     
            //设置接收超时时间,若调用accept的时候设置了O_NONBLOCK,表示马上返回不阻塞进程
            long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);
     
            error = -EAGAIN;
            if (!timeo)//如果是非阻塞模式timeo为0 则马上返回
                goto out_err;
     
            //将进程阻塞,等待连接的完成,inet_csk_wait_for_connect核心是一个循环,等待三次握手中,客户端发来的最后一个ACK报文
            error = inet_csk_wait_for_connect(sk, timeo);
            if (error)
                goto out_err;
        }
     
        //在监听套接字建立连接的队列中删除此request_sock连接项 并返回建立连接的sock
        newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk);
     
        //套接字状态变为TCP_SYN_RECV,对应连接建立完成,服务端进入ESTABLISHED状态
        WARN_ON(newsk->sk_state == TCP_SYN_RECV)

    分析这两段代码后,我们对TCP连接的建立已经有了一部分认知,tcp_v4_connect()会发送SYN报文开始三次握手,而inet_csk_accept接收来自客户端的ACK报文,标志着TCP连接建立完成。

    三次握手的分析还并不完整,服务器端是如何接收第一次握手发来的SYN数据报,并返回SYN+ACK数据报的?实际上服务器端接收到SYN报文后,最终会调用tcp_v4_do_rcv()进行处理, 和tcp_send_ack()一起返回第二次握手中的SYN+ACK报文,客户端则是使用tcp_send_ack() 返回最后的ACK报文。受限于篇幅,不再对这些函数的源码进行分析

  • 相关阅读:
    菜单代码
    一个三角形证明题
    根号7和根号13的正数部分
    绝对值是大问题,a^2-b^2=2009
    化学
    二次函数错题本:y=ax^2+4ax+3
    大阴线空中加油最厉害!比亚迪等几个分析图
    大阴线空中加油最厉害!比亚迪等几个分析图
    09-使用a标签实现锚链接的两种方法
    08-a超链接标签
  • 原文地址:https://www.cnblogs.com/cccc2019fzs/p/12097891.html
Copyright © 2020-2023  润新知