156 Linux C++ 通讯架构实战11,listen函数剖析,监听套接字,accept函数,sync攻击,阻塞和非阻塞I/O,同步和异步I/O

listen函数剖析

listen()函数调用格式: int listen(int sockfd, int backlog);

backlog参数的含义:

要理解好backlog这个参数,我们需要先谈一谈 "监听套接字 队列"的话题;

对于一个调用listen()进行监听的套接字,操作系统会给这个套接字 维护两个队列;分别是"未完成连接队列" 和 "已完成连接队列"

最开始设计这个参数的时候,backlog代表了两个 队列的和。后面随着版本的升级,改成了"已完成连接队列"的个数。(升级主要是为了对于syn攻击的一种解决方案,虽然这个方案不能完全解决syn攻击)。

升级后,后台还是有"未完成连接队列"存在的。

未完成连接队列 【保存连接用的】

当客户端 发送tcp连接三次握手的第一次【syn包】给服务器的时候,服务器就会在未完成队列中创建一个 跟这个 syn包对应的一项,

其实,我们可以把这项看成是一个半连接【因为连接还没建立起来呢】,这个半连接的状态会从LISTEN变成SYN_RCVD状态,同时给客户端返回第二次握手包【syn,ack】

这个时候,服务器是在等待完成第三次握手;

已完成连接队列 【保存连接用的】

当第三次握手完成了,这个连接就变成了ESTABLISHED状态,每个已经完成三次握手的客户端 都放在这个队列中作为一项;

//(1)客户端这个connect()什么时候返回,其实是收到三次握手的第二次握手包(也就是收到服务器发回来的syn/ack)之后就返回了;

//(2)RTT是未完成队列中任意一项在未完成队列中留存的时间,这个时间取决于客户端和服务器;

//对于客户端,这个RTT时间是第一次和第二次握手加起来的时间;

//对于服务器,这个RTT时间实际上是第二次和第三次握手加起来的时间;

//如果这三次握手包传递速度特别快的话,大概187毫秒能够建立起来这个连接;这个时间挺慢,所以感觉建立TCP连接的成本挺高;

syn攻击【syn flood】:典型的利用TCP/IP协议涉及弱点进行坑爹的一种行为;

如果一个恶意客户,迟迟不发送三次握手的第三个包。那么这个连接就建立不起来,那么这个处于SYN_RCVD的这一项【服务器端的未完成队列中】,

//就会一致停留在服务器的未完成队列中,这个停留时间大概是75秒,如果超过这个时间,这一项会被操作系统干掉;

一般称这种攻击也叫做 "拒绝服务攻击(DOS/DDOS)",

为了解决这个问题的发生,listen函数改动了backlog的说明:进一步明确和规定了:指定给定套接字上内核为之排队的最大已完成连接数【已完成连接队列中最大条目数】;

那么什么时候从"已完成队列"中取出数据呢?accept函数本质上就是干这个事情的。

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

返回值:

On success, these system calls return a nonnegative integer that is a descriptor for the accepted socket. On error, -1 is returned, and errno is set appropriately.

accept()函数,就使用来 从 已完成连接队列 中 的队首【队头】位置取出来一项【每一项都是一个已经完成三路握手的TCP连接】,返回给进程;

如果已完成连接队列是空的呢?那么咱们这个范例中accept()会一致卡在这里【休眠】等待,一直到已完成队列中有一项时才会被唤醒;

所以,从编程角度,我们要尽快的用accept()把已完成队列中的数据【TCP连接】取走,大家必须有这个认识;

accept()返回的是个套接字,这个套接字就代表那个已经用三次握手建立起来的那个tcp连接,因为accept()是从 已完成队列中取的数据;

换句话来说,我们服务器程序,必须要严格区分两个套接字:

//a)监听9000端口这个套接字,这个东西叫"监听套接字【listenfd】",只要服务器程序在运行,这个套接字就应该一直存在;

//b)当客户端连接进来,操作系统会为每个成功建立三次握手的客端再创建一个套接字【当然是一个已经连接套接字】,accept()返回的就是这种套接字;

//也就是从已完成连接队列中取得的一项。随后,服务器使用这个accept()返回的套接字和客户端通信的;

思考题:

//(1)如果两个队列之和【已完成连接队列,和未完成连接队列】达到了listen()所指定的第二参数,也就是说队列满了;

//此时,再有一个客户发送syn请求,服务器怎么反应?

//实际上服务器会忽略这个syn,不给回应; 客户端这边,发现syn没回应,过一会会重发syn包;

//(2)从连接被扔到已经完成队列中去,到accept()从已完成队列中把这个连接取出这个之间是有个时间差的,如果还没等accept()从已完成队列中把这个连接取走的时候,客户端如果发送来数据,这个数据就会被保存再已经连接的套接字的接收缓冲区里,这个缓冲区有多大,最大就能接收多少数据量;

大家在写代码时尽快用accept()把已完成队列里边的连接取走,尽快 留出空闲为止给后续的已完成三路握手的条目用,那么这个已完成队列一般不会满;

一般这个backlog值给300左右;

二:阻塞与非阻塞I/O

阻塞和非阻塞主要是指调用某个系统函数时,这个函数是否会导致我们的进程进入sleep()【卡在这休眠】状态而言的;

a)阻塞I/O

//我调用一个函数,这个函数就卡在在这里,整个程序流程不往下走了【休眠sleep】,该函数卡在这里等待一个事情发生,只有这个事情发生了,这个函数才会往下走;

//这种函数,就认为是阻塞函数;accept();

//这种阻塞,并不好,效率很低;一般我们不会用阻塞方式来写服务器程序,效率低;

b)非阻塞I/O:不会卡住,充分利用时间片,执行更高;

//非阻塞模式的两个鲜明特点:

//(1)不断的调用accept(),recvfrom()函数来检查有没有数据到来,如果没有,函数会返回一个特殊的错误标记来告诉你,这种标记可能是EWULDBLOCK,

//也可能是EAGAIN;如果数据没到来,那么这里有机会执行其他函数,但是也得不停的再次调用accept(),recvfrom()来检查数据是否到来,非常累;

//(2)如果数据到来,那么就得卡在这里把数据从内核缓冲区复制到用户缓冲区,所以复制这个阶段是卡着完成的;

三:同步与异步I/O:这两个概念容易和 阻塞/非阻塞混淆;

a)异步I/O:调用一个异步I/O函数时,我门要给这个函数指定一个接收缓冲区,我还要给定一个回调函数;

//调用完一个异步I/O函数后,该函数会立即返回。 其余判断交给操作系统,操作系统会判断数据是否到来,如果数据到来了,操作系统会把数据拷贝到你所提供的缓冲区里,然后调用你所指定的这个回调函数来通知你;

//很容易区别非阻塞和异步I/O的差别:

//(1)非阻塞I/O要不停的调用I/O函数来检查数据是否来,如果数据来了,就得卡在I/O函数这里把数据从内核缓冲区复制到用户缓冲区,然后这个函数才能返回;

//(2)异步I/O根本不需要不停的调用I/O函数来检查数据是否到来,只需要调用一次,然后就可以干别的事情去了;

//内核判断数据到来,拷贝数据到你提供的缓冲区,调用你的回调函数来通知你,你并没有被卡在那里的情况;

b)同步I/O

select/poll。epoll。都是用的同步I/O ,也叫做 I/O多路复用

//1)调用select()判断有没有数据,有数据,走下来,没数据卡在那里;

//2)select()返回之后,用recvfrom()去取数据;当然取数据的时候也会卡那么一下;

//同步I/O感觉更麻烦,要调用两个函数才能把数据拿到手;

//但是同步I/O和阻塞式I/O比,就是所谓的 I/O复用【用两个函数来收数据的优势】 能力;

//(3.1)I / O复用

//所谓I/O复用,就是我多个socket【多个TCP连接】可以弄成一捆【一堆】,我可以用select这种同步I/O函数在这等数据;

//select()的能力是等多条TCP连接上的任意一条有数据来;,然后哪条TCP有数据来,我再用具体的比如recvfrom()去收。

//所以,这种调用一个函数能够判断一堆TCP连接是否来数据的这种能力,叫I/O复用,英文I/O multiplexing【I/O多路复用】

//很多资料把 阻塞I/O,非阻塞I/O,同步I/O归结为一类 ,因为他们多多少少的都有阻塞的行为发生;

//甚至有的资料直接就把 阻塞I/O,非阻塞I/O 都归结为同步I/O模型,这也是可以的】

//而把异步I/O单独归结为一类,因为异步I/O是真正的没有阻塞行为发生的;

//(3.2)思考题

//什么叫 用 异步的方法 去使用 非阻塞调用 ?

各种对比

相关推荐
sduwcgg6 小时前
IQ-Learn 在 RTX 3090 服务器上的环境配置与踩坑记录
运维·服务器
呱呱巨基6 小时前
Linux 基础IO
linux·c++·笔记·学习
QFIUNE6 小时前
CD-HIT 详解:序列去冗余、安装使用与聚类结果解析
linux·服务器·机器学习·数据挖掘·conda·聚类
vortex57 小时前
XFCE 桌面环境组件详解:从面板到剪贴板管理
linux·xfce·桌面环境
marsh02067 小时前
43 openclaw熔断与降级:保障系统在异常情况下的可用性
java·运维·网络·ai·编程·技术
摇滚侠7 小时前
Docker 如何查询挂载的目录
运维·docker·容器
勇闯逆流河8 小时前
【Linux】linux进程控制(进程池的详解与实现)
linux·运维·服务器
zhangfeng11338 小时前
部署到服务器上 宝塔系统 使用宝塔在线编辑器 FTP 批量上传 Git 部署 打包上传 codebudyy 编程程序开发
服务器·git·编辑器
WJ.Polar9 小时前
Scapy基本应用
linux·运维·网络·python
lljss20209 小时前
1. NameServer 域名服务器---NS
linux·服务器·前端