accept 如何获取新连接

服务端调用 listen 后开始监听,然后死循环调用 accept 接收新的连接请求,将新连接 fd 加到 epoll 等待事件。对于客户端,只需要 connect 即可。linux 如何实现的呢?


函数声明

先看 man 说明,关于 accept 有两个版本,新的 accept4 额外多了一个 flags 参数,可以设置 SOCK_NONBLOCK, SOCK_CLOEXEC. 其实底层都是调用一个 accept4.

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

返回值是新连接的 fd, 参数 addr 如果不为空,则被填充为新连接的地址信息。

源码实现

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;

flags 仅支持 SOCK_CLOEXECSOCK_NONBLOCK, 设置其它位无效报错。


    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;

根据 fd 查找到当前监听 socket

    err = -ENFILE;
    newsock = sock_alloc();
    if (!newsock)
        goto out_put;

    newsock->type = sock->type;
    newsock->ops = sock->ops;

sock_alloc 分配新的 socket 结构体,用于建立新连接

    /*
     * 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;
    }

由于 linux 一切皆文件,所以新的 socket 连接,必然有 fd, file

    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;

accept 回调 inet_stream_ops.inet_accept, 而 inet_accept 最终调用 tcp_prot.inet_csk_accept 核心函数。

    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;
    }

获取新连接的地址信息,如果调用方参数 addr 不为空,则填充。

    /* File flags are not inherited via accept() unlike another OSes. */

    fd_install(newfd, newfile);
    err = newfd;

fd_installfd, file 关联,并添加到当前进程 PCB 打开文件表。

out_put:
    fput_light(sock->file, fput_needed);
out:
    return err;
out_fd:
    fput(newfile);
    put_unused_fd(newfd);
    goto out_put;
}

inet_csk_accept实现

这是核心代码,大致流程是从队列取待建连的 socket, 队列如果为空,那么跟据是否设置 O_NONBLOCK 判断是否等待。

struct sock *inet_csk_accept(struct sock *sk, int flags, int *err, bool kern)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    struct request_sock_queue *queue = &icsk->icsk_accept_queue;
    struct request_sock *req;
    struct sock *newsk;
    int error;

icsk_accept_queuelisten 时初始化,存放处于 SYN_RECV 状态等待建连的 socket,也就是说 inet_csk_accept 是消费者。那么生产者是内核的哪个模块呢?先挖大坑


    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)
        goto out_err;

当前状态必须处于 TCP_LISTEN

    /* Find already established connection */
    if (reqsk_queue_empty(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;
    }

如果设置了 O_NONBLOCK 非阻塞,队列没有数据直接返回。否则 inet_csk_wait_for_connect 等待新连接到来,并一直阻塞在这里,直到 timeo 超时。

    req = reqsk_queue_remove(queue, sk);
    newsk = req->sk;

如果逻辑走到这里,那么 icsk_accept_queue 队列一定有请求到来

    if (sk->sk_protocol == IPPROTO_TCP &&
        tcp_rsk(req)->tfo_listener) {
        spin_lock_bh(&queue->fastopenq.lock);
        if (tcp_rsk(req)->tfo_listener) {
            /* We are still waiting for the final ACK from 3WHS
             * so can't free req now. Instead, we set req->sk to
             * NULL to signify that the child socket is taken
             * so reqsk_fastopen_remove() will free the req
             * when 3WHS finishes (or is aborted).
             */
            req->sk = NULL;
            req = NULL;
        }
        spin_unlock_bh(&queue->fastopenq.lock);
    }

这里涉及 TFO,暂时忽略

out:
    release_sock(sk);
    if (req)
        reqsk_put(req);
    return newsk;
out_err:
    newsk = NULL;
    req = NULL;
    *err = error;
    goto out;
}

inet_csk_wait_for_connect实现

这个函数不长,但是涉及内核调度,超时。

static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
{
    struct inet_connection_sock *icsk = inet_csk(sk);
    DEFINE_WAIT(wait);
    int err;
    for (;;) {
        prepare_to_wait_exclusive(sk_sleep(sk), &wait,
                      TASK_INTERRUPTIBLE);
        release_sock(sk);
        if (reqsk_queue_empty(&icsk->icsk_accept_queue))
            timeo = schedule_timeout(timeo);
        sched_annotate_sleep();
        lock_sock(sk);
        err = 0;
        if (!reqsk_queue_empty(&icsk->icsk_accept_queue))
            break;
        err = -EINVAL;
        if (sk->sk_state != TCP_LISTEN)
            break;
        err = sock_intr_errno(timeo);
        if (signal_pending(current))
            break;
        err = -EAGAIN;
        if (!timeo)
            break;
    }
    finish_wait(sk_sleep(sk), &wait);
    return err;
}

1.首先定义 wait, 这是一个 wait_queue_entry 结构体,DEFINE_WAIT 是宏,看下展开式:

#define DEFINE_WAIT_FUNC(name, function)                    \
    struct wait_queue_entry name = {                    \
        .private    = current,                  \
        .func       = function,                 \
        .entry      = LIST_HEAD_INIT((name).entry),         \
    }

#define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)

这块很好理解,current 是当前进程,定义了一个 wait entry

2.每个 struct sock 有一个 socket_wq 成员,等待 socket 事件的进程会放到 socket_wq.wait_queue_head_t 链表中,等待唤醒。sk_sleep(sk) 获取链表,prepare_to_wait_exclusive 将当前进程注册到这个链表。

3.schedule_timeout 注册超时事件,出让 cpu, 将控制权交给内核,此时程序阻塞在这里。

4.程序被唤醒后,检查 icsk_accept_queue 队列是否有请求,没有的话,并且 timeo 还未到期,循环继续调度 prepare_to_wait_exclusive

5.如果此时队列有数据,或是超时到期,那么返回错误码。

小结

accept 至此大致看完。挖了一个大坑,当有客户端发起 SYN 请求,内核是如何处理,并写入 icsk_accept_queue 队列?

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容

  • 本次文章是大伟哥会山东老家的故事。 等待补充……
    大伟传说阅读 305评论 0 0
  • 抬起笔,我也不知道该写什么,是写我的回忆,还是写一下你现在所走向的学习道路。思索再三,写一个方向,你试一下能走...
    文门阅读 552评论 1 1
  • 最近读了七年就是一辈子这本书,受益匪浅,用了RIA便签读书法拆了其中9句话,与大家分享一下。 1.别太在意表现,更...
    流云戏水阅读 517评论 0 1
  • 六书 汉字造字方法 更多义项 汉代学者把汉字的构成和使用方式归纳成六种类型,总称六书。六书指象形、指事、会意、形声...
    无忧qimiao阅读 596评论 0 1
  • 小秋是个很漂亮的姑娘,浓密的长发披在肩上,大大的眼睛炯炯有神,殷桃小嘴可爱极了,肤白貌美就是专门形容她的。 她生在...
    81073c7be039阅读 487评论 0 1