linux内核线程socket,从Linux源码看Socket(TCP)的accept

从Linux源码看Socket(TCP)的accept

前言

笔者一直以为若是能知道从应用到框架再到操做系统的每一处代码,是一件Exciting的事情。 今天笔者就从Linux源码的角度看下Server端的Socket在进行Accept的时候到底作了哪些事情(基于Linux 3.10内核)。html

一个最简单的Server端例子

众所周知,一个Server端Socket的创建,须要socket、bind、listen、accept四个步骤。

今天,笔者就聚焦于accept。

503b3061bfad875e16916786a384c022.png

代码以下:react

void start_server(){

// server fd

int sockfd_server;

// accept fd

int sockfd;

int call_err;

struct sockaddr_in sock_addr;

......

call_err=bind(sockfd_server,(struct sockaddr*)(&sock_addr),sizeof(sock_addr));

......

call_err=listen(sockfd_server,MAX_BACK_LOG);

......

while(1){

struct sockaddr_in* s_addr_client = mem_alloc(sizeof(struct sockaddr_in));

int client_length = sizeof(*s_addr_client);

// 这边就是咱们今天的聚焦点accept

sockfd = accept(sockfd_server,(struct sockaddr_ *)(s_addr_client),(socklen_t *)&(client_length));

if(sockfd == -1){

printf("Accept error!\n");

continue;

}

process_connection(sockfd,(struct sockaddr_in*)(&s_addr_client));

}

}

首先咱们经过socket系统调用建立了一个Socket,其中指定了SOCK_STREAM,并且最后一个参数为0,也就是创建了一个一般全部的TCP Socket。在这里,咱们直接给出TCP Socket所对应的ops也就是操做函数。

ffb85785590c2721024c70f7d723d805.pnglinux

accept系统调用

好了,咱们直接进入accept系统调用吧。多线程

#include

// 成功,返回表明新链接的描述符,错误返回-1,同时错误码设置在errno

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

// 注意,实际上Linux还有个accept扩展accept4:

// 额外添加的flags参数能够为新链接描述符设置O_NONBLOCK|O_CLOEXEC(执行exec后关闭)这两个标记

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

注意,这边的accept调用是被glibc用SYSCALL_CANCEL包了一层,其将返回值修正为只有0和-1这两个选择,同时将错误码的绝对值设置在errno内。因为glibc对于系统调用的封装过于复杂,就不在这里细讲了。若是要寻找具体的逻辑,用负载均衡

// 注意accept和(之间要有空格,否则搜索不到

accept (int

在整个glibc代码中搜索便可。

理解accept的关键点是,它会建立一个新的Socket,这个新的Socket来与对端运行connect()的对等Socket进行链接,以下图所示:

3940bc01b90fe8640b27120241852969.png

接下来,咱们就进入Linux内核源码栈吧框架

accept

|->SYSCALL_CANCEL(accept......)

......

|->SYSCALL_DEFINE3(accept

// 最终调用了sys_accept4

|->sys_accept4

/* 检测监听描述符fd是否存在,不存在,返回-BADF

|->sockfd_lookup_light

|->sock_alloc /*新建Socket*/

|->get_unused_fd_flags /*获取一个未用的fd*/

|->sock->ops->accept(sock...) /*调用核心*/

上述流程以下面所示:

6c0b5fd84754e681e5f9c071edd1f0f8.png

由此得知,核心函数在sock->ops->accept上,因为咱们关注的是TCP,那么其实现即为

inet_stream_ops->accept也即inet_accept,再次跟踪下调用栈:socket

sock->ops->accept

|->inet_steam_ops->accept(inet_accept)

/* 由一开始的sock图可知sk_prot=tcp_prot

|->sk1->sk_prot->accept

|->inet_csk_accept

好了,穿过了层层包装,终于到具体逻辑部分了。上代码:tcp

struct sock *inet_csk_accept(struct sock *sk, int flags, int *err)

{

struct inet_connection_sock *icsk = inet_csk(sk);

/* 获取当前监听sock的accept队列*/

struct request_sock_queue *queue = &icsk->icsk_accept_queue;

......

/* 若是监听Socket状态非TCP_LISEN,返回错误 */

if (sk->sk_state != TCP_LISTEN)

goto out_err

/* 若是当前accept队列为空 */

if (reqsk_queue_empty(queue)) {

long timeo = sock_rcvtimeo(sk, flags & O_NONBLOCK);

/* 若是是非阻塞模式,直接返回-EAGAIN */

error = -EAGAIN;

if (!timeo)

goto out_err;

/* 若是是阻塞模式,切超时时间不为0,则等待新链接进入队列 */

error = inet_csk_wait_for_connect(sk, timeo);

if (error)

goto out_err;

}

/* 到这里accept queue不为空,从queue中获取一个链接 */

req = reqsk_queue_remove(queue);

newsk = req->sk;

/* fastopen 判断逻辑 */

......

/* 返回新的sock,也就是accept派生出的和client端对等的那个sock */

return newsk

}

上面流程以下图所示:

b1f45b28e6ce9ef297c6e623ac4a32bd.png

咱们关注下inet_csk_wait_for_connect,即accept的超时逻辑:函数

static int inet_csk_wait_for_connect(struct sock *sk, long timeo)

{

for (;;) {

/* 经过增长EXCLUSIVE标志使得在BIO中调用accept中不会产生惊群效应 */

prepare_to_wait_exclusive(sk_sleep(sk), &wait,

TASK_INTERRUPTIBLE);

if (reqsk_queue_empty(&icsk->icsk_accept_queue))

timeo = schedule_timeout(timeo);

.......

err = -EAGAIN;

/* 这边accept超时,返回的是-EAGAIN */

if (!timeo)

break;

}

finish_wait(sk_sleep(sk), &wait);

return err;

}

经过exclusice标志使得咱们在BIO中调用accept(不用epoll/select等)时,不会惊群。

由代码得知在accept超时时候返回(errno)的是EAGAIN而不是ETIMEOUT。操作系统

EPOLL(在accept时候)"惊群"

因为在EPOLL LT(水平触发模式下),一次accept事件,可能会唤醒多个等待在此listen fd上的(epoll_wait)线程,而最终可能只有一个能成功的获取到新链接(newfd),其它的都是-EGAIN,也即有一些没必要要的线程被唤醒了,作了无用功。关于epoll的原理能够看下笔者以前的博客《从linux源码看epoll》:

https://www.cnblogs.com/alchemystar/p/13161781.html

在这里描述一下缘由,核心就是epoll_wait在水平触发下会在这个fd仍有未处理事件的时候从新塞回ready_list并在此唤醒另外一个等待在epoll上的进程!

1f57085a0d814ac27286189350f17c18.png

因此咱们看到,虽然epoll_wait的时候给本身加了exclusive不会在有中断事件触发的时候惊群,可是水平触发这个机制确也形成了相似"惊群"的现象!

由上面的讨论看出,fd1仍旧有事件是形成额外唤醒的缘由,这个也很好理解,毕竟这个事件是另外一个线程处理的,那个线程估摸着还没来得及运行,天然也来不及处理!

咱们看下在accept事件中,怎么断定这个fd(listen sock的fd)还有未处理事件的。

// 经过f_op->poll断定

epi->ffd.file->f_op->poll

|->tcp_poll

/* 若是sock是listen状态,则由下面函数负责 */

|->inet_csk_listen_poll

/* 经过accept_queue队列是否为空判断监听sock是否有未处理事件*/

static inline unsigned int inet_csk_listen_poll(const struct sock *sk)

{

return !reqsk_queue_empty(&inet_csk(sk)->icsk_accept_queue) ?

(POLLIN | POLLRDNORM) : 0;

}

那么咱们就能够根据逻辑画出时序图了。

89575cc50c7f73d35a122a63d99646e2.png

其实不只仅是accept,要是多线程epoll_wait同一个fd的read/write也是一样的惊群,只不过应该不会有人这么作吧。

正是因为这种"惊群"效应的存在,因此咱们常常采用单开一个线程去专门accept的形式,例如reactor模式便是如此。可是,若是一瞬间有大量链接涌进来,单线程处理仍是有瓶颈的,没法充分利用多核的优点,在海量短链接场景下就显得稍显无力了。这也是有解决方式的!

采用so_reuseport解决惊群

前面讲过,因为咱们是在同一个fd上多线程去运行epoll_wait才会有此问题,那么其实咱们多开几个fd就解决了。首先想到的方案是,多开几个端口号,人为分开监听fd,但这个明显带来了额外的复杂性。为了解决这一问题,Linux提供了so_reuseport这个参数,其原理以下图所示:

8a5c374a22a7fb3e568d5c7ffcdef930.png

多个fd监听同一个端口号,在内核中作负载均衡(Sharding),将accept的任务分散到不一样的线程的不一样Socket上(Sharding),毫无疑问能够利用多核能力,大幅提高链接成功后的Socket分发能力。那么咱们的线程模型也能够改成用多线程accept了,以下图所示:

4ca5f4019a0e7936bf6f9ac14491da03.png

accept_queue全链接队列

在前面的讨论中,accept_queue是accept系统调用中的核心成员,那么这个accept_queue是怎么被填充(add)的呢?以下图所示:

61533d953cdef88ddf98c1984b34c3dc.png

图中展现了client和server在三次交互中,accept_queue(全链接队列)和syn_table半链接hash表的变迁状况。在accept_queue被填充后,由用户线程经过accept系统调用从队列中获取对应的fd

2f82e3bad3b9323afa08f7f7b4c89cac.png

值得注意的是,当用户线程来不及处理的时候,内核会drop掉三次握手成功的链接,致使一些诡异的现象,具体能够看笔者的另外一篇博客《解Bug之路-dubbo流量上线时的非平滑问题》:

https://www.cnblogs.com/alchemystar/p/13473999.html

另外,对于accept_queue具体的填充机制以及源码,能够见笔者另外一篇博客的详细分析

《从Linux源码看Socket(TCP)的listen及链接队列》:

https://www.cnblogs.com/alchemystar/p/13845081.html

总结

Linux内核源码博大精深,每次扎进去探索时候都会废寝忘食,其间能够看到各类优雅的设计,在此分享出来,但愿对读者有所帮助。欢迎你们关注我公众号,里面有各类干货,还有大礼包相送哦!

7aff48ace6b54c1c8546acc4.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/301629.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

豆瓣最高评分8.9!让你拥有“多样性思维”

▲数据汪特别推荐点击上图进入玩酷屋01表象与本质我们的大脑是如何工作的?所谓的类比到底是什么?我们是怎么在截然不同的情景间建立起联系的?类比在学习的过程中发挥着怎样的作用?人类大脑中的每个概念都源于多年来不知不觉中形成的一长串类…

复仇!3:1 KO叙利亚!.NET程序员用算法推演出国足进世界杯概率...

6月16日凌晨2点,国足在阿联酋沙迦体育场迎战卡塔尔世预赛亚洲区40强赛最后一个对手,已经锁定小组头名并出线的劲敌叙利亚队,上半场艾克森中框,武磊送助攻,张稀哲打破僵局,下半场奥斯曼扳平比分,…

linux终端背景透明度设置,Ubuntu Terminal标签背景颜色设置

使用ubuntu以来一直苦于terminal标签颜色差距太小,有时候根本不知道自己在哪个标签下,于是在网上搜索如何设置背景颜色可以让标签便于区分,现总结如下:1.打开gtk-widgets.cssvim /usr/share/themes/Ambiance/gtk-3.0/gtk-widgets.…

快速入门人工智能,这波福利不能错过!

不知不觉,寒假就这样溜走了,看着这个寒假养肥的膘,嗯~又是一个充实的假期。不少同学踏上返校之路,迎来新学期!新学期,当然少不了小天的陪伴啦!早春开学季,小天送福利,福利…

从代码角度揭示:华为鸿蒙的“套壳”真相!

华为鸿蒙操作系统(HarmonyOS)出来后,互联网上已经吵翻天了,有人认为HarmonyOS是“自主的全场景分布式系统”,是国产之光,另一派则认为HarmonyOS是“Android套壳”,是挂羊头卖狗肉。作为十年老程…

linux进化树分析的软件,一款好用的进化树可视化编辑软件

原标题:一款好用的进化树可视化编辑软件iTOL(Interactive Tree Of Life)是一个在线显示和操作的进化树工具。我们可以添加很多自定义的项目,丰富和完善自己的进化树,比如添加柱状图、蛋白结构域、heatmap、基因平行转移(horizontal gene tran…

每日一笑 | 床上还是桌上,你总得选一样~

全世界只有3.14 % 的人关注了数据与算法之美(图片来源于网络,侵权删)

Redis分布式锁抽丝剥茧

之前码甲哥写了两篇有关线程安全的文章:•你管这叫线程安全?•.NET八股文:线程同步技术解读分布式锁是"线程同步"的延续最近首度应用"分布式锁",现在想想,分布式锁不是孤立的技能点,这…

让苹果CEO库克折服的程序员仅10岁!?

▲数据汪特别推荐点击上图进入玩酷屋在国外,编程教育课早已普及,美国、英国、新加坡等国家少儿编程已进入小学标准必修课程体系。韩国、日本也相继在2017年和2020年开展一年级至初三的编程教育普及。美国总统孙女,五岁开始学习在电脑上编程最…

WPF Datagrid合并表头的思路

在使用datagrid的时候,有很多情况下,都需要合并表头,多行表头之类的操作。这就需要我们自定义列了。本文给出一个思路,可以实现此需要,只是本人对这个研究不很明白,只是只是实现,仅此而已。下面…

C# 爬虫:疫情实时信息图

运行结果:using System; using System.Drawing; using System.Text; using NSoup; using NSoup.Nodes; using System.IO; using System.Net; using System.Text.RegularExpressions; using System.Windows.Forms;namespace Pneumonia {public partial class MainFor…

史上最黑科技 | 人造肌肉、DNA折叠、柔性外骨骼…

全世界只有3.14 % 的人关注了数据与算法之美说起被机器人支配,一部分人恐惧得不行,另一部人只当个笑话,但无论哪一边,都忍不住想看看这个神秘的领域正在发生什么,这是本能:“我得盯着你,如果哪天…

linux l显示详细信息,fdisk -l显示信息详解

fdisk -l显示信息详解[rootwww.linuxidc.com ~]# fdisk -lDisk /dev/sda: 10.7 GB, 10737418240 bytes255 heads, 63 sectors/track, 1305 cylindersUnits cylinders of 16065 * 512 8225280 bytesSector size (logical/physical): 512 bytes / 512 bytesI/O size (minimum/o…

有钱真的能为所欲为,微软用75亿美元解决了比尔盖茨的“心头大患”

全世界只有3.14 % 的人关注了数据与算法之美2018年6月4日,微软在官方博客上宣布:以75 亿美元的价格收购了全球最大的开源代码托管平台GitHub。谁也没想到,微软和开源这场长达几十年的战争,到最后双方竟然喜结连理了。不过&#xf…

linux 逻辑卷 pe size 4.00 mib大小怎么改,linux逻辑卷的建立

开始的时候系统各目录的挂载情况如下:增加了一个8G大小的scsi磁盘启动系统之后。[rootpoint1 ~]#fdisk �Cl增加了一个sdb设别。一、分区并格式化磁盘[rootpoint1 ~]#fdisk /dev/sdb输入m是显示帮助菜单输入n创建一个分区,选择p创建主分区&…

svn 自动同步到web站点目录post-commit.bat

为什么80%的码农都做不了架构师?>>> 需求分析: 在服务器上搭建了visualSVN server ,然后为了统一测试环境,又在服务器上搭建了web server。现在的需求是,当开发人员通过svn提交更新的时候,让svn自动将文件…

.NET之模型绑定和验证

介绍模型绑定就是接收将来自HTTP请求的数据映射到模型的过程。如果找不到模型属性的值&#xff0c;并不会报错&#xff0c;而是给该属性设置默认值。示例&#xff1a;比如我们有一个接口为[HttpGet("{id}")] public ActionResult<Pet> GetById(int id, bool do…

每日一笑 | 大学教室的真实写照...

全世界只有3.14 % 的人关注了数据与算法之美&#xff08;图片来源于网络&#xff0c;侵权删&#xff09;

linux调用v4l2获取视频,嵌入式Linux:V4L2视频采集操作流程和接口说明

一般操作流程(视频设备)&#xff1a;1. 打开设备文件。 int fdopen("/dev/video0",O_RDWR);2. 取得设备的capability&#xff0c;看看设备具有什么功能&#xff0c;比如是否具有视频输入,或者音频输入输出等。VIDIOC_QUERYCAP,struct v4l2_capability3. 选择视频输入…

面向对象技术——UML

UML&#xff0c;统一建模语言是一种可视化建模语言。 UML包括九种类型的图&#xff1a;用例图&#xff0c;类图&#xff0c;对象图&#xff0c;顺序图&#xff0c;协作图&#xff0c;状态图&#xff0c;活动图&#xff0c;构件图&#xff0c;及部署图&#xff0c;各种图示系统在…