#TCP你學得會# 之 accpet的謎題

    假設咱們如今有這樣一個場景,在一個多線程程序中,主線程建立監聽套接字並阻塞在accept函數上等待新鏈接到來,另一個線程則分別在以下兩個時間點open兩個外部文件:多線程

    1) socket建立成功後,調用accept以前;socket

    2) 調用accept以後(但沒有任何外部鏈接);tcp

    按照預期兩次open函數返回的fd應該是多少呢?函數

    0,1,2是默認保留的,建立的socket fd爲3, accept又沒有新的鏈接能夠接收,那麼咱們兩次open操做的返回值固然應該是4和5咯。this

    這是事實嗎?spa

    實際的結果是這樣滴:線程

$ ls -l /proc/3216/fd
total 0
lrwx------ 1 yyy yyy 64 Mar  3 10:05 0 -> /dev/pts/3
lrwx------ 1 yyy yyy 64 Mar  3 10:05 1 -> /dev/pts/3
lrwx------ 1 yyy yyy 64 Mar  3 10:05 2 -> /dev/pts/3
lrwx------ 1 yyy yyy 64 Mar  3 10:05 3 -> socket:[17906]
lr-x------ 1 yyy yyy 64 Mar  3 10:05 4 -> /home/yyy/project/tcpudp/tcp/tcp_client.c
lr-x------ 1 yyy yyy 64 Mar  3 10:05 6 -> /home/yyy/project/tcpudp/tcp/tcp_server.c

    也就是說,accept以後的open操做返回的不是5,而是6。code

    有沒有很詫異,描述符5去哪裏了?server

    要解答這個問題仍是得去到內核代碼中的syscall accept中一探究竟。幸運的是,一打開這個函數就能看到線索了。資源

SYSCALL_DEFINE4(accept4, int, fd, struct sockaddr __user *, upeer_sockaddr,
        int __user *, upeer_addrlen, int, flags)
{
    ...
    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 (unlikely(IS_ERR(newfile))) {
        err = PTR_ERR(newfile);
        put_unused_fd(newfd);
        sock_release(newsock);
        goto out_put;
    }
    ...
    ...
    err = sock->ops->accept(sock, newsock, sock->file->f_flags);
    if (err < 0)
        goto out_fd;
    ...
    ...
 out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
out_fd:
    fput(newfile);
    put_unused_fd(newfd);
    goto out_put; 
}

    從源碼中能夠看到,一進入accept函數就會分配新的socket結構和文件描述符了,也就是說,在實際的鏈接到來以前相關的資源已經被預留出來了,所以上述實驗結果中第二個open返回了6而不是5,由於5已經做爲預留分配出去了。

    那麼當咱們在非阻塞的socket上使用accept函數也會是這個結果嗎?

    先從源碼入手分析,accept系統調用中會進一步調用sock->ops->accept,與sock->ops->accept對應的函數是inet_stream_ops.accept = inet_accept:

int inet_accept(struct socket *sock, struct socket *newsock, int flags)
{
    struct sock *sk1 = sock->sk;
    int err = -EINVAL;
    struct sock *sk2 = sk1->sk_prot->accept(sk1, flags, &err);

    if (!sk2)
        goto do_err;
    ...
do_err:
	return err;
}

    與sk1->sk_prot->accept對應的是tcp_prot.accept = inet_csk_accept:

/*
 * This will accept the next outstanding connection.
 */
struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)
{
    ...
    /* We need to make sure that this socket is listening,
    * and that it has something pending.
    */
    error = -EINVAL;
    if (sk->sk_state != TCP_LISTEN)
	goto out_err;

    /* Find already established connection */
    if (reqsk_queue_empty(&icsk->icsk_accept_queue)) {
	long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);

    /* If this is a non blocking socket don't sleep */
    error = -EAGAIN;
    if (!timeo)
        goto out_err;

    error = inet_csk_wait_for_connect(sk, timeo);
    if (error)
        goto out_err;
    }

    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;
}
EXPORT_SYMBOL(inet_csk_accept);

static inline long sock_rcvtimeo(const struct sock *sk, bool noblock)
{
    return noblock ? 0 : sk->sk_rcvtimeo;
}

    看起來在非阻塞的socket上調用accept且當前沒有新的鏈接可返回時,會在系統調用返回前將申請到的資源釋放掉,這時先後兩次open的返回值應該就是4和5了。

$ ls -l /proc/5764/fd
total 0
lrwx------ 1 yyy yyy 64 Mar  3 10:55 0 -> /dev/pts/3
lrwx------ 1 yyy yyy 64 Mar  3 10:55 1 -> /dev/pts/3
lrwx------ 1 yyy yyy 64 Mar  3 10:55 2 -> /dev/pts/3
lrwx------ 1 yyy yyy 64 Mar  3 10:55 3 -> socket:[20249]
lr-x------ 1 yyy yyy 64 Mar  3 10:55 4 -> /home/yyy/project/tcpudp/tcp/tcp_client.c
lr-x------ 1 yyy yyy 64 Mar  3 10:55 5 -> /home/yyy/project/tcpudp/tcp/tcp_server.c


    但願下次遇到這個問題時你能很快的想起其中的來龍去脈,而再也不困惑哦。

相關文章
相關標籤/搜索