accept 用于从指定套接字的连接队列中取出第一个连接,并返回一个新的套接字用于与客户端进行通信,示例代码如下

#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int accept(int sockfd, struct sockaddr *addr,  socklen_t *addrlen);
int accept4(int sockfd, struct sockaddr *addr, socklen_t *addrlen, int flags);

其中的参数解释如下:
·int sockfd :处于监听状态的套接字。
·struct sockaddr*addr :用于保存对端的地址信息。
·socklen_t*addrlen :是一个输入输出值。调用者将其初始化为 addr 缓存的大小, accept 返回时,会
将其设置为 addr 的大小。
·int flags :是新引入的系统调用 accept4 的标志位;目前支持 SOCK_NONBLOCK 和
SOCK_CLOEXEC

*
* For accept, we attempt to create a new socket, set up the link
* with the client, wake up the client, then return the new
* connected fd. We collect the address of the connector in kernel
* space and move it to user at the very end. This is unclean because
* we open the socket then return an error.
*
* 1003.1g adds the ability to recvmsg() to query connection pending
* status to recvmsg. We need to add that support in a way thats
* clean when we restucture accept also.
*/ 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_CLOEXEC | 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;
newsock = sock_alloc();
if (!newsock)
goto out_put;
/* 新socket的类型 新socket的socket层操作 */
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.
Socekt层协议,对SOCK_STREAM来说是inet_stream_ops,它的引用计数加一
*/
__module_get(newsock->ops->owner);
/*申请一个新的文件描述符*/
newfd = get_unused_fd_flags(flags);
if (unlikely(newfd < 0)) {
err = newfd;
sock_release(newsock);
goto out_put;
}
/* 为socket创建一个对应的file结构sock->file,返回fd */
newfile = sock_alloc_file(newsock, flags, sock->sk->sk_prot_creator->name);
if (IS_ERR(newfile)) {
err = PTR_ERR(newfile);
put_unused_fd(newfd);
sock_release(newsock);
goto out_put;
} err = security_socket_accept(sock, newsock);
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, false);
if (err < 0)
goto out_fd; if (upeer_sockaddr) {/* 如果要保存对端地址 */
if (newsock->ops->getname(newsock, (struct sockaddr *)&address,
&len, 2) < 0) {
err = -ECONNABORTED;
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;
}
static inline void sock_graft(struct sock *sk, struct socket *parent)
{
write_lock_bh(&sk->sk_callback_lock);
sk->sk_wq = parent->wq;/指向parent 等待队列
parent->sk = sk;
sk_set_socket(sk, parent);
sk->sk_uid = SOCK_INODE(parent)->i_uid;
security_sock_graft(sk, parent);
write_unlock_bh(&sk->sk_callback_lock);
}

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

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

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

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

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

    -.2 用户设置了超时时间,超时后会退出。

1、当前全队列中有socket,则accept()直接返回对应的fd。
2、如果当前全队列中没有socket,则如果当前socket是阻塞的,直接睡眠。
3、如果当前全队列中没有socket,如果非阻塞,就直接返回-EAGAIN。
4、如果是阻塞的listenfd,需要将当前进程挂在listenfd对应socket的等待队列里面,当前进程让出cpu,并且等待唤醒

/*
* This will accept the next outstanding connection.
*/
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; 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 */
if (reqsk_queue_empty(queue)) {//发没有现ESTABLISHED状态的连接请求块
/* 等待超时时间,如果是非阻塞则为0 */
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;
}
//走到这里,说明全队列中有socket,直接取出来
req = reqsk_queue_remove(queue, sk);
newsk = req->sk; 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);
}
out:
release_sock(sk);
if (req)
reqsk_put(req);
return newsk;
out_err:
newsk = NULL;
req = NULL;
*err = error;
goto out;
}
/*
* Wait for an incoming connection, avoid race conditions. This must be called
* with the socket locked.
*/
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; /*
* True wake-one mechanism for incoming connections: only
* one process gets woken up, not the 'whole herd'.
* Since we do not 'race & poll' for established sockets
* anymore, the common case will execute the loop only once.
*
* Subtle issue: "add_wait_queue_exclusive()" will be added
* after any current non-exclusive waiters, and we know that
* it will always _stay_ after any new non-exclusive waiters
* because all non-exclusive waiters are added at the
* beginning of the wait-queue. As such, it's ok to "drop"
* our exclusiveness temporarily when we get woken up without
* having to remove and re-insert us on the wait queue.
*/
for (;;) {
//prepare_to_wait_exclusive很重要,把 wait 挂到当前sk的等待队列里面。
prepare_to_wait_exclusive(sk_sleep(sk), &wait,
TASK_INTERRUPTIBLE);
release_sock(sk);
//icsk_accept_queue是全队列
if (reqsk_queue_empty(&icsk->icsk_accept_queue))
timeo = schedule_timeout(timeo);//阻塞情况下,只有主动唤醒当前进程,才会继续执行。
lock_sock(sk);
err = 0; //如果阻塞且非超时的情况从schedule_timeout返回,那么必然是全队列有值了。
if (!reqsk_queue_empty(&icsk->icsk_accept_queue))
break;//这个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;
}

prepare_to_wait_exclusive函数很重要,把当前上下文加到listenfd对应的socket等待队列里面,如果是多进程,那么listenfd对应的socket等待队列里面会有多个进程的上下文

多进程accept,不考虑resuseport,那么多进程accept只会出现在父子进程同时accept的情况,那么上文也说过,prepare_to_wait_exclusive函数会被当前进程上下文加入到listenfd等待队列里面,所以父子进程的上下文都会加入到socket的等待队列里面。核心问题就是这么唤醒,我们可以相当,所谓的惊群,就是把等待队里里面的所有进程都唤醒。

int tcp_v4_do_rcv(struct sock *sk, struct sk_buff *skb)
{
struct sock *rsk; ......
if (sk->sk_state == TCP_LISTEN) {
struct sock *nsk = tcp_v4_hnd_req(sk, skb);
if (!nsk)
goto discard; if (nsk != sk) {
sock_rps_save_rxhash(nsk, skb);
//当三次握手客户端的ack到来时,会走tcp_child_process这里
if (tcp_child_process(sk, nsk, skb)) {
rsk = nsk;
goto reset;
}
return 0;
}
}
......
} int tcp_child_process(struct sock *parent, struct sock *child,
struct sk_buff *skb)
{
int ret = 0;
int state = child->sk_state; if (!sock_owned_by_user(child)) {
ret = tcp_rcv_state_process(child, skb, tcp_hdr(skb),
skb->len);
/* Wakeup parent, send SIGIO */
if (state == TCP_SYN_RECV && child->sk_state != state)
parent->sk_data_ready(parent, 0);//唤醒 在accept的进程,调用 sock_def_readable
} else {
/* Alas, it is possible again, because we do lookup
* in main socket hash table and lock on listening
* socket does not protect us more.
*/
__sk_add_backlog(child, skb);
} bh_unlock_sock(child);
sock_put(child);
return ret;
} static void sock_def_readable(struct sock *sk, int len)
{
struct socket_wq *wq; rcu_read_lock();
wq = rcu_dereference(sk->sk_wq);
//显然,我们在accept的时候调用了`prepare_to_wait_exclusive`加入了队列,故唤醒靠 wake_up_interruptible_sync_poll
if (wq_has_sleeper(wq))
wake_up_interruptible_sync_poll(&wq->wait, POLLIN | POLLPRI |
POLLRDNORM | POLLRDBAND);
sk_wake_async(sk, SOCK_WAKE_WAITD, POLL_IN);
rcu_read_unlock();
}

 多个进程accept的时候,内核只会唤醒1个等待的进程,且唤醒的逻辑是FIFO

其代码逻辑如下

static void __wake_up_common(wait_queue_head_t *q, unsigned int mode,
int nr_exclusive, int wake_flags, void *key)
{
wait_queue_t *curr, *next; list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
unsigned flags = curr->flags; //prepare_to_wait_exclusive时候,flags是WQ_FLAG_EXCLUSIVE,入参nr_exclusive是1,所以只执行一次就break了。
if (curr->func(curr, mode, wake_flags, key) &&
(flags & WQ_FLAG_EXCLUSIVE) && !--nr_exclusive)
break;
}
}

Socket accept 简要分析的更多相关文章

  1. Socket listen 简要分析

    #include <sys/types.h> /* See NOTES */#include <sys/socket.h>int listen(int sockfd, int ...

  2. confluence中org.apache.tomcat.util.net.NioEndpoint$Acceptor.run Socket accept failed的解决方法

    https://www.cnblogs.com/heyongboke/p/9806396.html 1.confluence中报错信息如下: 严重 [http-nio-18090-Acceptor-0 ...

  3. RxJava && Agera 从源码简要分析基本调用流程(2)

    版权声明:本文由晋中望原创文章,转载请注明出处: 文章原文链接:https://www.qcloud.com/community/article/124 来源:腾云阁 https://www.qclo ...

  4. hidraw设备简要分析

    关键词:hid.hidraw.usbhid.hidp等等. 下面首先介绍hidraw设备主要用途,然后简要分析hidraw设备驱动(但是不涉及到相关USB/Bluwtooth驱动),最后分析用户空间接 ...

  5. GJM : 中断被Socket.Accept阻塞的线程

    原帖地址:http://blog.csdn.net/kingfox/article/details/7233350 原文作者:狐帝 刚刚学习C#,在编写一个网络通讯的程序的时候,遇到了点麻烦.监听代码 ...

  6. Socket accept failed

    启动tomcat显示如下错误: java.net.SocketException: select failed at java.net.PlainSocketImpl.socketAccept(Nat ...

  7. Activity源码简要分析总结

    Activity源码简要分析总结 摘自参考书籍,只列一下结论: 1. Activity的顶层View是DecorView,而我们在onCreate()方法中通过setContentView()设置的V ...

  8. Google发布SSLv3漏洞简要分析报告

    今天上午,Google发布了一份关于SSLv3漏洞的简要分析报告.根据Google的说法,该漏洞贯穿于所有的SSLv3版本中,利用该漏洞,黑客可以通过中间人攻击等类似的方式(只要劫持到的数据加密两端均 ...

  9. 构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(34)-文章发布系统①-简要分析

    原文:构建ASP.NET MVC4+EF5+EasyUI+Unity2.x注入的后台管理系统(34)-文章发布系统①-简要分析 系列目录 最新比较闲,为了学习下Android的开发构建ASP.NET ...

随机推荐

  1. chrome(谷歌)登录失败解决方案

    相信有很多小伙伴和我一样,同步chrome的收藏夹,这样也便于随时可以查看自己收藏的网址.但是同步文件,必须先要登录chrome账号,登录chrome账号时,总是会报黄页,或者一直加载不出来.接下来, ...

  2. BASH提示符颜色、显示返回值,终端标题显示当前目录与正在执行的命令

    BASH的PS1变量控制提示符相关的东西,善用它可以让BASH用起来舒服很多 提示符颜色 提示符显示上一个命令的返回值(exit code),并根据是否0调整颜色 提示符生成的时间(这样就知道上一条命 ...

  3. php中,posix_getpid() 和 getmypid() 有什么不同

    getmypid:windows 和 linux都可以用posix_getpid:仅linux可以用

  4. Flink + 强化学习 搭建实时推荐系统

    如今的推荐系统,对于实时性的要求越来越高,实时推荐的流程大致可以概括为这样: 推荐系统对于用户的请求产生推荐,用户对推荐结果作出反馈 (购买/点击/离开等等),推荐系统再根据用户反馈作出新的推荐.这个 ...

  5. Python函数递归调用

    函数的递归调用: 是函数嵌套调用的一种特殊形式 具体是指: 在调用一个函数的过程中又直接或间接地调用到了本身 # 直接调用本身 def func(): print('我是func') func() f ...

  6. 小C和小派的缠绵爱情——C语言调用Python代码

    我妒忌你的开源,你眼红我的速度,不如我们就在一起吧! --------SJ2050 2019.4.9号更新:实现在未安装python环境的机子上运行调用了python程序的C语言代码! 文章目录 环境 ...

  7. BlockCanary界面卡顿检测

    添加依赖: implementation 'com.github.markzhai:blockcanary-android:1.5.0' 运行后会同时安装检测工具,主要检测UI线程运行卡顿现象 pub ...

  8. 脑桥Brain-Pons

    date: 2014-02-01 15:30:11 updated: 2014-02-01 15:30:11 [一] "2025.7.3.Brain-Pons?Expeiment?Under ...

  9. oracle数据库表空间扩展

    //查看表空间情况 SELECT Upper(F.TABLESPACE_NAME) "表空间名",D.TOT_GROOTTE_MB "表空间大小(M)",D.T ...

  10. Navicat连接远程MySQL8.0数据库

    前言: 如果你有一台服务器,并且安装了Mysql8.0及以上版本数据库.此时想通过本地Navicat软件连接远程服务器上的mysql数据库.那么接下来你就要完成以下准备工作: 登录远程服务器上的数据库 ...