zoukankan      html  css  js  c++  java
  • 深入理解TCP协议及其源代码

    深入理解TCP协议及其源代码——connect及bind、listen、accept背后的3次握手

     

    一、TCP协议 理论分析

    1.1 TCP数据包格式:

    1.2 TCP三次握手:

    • 第一次握手:A客户进程向B发出连接请求报文段,(首部的同步位SYN=1,初始序号seq=x),(SYN=1的报文段不能携带数据)但要消耗掉一个序号,此时TCP客户进程进入SYN-SENT(同步已发送)状态。
    • 第二次握手:B收到连接请求报文段后,如同意建立连接,则向A发送确认,在确认报文段中(SYN=1,ACK=1,确认号ack=x+1,初始序号seq=y),TCP服务器进程进入SYN-RCVD(同步收到)状态;
    • 第三次握手:TCP客户进程收到B的确认后,要向B给出确认报文段(ACK=1,确认号ack=y+1,序号seq=x+1)(初始为seq=x,第二个报文段所以要+1),ACK报文段可以携带数据,不携带数据则不消耗序号。TCP连接已经建立,A进入ESTABLISHED(已建立连接)。
      当B收到A的确认后,也进入ESTABLISHED状态。

    对于服务端的流程——类似于接电话过程

    socket()[找到一个可以通话的手机]

    ----->bind()[插入一个固定号码]

    ------>listen()[随时准备接听]

    -------> accept

    ------->recv()------->send()------>close();

    对于客户端的主要流程----类似于打电话过程

    socket()----->connect()

    ------>recv/read/send------>close()

    三个接口函数介绍
    1.  connect()函数:

      是一个阻塞函数通过TCP三次握手建立连接客户端主动连接服务器,通过TCP三次握手通知Linux内核自动完成TCP 三次握手连接 如果连接成功为0 失败返回值-1,一般的情况下客户端的connect函数,默认是阻塞行为,直到三次握手阶段成功为止。

    2.  服务器端的 listen() 函数:

      不是一个阻塞函数: 功能:将套接字和套接字对应队列的长度告诉Linux内核。他是被动连接的一直监听来自不同客户端的请求, listen函数将socketfd 变成被动的连接监听socket 其中参数backlog作用设置内核中队列的长度 。注:listen的函数形式int listen(int sockfd, int backlog);  backlog代表listen队列的长度。

    3.  accept() 函数阻塞:

      从处于established 状态的队列中取出完成的连接,当队列中没有完成连接时候,会形成阻塞,直到取出队列中已完成连接的用户连接为止(Accept默认会阻塞进程,直到有一个客户连接建立后返回)。

    【 对应关系】三个函数与TCP三次握手之间的对应关系是这样的:

    服务器调用listen进行监听
    客户端调用connect来发送syn报文
    服务器协议栈负责三次握手的交互过程。连接建立后,往listen队列中添加一个成功的连接,直到队列的最大长度。
    服务器调用accept从listen队列中取出一条成功的tcp连接,listen队列中的连接个数就少一个

     

    二、源代码阅读&分析

    在建立TCP连接过程中:

    服务端执行:socket() --> bind() --> listen() --> accept()

    客户端执行:socket() --> connect()

    下面是逐个展示源代码:

    socket()函数:

    int __sys_socket(int family, int type, int protocol)
    {
            int retval;
            struct socket *sock;
            int flags;
    
            /* Check the SOCK_* constants for consistency.  */
            BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
            BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
            BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
            BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);
    
            flags = type & ~SOCK_TYPE_MASK;
            if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
                    return -EINVAL;
            type &= SOCK_TYPE_MASK;
    
            if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
                    flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
    
            retval = sock_create(family, type, protocol, &sock);
            if (retval < 0)
                    return retval;
    
            return sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
    }
    
    SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
    {
            return __sys_socket(family, type, protocol);
    }

    bind()函数:

    int __sys_bind(int fd, struct sockaddr __user *umyaddr, int addrlen)
    {
            struct socket *sock;
            struct sockaddr_storage address;
            int err, fput_needed;
    
            sock = sockfd_lookup_light(fd, &err, &fput_needed);
            if (sock) {
                    err = move_addr_to_kernel(umyaddr, addrlen, &address);
                    if (!err) {
                            err = security_socket_bind(sock,
                                                       (struct sockaddr *)&address,
                                                       addrlen);
                            if (!err)
                                    err = sock->ops->bind(sock,
                                                          (struct sockaddr *)
                                                          &address, addrlen);
                    }
                    fput_light(sock->file, fput_needed);
            }
            return err;
    }
    
    SYSCALL_DEFINE3(bind, int, fd, struct sockaddr __user *, umyaddr, int, addrlen)
    {
            return __sys_bind(fd, umyaddr, addrlen);
    }

    listen()函数:

    int __sys_listen(int fd, int backlog)
    {
            struct socket *sock;
            int err, fput_needed;
            int somaxconn;
    
            sock = sockfd_lookup_light(fd, &err, &fput_needed);
            if (sock) {
                    somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
                    if ((unsigned int)backlog > somaxconn)
                            backlog = somaxconn;
    
                    err = security_socket_listen(sock, backlog);
                    if (!err)
                            err = sock->ops->listen(sock, backlog);
    
                    fput_light(sock->file, fput_needed);
            }
            return err;
    }
    
    SYSCALL_DEFINE2(listen, int, fd, int, backlog)
    {
            return __sys_listen(fd, backlog);
    }

    accept()函数:

    int __sys_accept4(int fd, struct sockaddr __user *upeer_sockaddr,
                      int __user *upeer_addrlen, int flags)
    {
            struct socket *sock, *newsock;
            struct file *newfile;
            int err, len, newfd, fput_needed;
            struct sockaddr_storage address;
    
            if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
                    return -EINVAL;
    
            if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
                    flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
    
            sock = sockfd_lookup_light(fd, &err, &fput_needed);
            if (!sock)
                    goto out;
    
            err = -ENFILE;
            newsock = sock_alloc();
            if (!newsock)
                    goto out_put;
    
            newsock->type = sock->type;
            newsock->ops = sock->ops;
    
            /*
             * We don't need try_module_get here, as the listening socket (sock)
             * has the protocol module (sock->ops->owner) held.
             */
            __module_get(newsock->ops->owner);
    
            newfd = get_unused_fd_flags(flags);
            if (unlikely(newfd < 0)) {
                    err = newfd;
                    sock_release(newsock);
                    goto out_put;
            }
            newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);
            if (IS_ERR(newfile)) {
                    err = PTR_ERR(newfile);
                    put_unused_fd(newfd);
                    goto out_put;
            }
    
            err = security_socket_accept(sock, newsock);
            if (err)
                    goto out_fd;
    
            err = sock->ops->accept(sock, newsock, sock->file->f_flags, false);
            if (err < 0)
                    goto out_fd;
    
               if (upeer_sockaddr) {
                    len = newsock->ops->getname(newsock,
                                            (struct sockaddr *)&address, 2);
                    if (len < 0) {
                            err = -ECONNABORTED;
                            goto out_fd;
                    }
                    err = move_addr_to_user(&address,
                                            len, upeer_sockaddr, upeer_addrlen);
                    if (err < 0)
                            goto out_fd;
            }
    
            /* File flags are not inherited via accept() unlike another OSes. */
    
            fd_install(newfd, newfile);
            err = newfd;
    
    out_put:
            fput_light(sock->file, fput_needed);
    out:
            return err;
    out_fd:
            fput(newfile);
             put_unused_fd(newfd);
            goto out_put;
    }
    
    SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
                    int __user *, upeer_addrlen, int, flags)
    {
            return __sys_accept4(fd, upeer_sockaddr, upeer_addrlen, flags);
    }
    
    SYSCALL_DEFINE3(accept, int, fd, struct sockaddr __user *, upeer_sockaddr,
                    int __user *, upeer_addrlen)
    {
            return __sys_accept4(fd, upeer_sockaddr, upeer_addrlen, 0);
    }

    客户端调用connect去连接服务端:

    SYSCALL_DEFINE3(connect, int, fd, struct sockaddr __user *, uservaddr,
            int, addrlen)
    {
        return __sys_connect(fd, uservaddr, addrlen);
    }
    int __sys_connect_file(struct file *file, struct sockaddr_storage *address,
                   int addrlen, int file_flags)
    {
        struct socket *sock;
        int err;
       //得到socket对象
        sock = sock_from_file(file, &err);
        if (!sock)
            goto out;
    
        err =
            security_socket_connect(sock, (struct sockaddr *)address, addrlen);
        if (err)
            goto out;
       //对于流式套接字,sock->ops为 inet_stream_ops --> inet_stream_connect
       //对于数据报套接字,sock->ops为 inet_dgram_ops --> inet_dgram_connect
        err = sock->ops->connect(sock, (struct sockaddr *)address, addrlen,
                     sock->file->f_flags | file_flags);
    out:
        return err;
    }
    
    int __sys_connect(int fd, struct sockaddr __user *uservaddr, int addrlen)
    {
        int ret = -EBADF;
        struct fd f;
    
        f = fdget(fd);
        if (f.file) {
            struct sockaddr_storage address;
         //将地址对象从用户空间拷贝到内核空间
            ret = move_addr_to_kernel(uservaddr, addrlen, &address);
            if (!ret)
                ret = __sys_connect_file(f.file, &address, addrlen, 0);
            if (f.flags)
                fput(f.file);
        }
    
        return ret;
    }

    三、gdb运行跟踪

    在上面所示5个函数的地方打断点:

     打了5个断点,后面每次中断后查看状况,然后用 c 就可以继续执行

    服务端执行:socket() --> bind() --> listen() --> accept()

    客户端执行:socket() --> connect()

  • 相关阅读:
    Sql中使用With创建多张临时表
    sql(join on 和where的执行顺序)
    什么是正则化
    ETL讲解(转)
    MySQL等 SQL语句在线练习
    Sublime text 3 --html
    Sublime text 3 搭建Python3 IDE
    地区列车经过查询
    Lasso回归算法: 坐标轴下降法与最小角回归法小结
    完全卸载VMware
  • 原文地址:https://www.cnblogs.com/qyf2199/p/12102373.html
Copyright © 2011-2022 走看看