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()

  • 相关阅读:
    (转)CMD指令大全
    [转]测试人员要像医生一样把要测试的程序当自己的病人一样看待一样检测!
    robotFramework学习笔记
    mysql数据库转换成数据字典的方法(整理)
    【转】PHP SQL防注入的一些经验
    性能测试基础知识(概念)
    iOS--MJRefresh的使用 上拉刷新和下拉加载
    IOS----UIScrollerView的使用
    iOS -- UILabel的高度自适应
    第二章 图像形成
  • 原文地址:https://www.cnblogs.com/qyf2199/p/12102373.html
Copyright © 2011-2022 走看看