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

[Linux网络编程]深入了解TCP通信API,看看底层做了啥(看了这篇文章,你也算是读了Linux源码的高手!)

在这里插入图片描述

本篇文章篇幅不长,就当茶余饭后、无聊之余看个乐呵。
体验一下知识过脑,却不留下一丝痕迹的奇妙感觉!

文章目录

  • 前言:
  • 接收连接以前
    • socket()
    • bind()
    • listen()
  • 接收连接后
    • connect()
    • accept()-1
      • 全连接队列&半连接队列
    • accept()-2

前言:

在学了Linux网络编程时,有几个问题一直困扰着我

  1. listen的第二个参数backlog到底是什么
  2. 为什么网络套接字也是文件描述符
  3. 为什么要bind(),他起了什么作用

于是就决心梳理一下整套通信的过程,建立完整的通信框架,便有了这篇文章

tips:以下内容均在Linux2.6内核讨论,不同内核的实现方式大致相同,但是某些细节略有不同!!

接收连接以前

在这里插入图片描述

在初始化服务器时,server端会经过socket(),bind()、listen(),他们分别实现了

  • 创建套接字 -> socket()
  • 将套接字绑定到对应端口 -> bind()
  • 开启监听状态 -> listen()

socket()

Linux内,套接字的本质也是文件描述符。所以socket()调用创建的是一个文件。

在这里插入图片描述

上图是socket()函数调用的全过程

  1. task_struct进程控制块在进程启动已经创建
  2. struct socket会被创建。如果socket()调用成功(返回>0),则会创建一个struct file结构体,并为其分配一个文件描述符表的下标(fd_array[n])。struct file内部含有一个private_data指针,指向创建的struct socket结构体。
  3. struct sock会被创建并用调用socket()时传入的参数来初始化
  4. struct socketfile字段初始化为指向它的private_data所属的结构体,sk字段指向struct sock结构体

bind()

bind()函数调用可以将创建的套接字文件描述符绑定到某一端口、IP上

在这里插入图片描述

上图是bind()函数调用的全过程

  1. 在socket()函数调用的时候,也会有一个struct inet_sock结构体被创建。而这个结构体的第一个对象就是struct sock,所以我们只要知道了struct sock对象的地址就可以知道struct inet_sock对象的地址(这是linux常用的一种类似于联合体的方法)
  2. 调用bind()函数传入的参数会被用来初始化struct inet_sock结构体。这样一来,这个文件就有了网络的性质:端口、IP

listen()

listen()函数将套接字绑定监听状态,等待客户端发起连接请求

在这里插入图片描述

上图是listen()函数调用的全过程

  1. struct proto结构体中是一组函数指针,这就是C风格多态,用来实现不同协议的多态调用
  2. sock_common结构体中的skc_state变量初始化为LISTEN状态

接收连接后

connect()

客户端调用connect()时,实际上是发生了TCP连接建立的3次握手
需要明确一点:客户端发起connect()之前,自身也进行了socket()、bind()(隐式bind)操作

在这里插入图片描述

上图是connect()函数调用时发生的情况

  1. 创建了struct tcp_sock结构体,该结构体的起始地址也是struct inet_connection_sockstruct inet_sockstruct sock的起始地址(其中struct inet_connect_sock结构体是struct tcp_sock独有的来管理半连接队列的结构体,后面会讨论)
  2. strcut tcp_sock中的skc_state字段初始化为TCP_SYN_SENT

到此,本地的工作做完,然后就是发起三次握手

在这里插入图片描述

本地的struct tcp_sock结构体已经建立好了,现在就发送连接请求,请求服务器端处理我的连接请求(发起三次握手)

accept()-1

客户端发起connect()后,服务器端使用accept()接收新连接。让我们回顾三次握手

在这里插入图片描述

其中,分为几个阶段

  1. 第一次client发起SYN请求时,server会将该请求放入半连接队列(用于处理临时连接的小结构体)
  2. struct tcp_sock中的struct inet_connection_sock中的struct request_sock_queue管理的就是半连接队列
  3. 如果connect()再次确认要建立连接,则server确认建立后,会将该链接放入全连接队列,并将其从半连接队列删除
  4. struct sock中的struct sk_buff_head管理的就是全连接队列(找不到十足的证据,所以不能保证就是,但是大概率确定)

全连接队列&半连接队列

在这里插入图片描述

概念:

  • 半连接队列: client发送的连接请求还未完整进行三次握手,此时server为了对其进行管理、但是又不想使用太大的空间(因为client可能会放弃这个连接),所以将其放入半连接队列,使用链表进行管理(链表队列)

  • 全连接队列: 连接请求已经经过了三次握手,等待server调用accept()连接请求,便将其放入全连接队列(链表队列)中等待被accept()

  • backlog: 全连接队列允许存放节点的最大长度,也就是允许处于等待accept()调用的最大新连接个数

为何要设置全连接队列?

  • 服务器维护的全连接队列就是个中间商(生产消费模型),来提高执行效率
  • 如果队列过短/为空,可能导致用户发起连接的时候,恰巧碰到OS正在accept()别的连接,那么这个连接就会被丢弃
  • 如果队列过长,可能导致用户发起连接后,等了好久,server既没有接收我的请求,也没有返回我任何信息(比如打开一个网页,此时用户不好判断具体是什么原因导致了网页半天打不开:网络不好?还是连接没有被接收),这样会影响用户的体验

accept()-2

前面的accept()只讨论到了放入全连接队列,此处继续讨论

当上层调用accpet()后,OS会为其创建一个新的套接字,并为其分配一个新的文件描述符并返回,并将该连接请求从全连接队列中删除,然后就可以使用该套接字进行通信了

对于开头的另外两个问题,这里做出简单回答:

1. 为什么网络套接字也是文件描述符
Linux下一切皆文件。对于网络套接字,抽象来看就是对网络作IO操作罢了,所以就可以像操作普通文件一样,使用文件描述符管理网络套接字
2.bind()起到什么作用
bind()将本地的某一个具体的文件和网络绑定到了一起。具体来说,他为文件绑定了端口、IP,它定义了应用程序将要监听的地址和端口(client要请求的地址和端口),正是这个方式,确保了应用能通过网络与其他设备进行通信


http://www.mrgr.cn/news/49625.html

相关文章:

  • 【开源物联网平台】Fastbee系统稳定性和压测报告
  • 在wsl2下将Ubuntu从一个盘移动到其他盘
  • 微服务之间是如何独立通讯的?
  • spring 注解
  • manjaro kde 24 应该如何设置才能上网(2024-10-13亲测)
  • Git上传命令汇总
  • 基于springboot Vue3的两种图形验证码工具——vue3-puzzle-vcode纯前端防人机图形滑动验证码和kaptcha图片文字验证码
  • 查找和最小的 K 对数字
  • JavaScript中WeakMap研究_WeakMap基本介绍_WeakMap()构造函数_实例方法:delete、get、has、set
  • 数据结构:用栈实现队列(OJ232)
  • 原码、反码、补码、位运算
  • HDLBits中文版,标准参考答案 | 3.2.5 Finite State Machines | 有限状态机(5)
  • RTOS实时系统-互斥锁如何保确保同一时间只有一个任务可以访问该资源
  • linux系统账号安全应该如何设置
  • 【ShuQiHere】使用域名代替 IP 地址进行 SSH 连接的完整指南*
  • 【数据结构】:破译排序算法--数字世界的秩序密码(一)
  • 常见几大排序算法
  • 从物理到人工智能:诺贝尔物理学奖开启新纪元
  • 英语变化的总结
  • 如何构建高效的公路工程资料管理系统?