zoukankan      html  css  js  c++  java
  • Socket层实现系列 — accept()的实现(一)

    本文主要介绍了accept()的系统调用、Socket层实现,以及TCP层实现。

    内核版本:3.6

    Author:zhangskd @ csdn blog

    应用层

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);

    It extracts the first connection request on the queue of pending connections (backlog), creates a new

    connected socket, and returns a new file descriptor referring to that socket.

    If no pending connections are present on the queue, and the socket is not marked as non-blocking,

    accept() blocks the caller until a connection is present. If the socket is marked non-blocking and no

    pending connections are present on the queue, accept() fails with the error EAGAIN.

    在建立好接收队列以后,服务器就调用accept(),然后睡眠直到有客户端的连接请求到达。

    addr用于保存客户端的地址。

    系统调用

    accept()是由glibc提供的,声明位于include/sys/socket.h中,实现位于sysdeps/mach/hurd/accept.c中,

    主要是用来从用户空间进入名为sys_socketcall的系统调用,并传递参数。sys_socketcall()实际上是所有

    socket函数进入内核空间的共同入口。

    在sys_socketcall()中会调用sys_accept4()。

    SYSCALL_DEFINE2(socketcall, int, call, unsigned long __user *, args)
    {
        ...
        switch(call) {
            ...
            case SYS_ACCEPT:
                err = sys_accpet4(a0, (struct sockaddr __user *)a1, (int __user *)a[2], 0);
                break;
            ...
        }
        return err;
    }
    

    经过了socket层的总入口sys_socketcall(),现在进入sys_accpet4()。

    SYSCALL_DEFINE4(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_CLOSEXEC | SOCK_NONBLOCK))
            return -EINVAL;
        
        if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
            flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
    
        /* 通过文件描述符fd,找到对应的socket。
         * 以fd为索引从当前进程的文件描述符表files_struct中找到对应的file实例,
         * 然后从file实例的private_data成员中获取socket实例。
         */
        sock = sockfd_lookup_light(fd, &err, &fput_needed);
        if (! sock)
            goto out;
    
        err = -ENFILE; /* File table overflow */
        newsock = sock_alloc(); /* 创建一个新的inode和socket */
        if (! newsock)
            goto out_put;
    
        newsock->type = sock->type; /* 新socket的类型 */
        newsock->ops = sock->ops; /* 新socket的socket层操作 */
    
        /* We don't need try_module_get here, as the listening socket (sock)
         * has the protocol module (sock->ops->owner) held.
         * Socekt层协议,对SOCK_STREAM来说是inet_stream_ops,它的引用计数加一。
         */
        __module_get(newsock->ops->owner);    
    
        /* 为socket创建一个对应的file结构sock->file,返回fd */
        newfd = sock_alloc_file(newsock, &newfile, flags);
        if (unlikely(newfd < 0)) {
            err = newfd;
            sock_release(newsock);
            goto out_put;
        }
    
        err = security_socket_accept(sock, newsock); /* SELinux相关 */
        if (err)
            goto out_fd;
    
        /* SOCKET层的操作函数,如果是SOCK_STREAM,proto_ops为inet_stream_ops,
         * 接下来调用inet_accept()。
         */
        err = sock->ops->accept(sock, newsock, sock->file->f_flags);
        if (err < 0)
            goto out_fd;
    
        if (upeer_sockaddr) { /* 如果要保存对端地址 */
            /* 获取对端的地址,以及地址的长度 */
            if (newsock->ops->getname(newsock, (struct sockaddr *)&address, &len, 2) < 0) {
                err = -ECONNABORTED; /* Software caused connection abort */
                goto out_fd;
            }
    
            /* 把内核空间的socket地址复制到用户空间 */
            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.
         * 以newfd为索引,把newfile加入当前进程的文件描述符表files_struct中。
         */
        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;
    }
    

    sys_accept4()主要做了:

    1. 创建了一个新的socket和inode,以及它所对应的fd、file。

    2. 调用Socket层操作函数inet_accept()。

    3. 保存对端地址到指定的用户空间地址。

    Socket层

    SOCK_STREAM套接口的Socket层操作函数集实例为inet_stream_ops,连接接收函数为inet_accept()。

    const struct proto_ops inet_stream_ops = {
        .family = PF_INET,
        .owner = THIS_MODULE,
        ...
        .accept = inet_accept,
        ...
    };

    inet_accept()主要做了:

    1. 调用TCP层的操作函数,获取已建立的连接sock。

    2. 把新socket和sock关联起来。

    3. 把新socket的状态设为SS_CONNECTED。

    至此,新socket的各个字段都赋值完毕了。

    /* Accept a pending connection. 
     * The TCP layer now gives BSD semantics.
     */
    int inet_accept(struct socket *sock, struct socket *newsock, int flags)
    {
    
        struct sock *sk1 = sock->sk;
        int err = -EINVAL;
    
        /* 如果使用的是TCP,则sk_prot为tcp_prot,accept为inet_csk_accept()
         * 获取新连接的sock。
         */
        struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);
        if (! sk2)
            goto do_err;
    
        lock_sock(sk2);
    
        sock_rps_record_flow(sk2); /* RPS补丁 */
        WARN_ON(! ((1 << sk2->sk_state) & (TCPF_ESTABLISHED | TCPF_CLOSE_WAIT | TCPF_CLOSE)));
    
        sock_graft(sk2, newsock); /* 把sock和socket嫁接起来,让它们能相互索引 */
        newsock->state = SS_CONNECTED; /* 把新socket的状态设为已连接 */
    
        err = 0;
        release_sock(sk2);
    
    do_err:
        return err;
    }
    
    /*
     * struct callback_head - callback structure for use with RCU and task_work
     * @next: next update requests in a list
     * @func: actual update function to call after the grace period.
     */
    struct callback_head {
        struct callback_head *next;
        void (*func) (struct callback_head *head);
    };
    #define rcu_head callback_head
    
    /* 把sock和socket嫁接起来。*/
    static inline void sock_graft(struct sock *sk, struct socket *parent)
    {
        write_lock_bh(&sk->sk_callback_lock);
    
        sk->sk_wq = parent->wq; /* 等待队列 */
        parent->sk = sk;
        sk_set_socket(sk, parent);
        security_sock_graft(sk, parent);
    
        write_unlock_bh(&sk->sk_callback_lock);
    }
    
    static inline void sk_set_socket(struct sock *sk, struct socket *sock)
    {
        sk_tx_queue_clear(sk);
        sk->sk_socket = sock;
    }
    

    TCP层实现

    SOCK_STREAM套接口的TCP层操作函数集实例为tcp_prot,其中连接接收函数为inet_csk_accept()。

    struct proto tcp_prot = {
        .name = "TCP",
        .owner = THIS_MODULE,
        ...
        .accept = inet_csk_accept,
        ...
    };
    

    inet_csk_accept()用于从backlog队列(全连接队列)中取出一个ESTABLISHED状态的连接请求块,返回它所对应的连接sock。

    1. 非阻塞的,且当前没有已建立的连接,则直接退出,返回-EAGAIN。

    2. 阻塞的,且当前没有已建立的连接:

        2.1 用户没有设置超时时间,则无限期阻塞。

        2.2 用户设置了超时时间,超时后会退出。

    /* This will accept the next outstanding connection. */
    struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
    {
        struct inet_connection_sock *icsk = inet_csk(sk);
        struct sock *newsk;
        int error;
    
        lock_sock(sk);
    
        /* We need to make sure that this socket is listening,
         * and that it has something pending.
         */
        error = -EINVAL;
    
        if (sk->sk_state != TCP_LISTEN) /* socket必须处于监听状态 */
            goto out_err;
    
        /* Find already established connection.
         * 发没有现ESTABLISHED状态的连接请求块。
         */
        if (reqsk_queue_empty(&icsk->icsk_accept_queue)) {
    
            /* 等待超时时间,如果是非阻塞则为0 */
            long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK); 
    
            /* If this is a non blocking socket don't sleep */
            error = -EAGAIN; /* Try again */
    
            if (! timeo) /* 如果是非阻塞的,则直接退出 */
                goto out_err;
    
            /* 阻塞等待,直到有全连接。如果用户有设置等待超时时间,超时后会退出 */
            error = inet_csk_wait_for_connect(sk, timeo);
    
            if (error)
                goto out_err;
        }
    
        /* 获取新连接的sock,释放连接控制块 */
        newsk = reqsk_queue_get_child(&icsk->icsk_accept_queue, sk);
        WARN_ON(newsk->sk_state == TCP_SYN_RECV);
    
    out:
        release_sock(sk);
        return newsk;
    
    out err:
        newsk = NULL;
        *err = error;
        goto out;    
    }
    

    检查ESTABLISHED状态的连接请求块队列是否为空。

    static inline int reqsk_queue_empty(struct request_sock_queue *queue)
    {
        return queue->rskq_accept_head == NULL;
    } 
    
    static inline long sock_rcvtimeo(const struct sock *sk, bool noblock)
    {
        return noblock ? 0 : sk->sk_rcvtimeo; /* accept()超时时间 */
    }

    从backlog队列(全连接队列)中取出一个ESTABLISHED状态的连接请求块,返回它所对应的连接sock。

    同时更新backlog队列的全连接数,释放取出的连接控制块。

    static inline struct sock *reqsk_queue_get_child(struct request_sock_queue *queue, 
                                                     struct sock *parent)
    {
        /* 从全连接队列中,取出第一个ESTABLISHED状态的连接请求块 */
        struct request_sock *req = reqsk_queue_remove(queue);
        struct sock *child = req->sk; /* 一个已建立的连接 */
        
        WARN_ON(child == NULL);
    
        sk_acceptq_removed(parent); /* 当前backlog队列的全连接数减一 */
        __reqsk_free(req); /* 释放取出的连接请求控制块 */
    
        return child;
    }
    
    static inline struct request_sock *reqsk_queue_remove(struct request_sock_queue *queue)
    {
        struct request_sock *req = queue->rskq_accept_head; /* 第一个ESTABLISHED状态的连接请求块 */
        WARN_ON(req == NULL);
    
        queue->rskq_accept_head = req->dl_next;
    
        if (queue->rskq_accept_head == NULL)
            queue->rskq_accept_tail = NULL;
    
        return req;
    }
    
    static inline void sk_acceptq_removed(struct sock *sk)
    {
        sk->sk_ack_backlog--; /* 全连接的数量减一 */
    }
    
    /* 释放连接请求控制块 */
    static inline void __reqsk_free(struct request_sock *req)
    {
        kmem_cache_free(req->rsk_ops->slab, req);
    }
    
  • 相关阅读:
    AJAX异步传输——以php文件传输为例
    js控制json生成菜单——自制菜单(一)
    vs2010中关于HTML控件与服务器控件分别和js函数混合使用的问题
    SQL数据库连接到服务器出错——无法连接到XXX
    PHP错误:Namespace declaration statement has to be the very first statement in the script
    【LeetCode】19. Remove Nth Node From End of List
    【LeetCode】14. Longest Common Prefix
    【LeetCode】38. Count and Say
    【LeetCode】242. Valid Anagram
    【LeetCode】387. First Unique Character in a String
  • 原文地址:https://www.cnblogs.com/aiwz/p/6333319.html
Copyright © 2011-2022 走看看