深入理解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()

原文地址:https://www.cnblogs.com/qyf2199/p/12102373.html