浅谈网络 | 传输层之套接字Socket

目录

    • 基于 TCP 协议的 Socket 程序调用过程
    • 基于 UDP 协议的 Socket 程序函数调用过程
    • 服务器如何接入更多的项目
    • 构建高并发服务端:从多进程到 IO 多路复用

在前面,我们已经介绍了 TCP 和 UDP 协议,但还没有实践过。接下来这一节,我们将讲解如何基于 TCP 和 UDP 协议进行 Socket 编程。

在学习 TCP 和 UDP 协议时,我们分为了客户端和服务端,编写程序时同样遵循这种划分。

Socket 这个名字非常形象,可以理解为“插口”或“插槽”。虽然我们是编写软件程序,但可以把它想象成两端用网线连接,一头插在客户端,另一头插在服务端,通过这条“网线”进行通信。因此,在通信开始之前,客户端和服务端都需要建立一个 Socket。

那么,创建 Socket 时需要设置哪些参数呢?
Socket 编程是端到端的通信,通常我们无法感知数据在中间经过了多少局域网或路由器。因此,Socket 能够设置的参数,主要涉及网络层和传输层。

在网络层,需要指定 IP 协议的版本:IPv4 或 IPv6,分别对应设置为 AF_INET 和 AF_INET6。在传输层,需要选择使用的协议:

  • 如果使用的是基于数据流的 TCP 协议,则设置为 SOCK_STREAM;
  • 如果使用的是基于数据报的 UDP 协议,则设置为 SOCK_DGRAM。

这样,Socket 的基本参数就完成了配置,客户端和服务端之间便可以通过这个“插槽”开始通信了。

基于 TCP 协议的 Socket 程序调用过程

在创建了 Socket 之后,TCP 和 UDP 的后续流程稍有不同,这里我们先来看基于 TCP 协议的流程。

  1. 服务端:监听端口
    服务端首先需要监听一个端口。通常会先调用 bind 函数,将 Socket 绑定到一个指定的 IP 地址和端口号。

    为什么需要端口?
    因为你写的是一个应用程序,当网络数据包到达时,内核需要通过 TCP 头中的端口号,找到对应的应用程序并将数据传递给它。

    为什么需要 IP 地址?
    一台机器可能有多个网卡,也就可能有多个 IP 地址。通过 bind,你可以选择监听所有网卡的地址,也可以只监听某一个网卡的地址,从而只接收发给该网卡的网络数据包。

  2. 服务端:进入监听状态
    绑定了 IP 和端口后,服务端调用 listen 函数开始监听连接。这时,服务端进入 TCP 状态图中的 LISTEN 状态,准备接受客户端的连接请求。

    在内核中,为每个监听 Socket 维护两个队列:

    已建立连接队列:保存已经完成三次握手的连接,此时这些连接处于 ESTABLISHED 状态。
    未完成连接队列:保存还在三次握手阶段的连接,此时这些连接处于 SYN_RCVD 状态。

  3. 服务端:接受连接
    服务端调用 accept 函数,从 已建立连接队列 中取出一个已完成的连接进行处理。如果队列为空,则 accept 会阻塞,等待新的连接完成。

  4. 客户端:发起连接
    客户端通过 connect 函数向服务端发起连接:

    在参数中指定服务端的 IP 地址和端口号。
    内核会给客户端分配一个临时端口,完成连接所需的信息。
    发起三次握手,等待服务端响应。
    一旦握手完成,服务端的 accept 函数会返回一个 新的 Socket,用于后续数据的读写。

  5. 监听 Socket 和已连接 Socket 的区别
    这是一个重要的知识点:

    监听 Socket:用于监听客户端连接,它负责接收连接请求,处于 LISTEN 状态。
    已连接 Socket:用于真正的数据传输,在连接建立完成后,进入 ESTABLISHED 状态。
    换句话说:

    监听 Socket 接收连接。
    已连接 Socket 处理具体的通信。

  6. 数据读写
    连接建立后,服务端和客户端通过 read 和 write 函数实现数据传输。这种操作与对文件流的读写非常类似。

基于 TCP 协议的 Socket 程序函数调用过程如下图:

在这里插入图片描述
总结:

  1. 服务端流程:

    创建 Socket。
    调用 bind 绑定 IP 地址和端口。
    调用 listen 进入监听状态。
    调用 accept 接受客户端连接,获得新的 Socket 处理通信。

  2. 客户端流程:

    创建 Socket。
    调用 connect 发起连接,指定服务端的 IP 和端口。
    等待三次握手完成。

  3. 数据传输:

    双方通过 read 和 write 函数进行数据交换,直至通信结束。

说 TCP 的 Socket 就是一个文件流,是非常准确的。因为,Socket 在 Linux 中就是以文件的形式存在的。除此之外,还存在文件描述符。写入和读出,也是通过文件描述符。

在内核中,Socket 是一个文件,那对应就有文件描述符。每一个进程都有一个数据结构 task_struct,里面指向一个文件描述符数组,来列出这个进程打开的所有文件的文件描述符。文件描述符是一个整数,是这个数组的下标。

这个数组中的内容是一个指针,指向内核中所有打开的文件的列表。既然是一个文件,就会有一个 inode,只不过 Socket 对应的 inode 不像真正的文件系统一样,保存在硬盘上的,而是在内存中的。在这个 inode 中,指向了 Socket 在内核中的 Socket 结构。

在这个结构里面,主要的是两个队列,一个是发送队列,一个是接收队列。在这两个队列里面保存的是一个缓存 sk_buff。这个缓存里面能够看到完整的包的结构。

Socket 结构如下图

在这里插入图片描述

基于 UDP 协议的 Socket 程序函数调用过程

对于 UDP 来讲,过程有些不一样。UDP 是没有连接的,所以不需要三次握手,也就不需要调用 listen 和 connect,但是,UDP 的的交互仍然需要 IP 和端口号,因而也需要 bind。UDP 是没有维护连接状态的,因而不需要每对连接建立一组 Socket,而是只要有一个 Socket,就能够和多个客户端通信。也正是因为没有连接状态,每次通信的时候,都调用 sendto 和 recvfrom,都可以传入 IP 地址和端口。

基于 UDP 协议的 Socket 程序函数调用过程如下图:
在这里插入图片描述

服务器如何接入更多的项目

会了基本的 Socket 函数之后,你就可以轻松写出一个网络交互程序。像之前介绍的过程那样,建立连接后,进入一个 while 循环:客户端发送数据,服务端接收并处理,然后服务端响应,客户端接收。这种方式非常基础,但却有明显的局限性,因为它几乎只能实现一对一的通信。

如果一个服务器只能服务一个客户,那显然是不现实的。这就像一个老板开了公司,只有自己一个人服务客户,只能服务完一家再接下一家,这样的效率很低,也无法实现规模化。

作为老板,你肯定会想:最多能接多少个项目呢?这就需要计算一下服务器的理论最大连接数。TCP 连接是由一个四元组标识的:{本机 IP, 本机端口, 对端 IP, 对端端口}。对于服务器端,通常固定监听一个本地端口等待客户端连接请求,因此服务端的 TCP 连接四元组中,只有对端的 IP 和端口是变化的。基于此,服务器的最大 TCP 连接数 = 客户端 IP 数 × 客户端端口数

对于 IPv4,客户端的 IP 数最多为 2^32,端口数最多为 2^16,因此理论上单台服务器的最大 TCP 连接数为 2^48

然而,实际中服务器的最大并发连接数远远达不到理论值,主要受到以下两个限制:

  • 文件描述符限制:在 Linux 中,Socket 是文件的一种,每个 TCP 连接都需要占用一个文件描述符。文件描述符的数量受操作系统限制,可以通过 ulimit 调整其上限。
  • 内存限制:每个 TCP 连接都会占用一定的内存,包括内核中的数据结构和 Socket 缓存等。服务器的内存总量决定了可支持的最大连接数。

因此,作为服务器的管理者,为了在有限的资源下接更多的项目,需要优化资源使用,减少每个连接的资源消耗。常见的优化方法包括:

  • 提高文件描述符的上限,通过修改 ulimit 和系统配置支持更多连接。
  • 使用更轻量的协议(如 HTTP/2 或 QUIC)来降低单个连接的资源开销。
  • 实现异步 I/O 或使用事件驱动的网络模型(如 epoll、IOCP 或 libuv)以提高并发性能。
  • 通过负载均衡分流到多台服务器,分散单台机器的连接压力。

构建高并发服务端:从多进程到 IO 多路复用

在现代网络编程中,设计一个能够支持大量连接的高并发服务端是一个常见挑战。本文将从多进程、多线程到 IO 多路复用逐步介绍高并发服务端的几种实现方式,以及它们的优劣和适用场景。

1、多进程模型:将任务外包给“子公司”

多进程模型是高并发服务端的早期解决方案。每当一个客户端发起连接,服务端就通过 fork 创建一个子进程,专门处理这个连接,父进程继续监听新的客户端请求。

工作原理

  1. 服务端通过 bindlisten 函数监听某个端口。
  2. 每当有客户端发起连接,服务端通过 accept 函数接受连接。
  3. 调用 fork 创建一个子进程,子进程复制父进程的文件描述符,并用新的 Socket 与客户端通信。
  4. 子进程完成任务后退出,父进程继续等待其他连接。

优点

  • 简单易用:代码逻辑清晰,子进程隔离性强,互不干扰。
  • 可靠性高:子进程出问题不会影响父进程和其他子进程。

缺点

  • 开销大:每次创建进程需要分配新的内存和资源,操作系统的开销较高。
  • 资源有限:受文件描述符数量和内存限制,无法处理大量连接。
  • 上下文切换开销:进程间切换开销较高,处理高并发性能有限。

适用场景

  • 小规模并发服务。
  • 每个任务独立性强、资源需求大的场景。

2、多线程模型:组建“项目组”完成任务

相比多进程模型,多线程模型更加轻量。每当有客户端连接,服务端通过创建线程来处理任务,而线程之间共享同一进程的资源(如文件描述符和内存)。

工作原理

  1. 服务端监听端口并接受连接。
  2. 每当有客户端连接,创建一个新线程(pthread_create)来处理该连接。
  3. 新线程通过已连接的 Socket 与客户端通信。
  4. 线程完成任务后退出或返回线程池。

优点

  • 轻量化:线程共享进程资源,创建和销毁的开销远小于进程。
  • 并发能力强:能够比多进程支持更多连接。

缺点

  • 资源竞争:线程共享内存,需要同步机制(如锁)避免竞争,增加了编程复杂性。
  • 线程数量有限:线程过多时,操作系统资源耗尽,无法支持更高并发。
  • 调试复杂:线程共享资源容易导致难以发现的竞争和死锁问题。

适用场景

  • 中小规模并发服务。
  • 每个任务需要较少资源,且线程间需要共享数据的场景。

3、 IO 多路复用:一个“项目组”管理多个任务

多进程和多线程模型的一个关键问题在于每个连接需要一个独立的处理单元(进程或线程),这对系统资源是一个极大的浪费。IO 多路复用则通过一个线程同时管理多个连接,大幅提升了资源利用率。

工作原理

  1. 服务端通过 selectpollepoll 函数管理多个文件描述符。
  2. 将所有连接的 Socket 文件描述符放入一个集合。
  3. 使用 IO 多路复用函数监听文件描述符的状态变化。
    • 如果某个 Socket 准备好(可读或可写),就进行处理。
  4. 处理完成后,继续监听下一个事件。

优点

  • 高效:一个线程管理多个连接,减少了线程/进程的创建开销。
  • 灵活:支持动态管理 Socket 文件描述符集合。
  • 扩展性强:能够轻松支持数万甚至更多连接(如使用 epoll)。

缺点

  • 复杂性高:程序需要设计事件驱动机制,逻辑复杂。
  • CPU 密集型:如果任务本身较复杂(如大量计算),单线程可能成为瓶颈。

适用场景

  • 大规模高并发场景(如聊天系统、消息推送)。
  • 每个任务只需较少计算且连接时间较长的场景。

4、IO 多路复用的进化:从 selectepoll

select

  • 通过文件描述符集合管理多个 Socket。
  • 需要每次轮询所有文件描述符,效率低,受限于 FD_SETSIZE

poll

  • 改进了 select,去掉了文件描述符集合的限制。
  • 仍需轮询,效率有限。

epoll

  • 采用事件通知机制(callback),避免了轮询。
  • 支持动态添加/移除文件描述符,监听效率高。
  • 被称为解决 C10K(同时支持 1 万个连接)问题的利器。

核心操作

  1. epoll_create:创建 epoll 实例。
  2. epoll_ctl:向 epoll 实例中添加或删除文件描述符。
  3. epoll_wait:阻塞等待事件发生。

5、 综合方案:线程池 + IO 多路复用

单纯使用 IO 多路复用在任务较重时可能会出现单线程瓶颈。结合线程池可以进一步提升性能:

  1. 主线程使用 epoll 监听所有连接。
  2. 一旦某个连接有事件发生,将任务分发给线程池中的工作线程处理。
  3. 主线程继续监听其他连接。

优点

  • 充分利用 IO 多路复用的高效事件通知机制。
  • 减少线程创建销毁的开销,避免线程过多导致的资源争抢。
  • 兼顾高并发和任务处理性能。

总结

  • 多进程适用于简单的小规模并发任务,但资源开销较大。
  • 多线程比多进程更轻量,但需要同步机制处理资源竞争。
  • IO 多路复用是应对高并发的关键技术,特别是 epoll 提供了高效的事件通知机制。
  • 线程池 + IO 多路复用是目前高并发服务端的主流方案,兼具高效和灵活。

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

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

相关文章

Spire.PDF for .NET【页面设置】演示:打开 PDF 时自动显示书签或缩略图

用户打开 PDF 文档时,他们会看到 PDF 的初始视图。默认情况下,打开 PDF 时不会显示书签面板或缩略图面板。在本文中,我们将演示如何设置文档属性,以便每次启动文件时都会打开书签面板或缩略图面板。 Spire.PDF for .NET 是一款独…

FileLink内外网文件共享系统与FTP对比:高效、安全的文件传输新选择

随着信息技术的不断进步,文件传输和共享已经成为企业日常工作中不可或缺的一部分。传统的FTP(File Transfer Protocol)协议在一定程度上为文件共享提供了便利,但随着企业对文件传输的需求越来越复杂,FileLink内外网文件…

神经网络归一化方法总结

在深度学习中,归一化 是提高训练效率和稳定性的关键技术。以下是几种常见的神经网络归一化方法的总结,包括其核心思想、适用场景及优缺点。 四种归一化 特性Batch NormalizationGroup NormalizationLayer NormalizationInstance Normalization计算维度…

ORB-SLAM2源码学习:Initializer.cc:Initializer::ComputeF21地图初始化——计算基础矩阵

前言 在平面场景我们通过求解单应矩阵H来求解位姿&#xff0c;但是我们在实际中常见的都是非平面场景&#xff0c; 此时需要用基础矩阵F求解位姿。 1.函数声明 cv::Mat Initializer::ComputeF21(const vector<cv::Point2f> &vP1, const vector<cv::Point2f>…

离散化 C++

题目 解题思路 将所有对坐标的访问用map映射到一个新的坐标轴上再在新的坐标轴上进行加法用前缀和快速求出区间的和 代码实现 #include<iostream> #include<algorithm> #include<unordered_map>using namespace std;typedef pair<int, int> PII;con…

uniop触摸屏维修eTOP40系列ETOP40-0050

在现代化的工业与商业环境中&#xff0c;触摸屏设备已成为不可或缺的人机交互界面。UNIOP&#xff0c;作为一个知名的触摸屏品牌&#xff0c;以其高性能、稳定性和用户友好性&#xff0c;广泛应用于各种自动化控制系统、自助服务终端以及高端展示系统中。然而&#xff0c;即便如…

机器学习与图像处理中上采样与下采样

一、机器学习中的上采样 目的&#xff1a;在机器学习中&#xff0c;上采样用于处理不平衡数据集&#xff0c;即某些类别的样本数量远多于其他类别。上采样的目标是通过增加少数类样本的数量来平衡类别分布&#xff0c;从而提高模型对少数类的识别能力。 1.随机过采样&#xff0…

Unity中动态生成贴图并保存成png图片实现

实现原理&#xff1a; 要生成长x宽y的贴图&#xff0c;就是生成x*y个像素填充到贴图中&#xff0c;如下图&#xff1a; 如果要改变局部颜色&#xff0c;就是从x1到x2(x1<x2),y1到y2(y1<y2)这个范围做处理&#xff0c; 或者要想做圆形就是计算距某个点&#xff08;x1,y1&…

DHCP服务(包含配置过程)

目录 一、 DHCP的定义 二、 使用DHCP的好处 三、 DHCP的分配方式 四、 DHCP的租约过程 1. 客户机请求IP 2. 服务器响应 3. 客户机选择IP 4. 服务器确定租约 5. 重新登录 6. 更新租约 五、 DHCP服务配置过程 一、 DHCP的定义 DHCP&#xff08;Dynamic Host Configur…

认识RabbitMq和RabbitMq的使用

1 认识RabbitMq RabbitMQ是⼀个消息中间件&#xff0c;也是⼀个生产者消费者模型&#xff0c;它负责接收&#xff0c;存储并转发消息。 2.1 Producer和Consumer Producer&#xff1a;生产者&#xff0c;是RabbitMQServer的客户端&#xff0c;向RabbitMQ发送消息 Consumer&…

HTML飞舞的爱心

目录 系列文章 写在前面 完整代码 代码分析 写在后面 系列文章 序号目录1HTML满屏跳动的爱心&#xff08;可写字&#xff09;2HTML五彩缤纷的爱心3HTML满屏漂浮爱心4HTML情人节快乐5HTML蓝色爱心射线6HTML跳动的爱心&#xff08;简易版&#xff09;7HTML粒子爱心8HTML蓝色…

Excel把其中一张工作表导出成一个新的文件

excel导出一张工作表 一个Excel表里有多个工作表&#xff0c;怎么才能导出一个工作表&#xff0c;让其生成新的Excel文件呢&#xff1f; 第一步&#xff1a;首先打开Excel表格&#xff0c;然后选择要导出的工作表的名字&#xff0c;比如“Sheet1”&#xff0c;把鼠标放到“She…

Redis-09 SpringBoot集成Redis

Jedis 和 lettuce 基本已经过时 集成RedisTemplate 单机 1.建 Modul 2.改pom <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instanc…

git 命令之只提交文件的部分更改

git 命令之只提交文件的部分更改 有时&#xff0c;我们在一个文件中进行了多个更改&#xff0c;但只想提交其中的一部分更改。这时可以使用 使用 git add -p 命令 Git add -p命令允许我们选择并添加文件中的特定更改。它将会显示一个交互式界面&#xff0c;显示出文件中的每个更…

Node.js的http模块:创建HTTP服务器、客户端示例

新书速览|Vue.jsNode.js全栈开发实战-CSDN博客 《Vue.jsNode.js全栈开发实战&#xff08;第2版&#xff09;&#xff08;Web前端技术丛书&#xff09;》(王金柱)【摘要 书评 试读】- 京东图书 (jd.com) 要使用http模块&#xff0c;只需要在文件中通过require(http)引入即可。…

bridge-multicast-igmpsnooping

# 1.topo # 2.创建命名空间 ip netns add ns0 ip netns add ns1 ip netns add ns2 ip netns add ns3 # 3.创建veth设备 ip link add ns0-veth0 type veth peer name hn0-veth0 ip link add ns1-veth0 type veth peer name hn1-veth0 ip link add ns2-veth0 type veth pe…

麒麟部署一套NFS服务器,用于创建网络文件系统

一、服务端共享目录 在本例中,kyserver01(172.16.200.10)作为客户端,创建一个目录/testdir并挂载共享目录;kyserver02(172.16.200.11)作为服务端,创建一个共享目录/test,设置为读写权限,要求客户端使用root登录时映射为nobody用户、非root登录时保持不变。 服务端启…

《线性代数的本质》

之前收藏的一门课&#xff0c;刚好期末复习&#xff0c;顺便看一看哈哈 课程链接&#xff1a;【线性代数的本质】合集-转载于3Blue1Brown官方双语】 向量究竟是什么 线性代数中最基础、最根源的组成部分就是向量&#xff0c;需要先明白什么是向量 不同专业对向量的看法 物理专…

Scala—Collections集合概述

Scala Scala-集合概述 ScalaScala集合概述1 不可变集合&#xff08;Immutable Collections&#xff09;2 可变集合&#xff08;Mutable Collections&#xff09;3 Scala 集合类的层次结构 Scala集合概述 在 Scala 中&#xff0c;集合主要分为两大类&#xff1a;可变集合&#…

LLC与反激电路设计【学习笔记】

LLC电路&#xff1a; LLC电路是由2个电感和1个电容构成的谐振电路&#xff0c;故称之为LLC&#xff1a; LLC电路通过谐振能够实现MOS管的软开(soft switching)&#xff0c;减少开关损耗。另外MOS管的通态损耗也很低&#xff0c;换言之产生的焦耳热也少&#xff0c;这样就可以不…