当前位置: 首页 > news >正文

socket接口api的深度探究

一、socket接口使用

1.1 socket抽象层

Linux内核net/socket.c定义了一套socket的操作api。图1展示了socket层所处与TCP/IP协议栈之上和应用层之下。

图1 socket层的位置

1.2 一些需要预先知道的内核操作api

socket层大量使用了这些内核操作api,完成协议栈的调用入口。在深度探究socket层实现之前,先来了解下这些内核api。

  • fget_light()和fput_light():轻量级的文件查找入口。多任务对同一个文件进行操作,所以需要对文件做引用计数。fget_light在当前进程的struct files_struct中根据所谓的用户空间文件描述符fd来获取文件描述符。另外,根据当前fs_struct是否被多各进程共享来判断是否需要对文件描述符进行加锁,并将加锁结果存到一个int中返回, fput_light则根据该结果来判断是否需要对文件描述符解锁。fget_light()/fput_light是fget/fput的变形,不用考虑多进程共享同一个文件表而导致的竞争避免锁。fget/fput是指在文件表的引用计数+1/-1

  • sockfd_lookup_light根据fd找到相应的socket object(内核真正操作的对象)。

  • so_xxx: 内核相关socket操作接口。socket object操作协议栈的api入口。

  • in_pcballoc()。分配内核内存,内存名字叫Internet protocol control block。

  • in_pcbbind(), 绑定IN_PCB到指定的地址,如果不指定地址,那么会寻找一个可用的端口进行绑

  • in_pcblookup(): 指定的端口是否可用。

  • sbappend()追加数据到发送缓冲区。

  • so->so_proto->pr_usrreq 是socket object操作协议栈的函数

  • tcp_ursreq()是tcp 协议栈操作的入口函数,支持以下操作类型:PRU_ATTACH,PRU_BIND,PRU_LISTEN, PRU_ACCEPT, PRU_CONNECT, PRU_SHUTDOWN,PRU_ABORT, PRU_DETACH,PRU_SEND,PRU_SENDOOB,PRU_RCVD,PRU_RCVOOB

  • tcp_newtcpcb()。TCP control block被分配,socket描述符指向的正是这个TCP control block。

  • tcp_attach().

  • tcp_xxx: tcp_close(), tcp_disconect(),tcp_drop()

  • pr_xxx: 一套socket层和协议栈通信的接口,包括pr_usrreq(),pr_input(),pr_output(),pr_ctlinput(),pr_ctloutput()。

1.3 socket函数api

1.3.1 socket函数

  • 功能:在内核创建一个socket对象,并返回引用的操作fd。通过这个fd操作这个socket

  • 实现:通过in_pcballoc() 分配Internet control block的内存。接着调用tcp_newtcpcb()分配TCP control block的内存。初始化TCP定时器等。链接Internet control block的内存和TCP control block的内存

  • 注意:此时TCP为CLOSED状态

图2 创建socket函数的流程

1.3.2 bind函数

  • 功能:指派一个ip地址给socket,并且分配一个fd返回

  • 实现:检查端口没有被重复绑定过。网卡有带ip。如果没有指定端口,则会先尝试从reserverd port(<1024)开始找,如果还没有可用会继续从ephemeral port (比如1024~5000)找。

  • 注意:客户端不需要显示bind,因为connect在内部已经自动实现了bind的逻辑。

图3 bind的流程

1.3.3 connect函数

  • 功能:通过该函数建立和对端的有状态连接。connect成功调用意味着TCP三次握手成功

  • 实现:在用户态校验服务端地址(要connect的对端地址)是合法的,移入到内核空间,进入内核态进行connect操作。检查是否以及bind,如果没有bind,进行bind。接着soisconnecting会置tcp状态为SYN_SENT,调用tcp_output会发送SYN包。进入睡眠直到协议栈唤醒,如果成功置ESTABLISHED。

  • 注意:connect之前的显示调用bind不是必须的,当然有也可以。

图4 connect流程

1.3.4 listen函数

  • 功能:listen表示协议栈可以接收新的连接请求,同时正常处理连接中的请求数量是有限的(backlog=521)。

  • 实现:迁移TCP状态从CLOSED迁移到LISTEN

  • 注意

图5 listen流程

1.3.5 accept流程

  • 功能:从listen socket创建新的通信socket

  • 实现:默认accept是阻塞,直到accept到连接,创建新的socket,唤醒客户端,返回这个socket,并且把外网ip和端口从内核协议栈拷贝给用户态应用层。

  • 注意:除了accpet,还有accept4(为什么叫4,因为有4个形参)比accept多了一个参数,可以传flag到系统调用。可以看到两者的区别仅仅在于accept4()有第四个参数flags,这个参数如果为0,就跟accept()一样;下面的两个参数可以用按位OR来获取不同的行为。SOCK_NONBLOCK:为新打开的文件描述符设置O_NONBLOCK标志位,如果是accept需要和fcntl()搭配使用,这样设置的效果和accept4是一样的,区别就是用accept的话需要多调用个fcntl函数。SOCK_CLOEXEC: 为新打开的文件描述符设置FD_CLOEXEC标志位,该标志位的作用是在进程使用fork()加上execve()的时候自动关闭打开的文件描述符。其实使用fcntl()设置FD_CLOEXEC标志位(也就是用open()的时候设置的O_CLOEXEC标志位)也能达到同样的效果,但跟fcntl()有什么不同呢?在多线程环境中,如果使用fcntl()会多出一步操作,这样就可能形成竞争。而使用accept4()就可以直接在打开的文件描述符上设置,可以消除竞争的问题。(原则上该竞争在那些新建文件描述符的调用中都存在,所以很多linux的系统调用都做了类似的处理)

图6 accept流程

1.3.6 send/write函数

  • 功能:发送数据

  • 实现:验证socket和connection状态,分配空间,拷贝消息到内核

  • 注意:发送函数有4个api:sendto,sendmsg,write,writev。send只能操作网络fd,而write更通用,可用处理任意通用fd。另外send允许您为实际操作指定某些选项。读/写是“通用”文件描述符函数,而recv / send稍微更专门化(例如,您可以设置一个标志忽略SIGPIPE,或者发送带外消息…)。

图7 send流程

1.3.7 recv/read函数

  • 功能:接收数据

  • 实现:除了拷贝内核接收区的数据到应用层,还发送窗口更新信息给网络对端

  • 注意:recv和send一样也提供了4套接口:recvfrom,recvmsg,read,readv。recv和read的区别如同send和write的区别

图8 recv流程

1.3.8 close函数

  • 功能:关闭连接

  • 实现:如果是listening socket:遍历两个保持正在连接的pending conection的队列。阻止进一步的accept导致的tcp连接下一步状态迁移。比如说连接处于SYN_RCVD,那么会发送RST包。如果是其他socket,那么会置detach 连接在socket的control block。并且检查linger选项和linger time。如果linger time为0,那么会立即drop掉这个tcp连接。否则则有可能会发送FIN包关闭连接

  • 注意

图9 close流程

1.3.9 shundown函数

拒绝新的网络读数据,释放资源,丢弃读缓冲区,并且关闭读端连接,协议栈将写端缓冲区buff发送出去,并且关闭写端。写端关闭有可能会发送FIN包。

二、深入理解过程

2.1 tcp的三次握手

图10 tcp三次连接(上半部分)

图11 tcp连接的下半部分

2.2 为什么是3次,而不是2次

此时已经客户端已经显示ESTABLISHED,是否可代表只需要两次握手。

为什么不是两次握手,而是三次握手,会有什么问题

还有一种失效连接的处理,客户端向服务端发出的SYN包延迟了,服务端没收到,客户端再重新发一个SYN包,然后服务端新建了这个连接。那么此时如果之前由于网络节点的延迟又达到了B,那么B会以为是A发起的新连接。于是B同意,并向A发起确认,但是此时A根本不会理会。B一直等A发送应用层数据,但是A并没有这个连接的发送任务。

三、异常情况

3.1 accept过程的异常

3.1.1 SYN没成功的重试次数

服务端会根据/proc/sys/net/ipv4/tcp_synack_retries(我的机器设置为5)设置的重试次数,重发SYN+ACK,

3.1.2 backlog已满的状态怎么办

  • 服务端发送SYN+ACK,客户端收到后会回复ACK,如果此时ACCEPT队列仍处于已满状态,退避2^n后再次重试,直到超过重试次数超过/proc/sys/net/ipv4/tcp_synack_retries设置的次数,服务端链接状态SYNC_RECV->CLOSED,客户端链接状态为ESTABLISHED。

  • 如果内核参数/proc/sys/net/ipv4/tcp_abort_on_overflow 是0,服务端会忽略最后一个ACK,此时服务端的TCP链接处于SYN_RECV半连接状态,客户端的TCP链接处于ESTABLISHED状态,客户端以为链接创建成功,服务端却处于半连接状态,状态不一致!其实这种不一致在TCP/IP协议里经常出现,处理方式一般都是重试和退避。

  • 如果内核参数/proc/sys/net/ipv4/tcp_abort_on_overflow 是1,则服务端会回复一个RST包,由SYN_RECV->CLOSED,客户端链接 ESTABLISHED->CLOSED。

3.2 send过程中

3.2.1 进程退出

先用kill -9方法,其实kill -9不能模拟服务器断电的情况。进程退出总共有8中情况:

有8种方式使进程终止,其中前5种为正常终止,它们是

  • 从 main 返回

  • 调用 exit

  • 调用 _exit 或 _Exit

  • 最后一个线程从其启动例程返回

  • 最后一个线程调用 pthread_exit

异常终止有3种,它们是

  • 调用 abort

  • 接到一个信号并终止

  • 最后一个线程对取消请求做出响应

图12 进程正常退出

通过tcp抓包发现,有正常的四次挥手过程

3.2.2 拔电源、拔网线、交换机瘫痪的办法

那如果进程是由于服务器断电,导致的失连,服务器会怎么样。

操作步骤如下:设备B监听端口。设备A通过connect设备B的监听端口。设备A的进程睡眠,此时断掉设备B的网卡。拔网卡的命令是

ip link set eth0 down; 
ip link set eth0 up; 

设备A停止睡觉,send数据,返回值正是这个数据的长度,如果在继续send,会返回成功吗,会接受到对方RST包吗,为什么。这里看到进程发送完退出,会进入一段次数的退避重传(15次,共924秒,哪里配置的),然后没有FIN挥手过程。

send为什么成功的解释是,send只会探测到本地的错误,而不会探测到网络错误。

重试次数的配置:

  • /proc/sys/net/ipv4/tcp_retries1

这个值影响由于某些错误引起的没有ACK的RTO重传和上报这些错误给网路层的时间。

RFC 1122推荐至少3次重传,这是个默认值。

  • /proc/sys/net/ipv4/tcp_retries2

这个值影响当RTO重传仍没收到ACK的TCP连接的超时时间。

给定一个值N,假定一个TCP连接带有TCP_RTO_MIN的初始RTO的指数值会重传N次,在第(N+1)个RTO时杀死这个连接。默认值是15,生成一个假想的超时时间是924.6秒,和一个有效超时的下限。当超过这个假设的超时时间,TCP会在第一个RTO就会超时.RFC1122推荐至少超时时间有100秒,相当于这个值等于8.

引用:socket接口api的深度探究

http://www.dtcms.com/a/268791.html

相关文章:

  • 初识Neo4j之Cypher
  • 【Unity笔记】Unity 粒子系统 Triggers 使用解析:监听粒子进入与离开区域并触发事件
  • 在 macOS 上安装和测试 LibreOffice
  • 深入解析TCP:可靠传输的核心机制与实现逻辑(三次握手、四次挥手、流量控制、滑动窗口、拥塞控制、慢启动、延时应答、面向字节流、粘包问题)
  • 借助HarmonyOS SDK,《NBA巅峰对决》实现“分钟级启动”到“秒级进场”
  • 【7】PostgreSQL 事务
  • SRAM与三级缓存(L1/L2/L3 Cache)的关系
  • 芯谷科技--高性能双运算放大器D358
  • 第二届云计算与大数据国际学术会议(ICCBD 2025)
  • 火山引擎Data Agent全面上线售卖!以企业级数据智能体,重构数据应用范式
  • PostgreSQL中的HASH分区:原理、实现与最佳实践
  • 查看WPS Ofice是64位还是32位
  • 腾讯云 CDN 不支持 WebSocket 的现状与华为云 CDN 的替代方案-优雅草卓伊凡
  • 缺乏项目进度追踪工具,如何选择适合的工具
  • 中电金信 :十问高质量数据集:金融大模型价值重塑有“据”可循
  • 案例分享:应用VIC-3D High-Speed FFT进行吉他拨弦振动的工作变形ODS测量
  • QML中的Item
  • 【银行测试】手机银行APP专项项目+测试点汇总(二)
  • RESTful API概念和设计原则
  • C++之string类的实现代码及其详解(中)
  • 软件之禅(十二)面向对象和市场经济---平等性原理
  • 对象存储-OSS
  • PC端基于SpringBoot架构控制无人机(三):系统架构设计
  • Vite 常用配置详解
  • 创造一个无限可能的机器人世界!——Genesis开源项目了解一下
  • 【Linux | 网络】网络基础
  • Java面试宝典:异常
  • 145.在 Vue3 中使用 OpenLayers 设置原始图、模糊、色相翻转、阴影效果
  • 创客匠人创始人IP打造实录:从行业观察者到生态构建者
  • 如何解决pip安装报错ModuleNotFoundError: No module named ‘datetime’问题