DP读书:《openEuler操作系统》(十)套接字 Socket 数据传输的基本模型
10min速通Socket
- 套接字
- 简介
- 数据传输基本模型
- 1.TCP/IP模型
- 2.UDP模型
- 套接字类型
- 套接字(Socket)编程
- Socket 的连接
- 1.连接概述
- (1)基本概念
- (2)连接状态
- (3)连接队列
- 2.建立连接
- 3.关闭连接
- socket 编程接口介绍
- 数据的传输
- 1. 阻塞与非阻塞
- 2. I/O复用
- 数据的传输路径
- 数据报文收发的总体流程
- 1. 发送报文
- 2. 接收报文
- 整理工具:
- 参考文献:
进程通信的概念最初来源于单机系统。由于每个进程都在自己的地址范围内运行,为保证两个相互通信的进程之间既互不干扰又协调一致工作,操作系统为进程通信提供了相应设施,如:
UNIX BSD有:管道(pipe)、命名管道(named pipe)软中断信号(signal)
UNIX system V有:消息(message)、共享存储区(shared memory)和信号量(semaphore)等.1
他们都仅限于用在本机进程之间通信。网间进程通信要解决的是不同主机进程间的相互通信问题(可把同机进程通信看作是其中的特例)。为此,首先要解决的是网间进程标识问题。同一主机上,不同进程可用进程号(process ID)唯一标识。但在网络环境下,各主机独立分配的进程号不能唯一标识该进程。例如,主机A赋于某进程号5,在B机中也可以存在5号进程,因此,“5号进程”这句话就没有意义了。 其次,操作系统支持的网络协议众多,不同协议的工作方式不同,地址格式也不同。因此,网间进程通信还要解决多重协议的识别问题。
其实TCP/IP协议族已经帮我们解决了这个问题,网络层的“ip地址”可以唯一标识网络中的主机,而传输层的“协议+端口”可以唯一标识主机中的应用程序(进程)。这样利用三元组(ip地址,协议,端口)就可以标识网络的进程了,网络中的进程通信就可以利用这个标志与其它进程进行交互。
使用TCP/IP协议的应用程序通常采用应用编程接口:UNIX BSD的套接字(socket)和UNIX System V的TLI(已经被淘汰),来实现网络进程之间的通信。
就目前而言,几乎所有的应用程序都是采用socket,而现在又是网络时代,网络中进程通信是无处不在,这就是我为什么说“一切皆socket”。
套接字
Socket是进程间通信的一种抽象,提供了一套API接口,对网络传输层一套具体的进程提供了抽象接口的调用
socket起源于Unix,而Unix/Linux基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现, socket即是一种特殊的文件,一些socket函数就是对其进行的操作(读/写IO、打开、关闭).
说白了Socket是应用层与TCP/IP协议族通信的中间软件抽象层,它是一组接口。在设计模式中,Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
注意:其实socket也没有层的概念,它只是一个facade设计模式的应用,让编程变的更简单。是一个软件抽象层。在网络编程中,我们大量用的都是通过socket实现的。
进程间通信机制( s o c k e t I P C ) 进程间通信机制(socket IPC) 进程间通信机制(socketIPC)
openEuler通过Socket接口为用户程序提供网络服务。本节通过Socket介绍数据传输的基本模型. 2
套接字存在于进程与协议栈之间,为应用程序提供API调用、远程TCP、IP通信同住机内Unix通信、应用程序与内核之间的netlink之类的,一个socket对象。
简介
套接字(Socket)是计算机网络中进行数据通信的端点,它提供了一种在不同计算机或同一台计算机的不同进程之间进行数据交换的机制。在操作系统中,套接字是网络通信的基础,通过套接字可以实现进程间的通信(IPC, Inter-Process Communication)以及不同计算机之间的网络通信。

数据传输基本模型
套接字通常基于两种基本的数据传输模型:面向连接的传输(TCP,Transmission Control Protocol)和无连接的传输(UDP,User Datagram Protocol)。
1.TCP/IP模型
TCP/IP模型是一个四层模型,它包括应用层、传输层、网络层和链路层。
-
应用层:负责处理用户的应用程序,如Web浏览器、FTP客户端等。应用层协议定义了应用程序如何通过套接字接口使用网络服务。常见的应用层协议有HTTP、FTP、SMTP等。
-
传输层:负责数据的可靠传输。TCP是传输层的主要协议,它提供了面向连接的、可靠的、基于字节流的传输服务。TCP通过三次握手建立连接,并通过序列号确保数据的顺序性和可靠性。
-
网络层:负责将数据包从源地址路由到目的地址。IP(Internet Protocol)是网络层的主要协议,它定义了数据包的结构,并提供了地址和路由功能。
-
链路层:负责数据在物理介质(如以太网、Wi-Fi等)上的传输。链路层协议(如Ethernet、PPP等)定义了如何在物理网络上发送和接收数据。
2.UDP模型
UDP是另一种重要的传输层协议,与TCP不同,UDP提供的是无连接的、不可靠的数据传输服务。UDP数据包包含应用程序数据、源端口、目的端口和UDP长度等信息。由于UDP不提供可靠性保证,它通常用于对实时性要求较高或能够容忍偶尔丢包的场景,如视频流、VoIP等。
套接字类型
在openEuler或其他类Unix系统中,套接字通常分为三种类型:
-
流套接字(SOCK_STREAM):提供面向连接的、可靠的、基于字节流的传输服务,通常用于TCP协议。
-
数据报套接字(SOCK_DGRAM):提供无连接的、不可靠的、基于数据报的传输服务,通常用于UDP协议。
-
原始套接字(SOCK_RAW):允许直接访问底层协议,通常用于开发新的网络协议或进行网络调试。
套接字(Socket)编程
套接字编程通常涉及以下几个步骤:
- 创建套接字:使用
socket()函数创建一个套接字,并指定协议族(如AF_INET用于IPv4协议)、套接字类型和协议。
创建Socket:- 使用系统调用(如`socket()`函数)创建一个新的Socket描述符。- 指定协议族(如`AF_INET`表示IPv4协议)、Socket类型(如`SOCK_STREAM`表示流式Socket)以及协议(通常为0,表示使用默认协议)。
- 绑定地址和端口:使用
bind()函数将套接字绑定到一个本地地址和端口上,以便其他进程或计算机可以通过该地址和端口访问该套接字。
绑定地址:- 使用`bind()`函数将Socket与本地地址和端口号绑定。- 这样,当远程主机尝试连接时,系统就知道将连接路由到这个Socket。
- 监听和接受连接(对于服务器端):使用
listen()函数使套接字进入监听状态,并使用accept()函数接受客户端的连接请求。
监听连接:- 对于服务端Socket,使用`listen()`函数将其置于监听状态。- 这将允许远程主机发起连接请求。
- 连接和发送/接收数据(对于客户端):使用
connect()函数连接到服务器端的套接字,并使用send()或write()函数发送数据,使用recv()或read()函数接收数据。
接受连接:- 当有远程主机发起连接请求时,服务端使用`accept()`函数接受连接。- `accept()`会创建一个新的Socket描述符,用于与远程主机通信。- 原来的Socket(监听Socket)继续处于监听状态,等待新的连接请求。
- 关闭套接字:使用
close()或shutdown()函数关闭套接字,释放相关资源。
通过这些步骤,可以在openEuler或其他类Unix系统中进行基于套接字的网络通信编程。
Socket 的连接
更准确来说是,流式Socket连接的相关内容
1.连接概述
基本概念
在系统场景中系统一般提供三种类型的Socket:也就是
-
流式Socket(Stream Socket):
- 基于TCP(Transmission Control Protocol,传输控制协议)的Socket。
- 提供面向连接的、可靠的、基于字节流的传输服务。
- 数据在发送和接收时保持顺序,无重复,并且无丢失。
- 需要三次握手建立连接,四次挥手断开连接。
-
数据报Socket(Datagram Socket):
- 基于UDP(User Datagram Protocol,用户数据报协议)的Socket。
- 提供无连接的、不可靠的、基于数据报的服务。
- 数据在发送和接收时可能不保持顺序,也可能出现重复或丢失。
- 不需要建立和维护连接,适用于对实时性要求较高,但数据可靠性要求不高的场景。
-
原始Socket(Raw Socket):
- 允许应用程序直接操作底层协议,绕过内核协议栈。
- 开发者可以自定义协议头,直接构造数据包。
- 这类Socket在常规应用中较少使用,因为它需要对网络协议有深入的了解。
- 流式Socket(Stream Socket)基于TCP,要三次握手的那个,可靠的字节流。
- 数据报Socket(Dategram Socket)基于UDP,基于数据报的非可靠数据传输服务
- 原始Socket(Raw Socket)绕过内核协议栈,填充各级协议头直接构造数据包,常规应用不使用。
TCP通信需要先建立虚拟链路(通信双方的一个连接,connection),TCP/IP通讯下,Socket采用四元组(源IP、源端口、目的IP、目的端口)标识(identity)
i d e n t i t y identity identity
(1)基本概念
同一时间只能处理一个连接
openEuler提供了两种列缓存连接请求,分别为半连接队列和连接队列,当服务端建立具体的请求时,半连接队列和连接队列在其中起缓存作用。

第一次的握手,
(2)连接状态
连接状态
//源文件:include/net/tcp_states.h
enum{
TCP_ESTABLISHED = 1;
TCP_SYN_SENT,
TCP_SYN_RECV,
TCP_FIN_WAIT1,
TCP_FIN_WAIT2,
TCP_CLOSE,
TCP_CLOSE_WAIT,
TCP_LAST_ACK,
TCP_LISENCE,
...
};
//连接状态
TCP连接状态由tcp_states.h中定义的枚举类型表示。以下是一些常见的TCP连接状态:#TCP_ESTABLISHED:连接已经建立,数据可以传输。
#TCP_SYN_SENT:连接正在建立中,服务器已发送SYN报文,等待客户端的SYN-ACK报文。
TCP_SYN_RECV:连接正在建立中,服务器已收到客户端的SYN报文,并发送了SYN-ACK报文,等待客户端的ACK报文。
#TCP_FIN_WAIT1:连接正在关闭中,应用程序已调用close()函数,等待对方发送FIN报文。
TCP_FIN_WAIT2:连接正在关闭中,已收到对方的FIN报文,并发送了ACK报文,等待对方确认。
TCP_CLOSE:连接已关闭,无数据传输。
#TCP_CLOSE_WAIT:连接正在关闭中,对方已发送FIN报文,等待应用程序关闭连接。
TCP_LAST_ACK:连接正在关闭中,已发送最后的ACK报文,等待对方确认。
(3)连接队列
2.建立连接
- 数据传输:
- 一旦连接建立,就可以使用
send()或write()函数发送数据,以及使用recv()或read()函数接收数据。 - 数据传输是双向的,可以在两个Socket之间自由流动。
- 一旦连接建立,就可以使用
TCP连接的建立通常通过三次握手来完成:1. **SYN(SYN=1, seq=x)**:客户端发送一个SYN报文给服务器,并进入SYN_SENT状态,等待服务器的确认。2. **SYN-ACK(SYN=1, ACK=1, seq=y, ack=x+1)**:服务器收到SYN报文后,发送一个SYN-ACK报文给客户端,并进入SYN_RECV状态,等待客户端的确认。3. **ACK(ACK=1, seq=x+1, ack=y+1)**:客户端收到SYN-ACK报文后,发送一个ACK报文给服务器,并进入ESTABLISHED状态,表示连接已建立。服务器收到ACK报文后,也进入ESTABLISHED状态。

3.关闭连接
- 关闭连接:
- 当数据传输完成后,使用
close()函数关闭Socket。 - 对于服务端,可能需要显式关闭监听Socket以停止接受新的连接。
- 当数据传输完成后,使用
TCP连接的关闭通常通过四次挥手来完成:1. **FIN(FIN=1, seq=u)**:应用程序调用`close()`函数关闭连接,客户端发送一个FIN报文给服务器,并进入FIN_WAIT_1状态,等待服务器的确认。2. **ACK(ACK=1, seq=v, ack=u+1)**:服务器收到FIN报文后,发送一个ACK报文给客户端,并进入CLOSE_WAIT状态,表示已知道客户端要关闭连接。3. **FIN(FIN=1, seq=w)**:当服务器准备好关闭连接时,发送一个FIN报文给客户端,并进入LAST_ACK状态,等待客户端的确认。4. **ACK(ACK=1, seq=u+1, ack=w+1)**:客户端收到FIN报文后,发送一个ACK报文给服务器,并进入TIME_WAIT状态,等待足够的时间以确保服务器收到ACK报文。服务器收到ACK报文后,关闭连接。

上图为四次挥手的示意图
由于TCP连接是全双工的,因此每个方向都必须单独进行关闭。这个原则是当一方完成它的数据发送任务后就能发送一个FIN来终止这个方向的连接。收到一个 FIN只意味着这一方向上没有数据流动,一个TCP连接在收到一个FIN后仍能发送数据。首先进行关闭的一方将执行主动关闭,而另一方执行被动关闭。
(1)客户端A发送一个FIN,用来关闭客户A到服务器B的数据传送(报文段4)。
(2)服务器B收到这个FIN,它发回一个ACK,确认序号为收到的序号加1(报文段5)。和SYN一样,一个FIN将占用一个序号。
(3)服务器B关闭与客户端A的连接,发送一个FIN给客户端A(报文段6)。
(4)客户端A发回ACK报文确认,并将确认序号设置为收到序号加1(报文段7)。
对应函数接口如图:
socket 编程接口介绍
下面介绍socket 编程中使用到的一些接口函数。使用 socket 接口需要在我们的应用程序
代码中包含两个头文件:
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
1. socket()函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>
int socket(int domain, int type, int protocol);
socket()函数类似于 open()函数,它用于创建一个网络通信端点(打开一个网络通信),如果成功则返回一个网络文件描述符,通常把这个文件描述符称为 socket 描述符(socket descriptor),这个 socket 描述符跟文件描述符一样,后续的操作都有用到它,把它作为参数,通过它来进行一些读写操作。
该函数包括 3 个参数,如下所示:
d o m a i n domain domain
参数 domain 用于指定一个通信域;这将选择将用于通信的协议族。

对于 TCP/IP 协议来说,通常选择 AF_INET 就可以了,当然如果你的 IP 协议的版本支持 IPv6,那么可以选择 AF_INET6。
t y p e type type
参数 type 指定套接字的类型,当前支持的类型有:

p r o t o c o l protocol protocol
参数 protocol 通常设置为 0,表示为给定的通信域和套接字类型选择默认协议。当对同一域和套接字类型支持多个协议时,可以使用 protocol 参数选择一个特定协议。在 AF_INET 通信域中,套接字类型为 SOCK_STREAM 的默认协议是传输控制协议 (Transmission Control Protocol,TCP 协议)
在 AF_INET 通信域中,套接字类型为 SOCK_DGRAM 的默认协议时 UDP。
调用 socket()与调用 open()函数很类似,调用成功情况下,均会返回用于文件 I/O 的文件描述符,只不过对于 socket()来说,其返回的文件描述符一般称为 socket 描述符。当不再需要该文件描述符时,可调用close()函数来关闭套接字,释放相应的资源。
如果 socket() 函数调用失败,则会返回-1,并且会设置 errno 变量以指示错误类型。
int socket_fd = socket(AF_INET, SOCK_STREAM, 0);//打开套接字
if (0 > socket_fd) {perror("socket error");exit(-1);
}
......
......
close(socket_fd); //关闭套接字
2. bind()函数
bind()函数原型如下所示:
int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
bind()函数用于将一个 IP 地址或端口号与一个套接字进行绑定(将套接字与地址进行关联)。将一个客户端的套接字关联上一个地址没有多少新意,可以让系统选一个默认的地址。一般来讲,会将一个服务器的套接字绑定到一个众所周知的地址—即一个固定的与服务器进行通信的客户端应用程序提前就知道的地址(注意这里说的地址包括 IP 地址和端口号)。因为对于客户端来说,它与服务器进行通信,首先需要知道服务器的 IP 地址以及对应的端口号,所以通常服务器的 IP 地址以及端口号都是众所周知的。
调用 bind()函数将参数 sockfd 指定的套接字与一个地址 addr 进行绑定,成功返回 0,失败情况下返回-1,并设置 errno 以提示错误原因。
参数 addr 是一个指针,指向一个 struct sockaddr 类型变量,如下所示:
struct sockaddr {sa_family_t sa_family;char sa_data[14];
}
第二个成员 sa_data 是一个 char 类型数组,一共 14 个字节,在这 14 个字节中就包括了 IP 地址、端口号等信息,这个结构对用户并不友好,它把这些信息都封装在了 sa_data 数组中,这样使得用户是无法对sa_data 数组进行赋值。事实上,这是一个通用的 socket 地址结构体。
一般我们在使用的时候都会使用 struct sockaddr_in 结构体,sockaddr_in 和 sockaddr 是并列的结构(占用的空间是一样的),指向 sockaddr_in 的结构体的指针也可以指向 sockadd 的结构体,并代替它,而且sockaddr_in 结构对用户将更加友好,在使用的时候进行类型转换就可以了。该结构体内容如下所示:
struct sockaddr_in {sa_family_t sin_family; /* 协议族 */in_port_t sin_port; /* 端口号 */struct in_addr sin_addr; /* IP 地址 */unsigned char sin_zero[8];
};
这个结构体的第一个字段是与 sockaddr 结构体是一致的,而剩下的字段就是 sa_data 数组连续的 14 字节信息里面的内容,只不过从新定义了成员变量而已,sin_port 字段是我们需要填写的端口号信息,sin_addr字段是我们需要填写的 IP 地址信息,剩下 sin_zero 区域的 8 字节保留未用。
最后一个参数 addrlen 指定了 addr 所指向的结构体对应的字节长度。
使用示例
struct sockaddr_in socket_addr;
memset(&socket_addr, 0x0, sizeof(socket_addr)); //清零//填充变量
socket_addr.sin_family = AF_INET;
socket_addr.sin_addr.s_addr = htonl(INADDR_ANY);
socket_addr.sin_port = htons(5555);
//将地址与套接字进行关联、绑定
bind(socket_fd, (struct sockaddr *)&socket_addr, sizeof(socket_addr));
Tips: 代码中的 htons 和 htonl 并不是函数,只是一个宏定义,主要的作用在于为了避免大小端的问题,需要这些宏需要在我们的应用程序代码中包含头文件<netinet/in.h>。
Tips:bind()函数并不是总是需要调用的,只有用户进程想与一个具体的 IP 地址或端口号相关联的时候才需要调用这个函数。如果用户进程没有这个必要,那么程序可以依赖内核的自动的选址机制来完成自动地址选择,通常在客户端应用程序中会这样做。
3. listen()函数
listen()函数只能在服务器进程中使用,让服务器进程进入监听状态,等待客户端的连接请求,listen()函数在一般在 bind()函数之后调用,在 accept()函数之前调用,它的函数原型是:
int listen(int sockfd, int backlog);
无法在一个已经连接的套接字(即已经成功执行 connect()的套接字或由 accept()调用返回的套接字)上执行 listen()。
参数 backlog 用来描述 sockfd 的等待连接队列能够达到的最大值。在服务器进程正处理客户端连接请求的时候,可能还存在其它的客户端请求建立连接,因为 TCP 连接是一个过程,由于同时尝试连接的用户过多,使得服务器进程无法快速地完成所有的连接请求,那怎么办呢?直接丢掉其他客户端的连接肯定不是一个很好的解决方法。因此内核会在自己的进程空间里维护一个队列,这些连接请求就会被放入一个队列中,服务器进程会按照先来后到的顺序去处理这些连接请求,这样的一个队列内核不可能让其任意大,所以必须有一个大小的上限,这个 backlog 参数告诉内核使用这个数值作为队列的上限。而当一个客户端的连接请求到达并且该队列为满时,客户端可能会收到一个表示连接失败的错误,本次请求会被丢弃不作处理。
- accept()函数
服务器调用 listen()函数之后,就会进入到监听状态,等待客户端的连接请求,使用 accept()函数获取客户端的连接请求并建立连接。函数原型如下所示:
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
为了能够正常让客户端能正常连接到服务器,服务器必须遵循以下处理流程:
① 调用 socket()函数打开套接字;
② 调用 bind()函数将套接字与一个端口号以及 IP 地址进行绑定;
③ 调用 listen()函数让服务器进程进入监听状态,监听客户端的连接请求;
④ 调用 accept()函数处理到来的连接请求。
accept()函数通常只用于服务器应用程序中,如果调用 accept()函数时,并没有客户端请求连接(等待连接队列中也没有等待连接的请求),此时 accept()会进入阻塞状态,直到有客户端连接请求到达为止。当有客户端连接请求到达时,accept()函数与远程客户端之间建立连接,accept()函数返回一个新的套接字。这个套接字与 socket()函数返回的套接字并不同,socket()函数返回的是服务器的套接字(以服务器为例),而accept()函数返回的套接字连接到调用 connect()的客户端,服务器通过该套接字与客户端进行数据交互,譬如向客户端发送数据、或从客户端接收数据。
所以,理解 accept()函数的关键点在于它会创建一个新的套接字,其实这个新的套接字就是与执行
connect()(客户端调用 connect()向服务器发起连接请求)的客户端之间建立了连接,这个套接字代表了服务器与客户端的一个连接。如果 accept()函数执行出错,将会返回-1,并会设置 errno 以指示错误原因。
参数 addr 是一个传出参数,参数 addr 用来返回已连接的客户端的 IP 地址与端口号等这些信息。
参数addrlen 应设置为 addr 所指向的对象的字节长度,如果我们对客户端的 IP 地址与端口号这些信息不感兴趣,可以把 arrd 和 addrlen 均置为空指针 NULL。
5. connect()函数
connect()函数原型如下所示:
int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
该函数用于客户端应用程序中,客户端调用 connect()函数将套接字 sockfd 与远程服务器进行连接,参数 addr 指定了待连接的服务器的 IP 地址以及端口号等信息,参数 addrlen 指定了 addr 指向的 struct sockaddr对象的字节大小。
客户端通过 connect()函数请求与服务器建立连接,对于 TCP 连接来说,调用该函数将发生 TCP 连接的握手过程,并最终建立一个 TCP 连接,而对于 UDP 协议来说,调用这个函数只是在 sockfd 中记录服务器IP 地址与端口号,而不发送任何数据。
函数调用成功则返回 0,失败返回-1,并设置 errno 以指示错误原因。
- 发送和接收函数
一旦客户端与服务器建立好连接之后,我们就可以通过套接字描述符来收发数据了(对于客户端使用socket()返回的套接字描述符,而对于服务器来说,需要使用 accept()返回的套接字描述符),这与我们读写普通文件是差不多的操作,譬如可以调用 read()或 recv()函数读取网络数据,调用 write()或 send()函数发送数据。
read()函数
read()函数大家都很熟悉了,通过 read()函数从一个文件描述符中读取指定字节大小的数据并放入到指定的缓冲区中,read()调用成功将返回读取到的字节数,此返回值受文件剩余字节数限制,当返回值小于指定的字节数时并不意味着错误;这可能是因为当前可读取的字节数小于指定的字节数(比如已经接近文件结尾,或者正在从管道或者终端读取数据,或者 read()函数被信号中断等),出错返回-1 并设置 errno,如果在调 read 之前已到达文件末尾,则这次 read 返回 0。
套接字描述符也是文件描述符,所以使用 read()函数读取网络数据时,read()函数的参数 fd 就是对应的套接字描述符。
recv()函数
recv()函数原型如下所示:
ssize_t recv(int sockfd, void *buf, size_t len, int flags);
不论是客户端还是服务器都可以通过 revc()函数读取网络数据,它与 read()函数的功能是相似的。参数sockfd 指定套接字描述符,参数 buf 指向了一个数据接收缓冲区,参数 len 指定了读取数据的字节大小,参数 flags 可以指定一些标志用于控制如何接收数据。

函数 recv()与 read()很相似,但是 recv()可以通过指定 flags 标志来控制如何接收数据3

数据的传输
在网络编程中,数据的传输是核心功能之一。数据传输涉及到许多概念和技术,包括阻塞与非阻塞I/O、I/O复用等。这些概念和技术对于理解和优化网络性能至关重要。
1. 阻塞与非阻塞
阻塞I/O:在阻塞I/O模型中,当应用程序发起一个读或写请求时,如果数据还没有准备好,应用程序将被阻塞,直到数据准备好为止。在这个过程中,应用程序无法执行其他任务。这种模型简单易懂,但对于需要同时处理多个I/O操作的应用来说,效率较低。
非阻塞I/O:在非阻塞I/O模型中,应用程序发起读或写请求时,如果数据没有准备好,操作会立即返回一个错误,而不是阻塞应用程序。这样,应用程序可以继续执行其他任务,直到数据准备好为止。非阻塞I/O提高了应用程序的响应性,但需要开发者自己管理多个I/O操作的轮询和调度,增加了编程的复杂性。
2. I/O复用
I/O复用:I/O复用技术允许单个线程同时处理多个I/O操作。通过复用描述符集合,线程可以在多个描述符之间进行切换,从而实现对多个I/O操作的并行处理。I/O复用技术包括select、poll和epoll等。
select:是最早的I/O复用技术之一。它允许应用程序监视多个文件描述符的状态变化。当某个文件描述符的状态发生变化时,select会通知应用程序,从而进行相应的读或写操作。但是,select在处理大量文件描述符时性能较低,因为它采用轮询的方式来检查每个文件描述符的状态。
poll:poll是select的改进版本,它解决了select在处理大量文件描述符时性能低下的问题。poll使用一个链表来存储文件描述符,而不是使用位图,这使得它在处理大量文件描述符时更加高效。
epoll:epoll是Linux特有的I/O复用技术,它采用事件驱动的方式来实现对多个I/O操作的并行处理。epoll通过一个红黑树来管理文件描述符,并使用一个事件表来存储触发的事件。当某个文件描述符的状态发生变化时,epoll会通知应用程序,并将事件添加到事件表中。这样,应用程序可以一次性处理多个触发的事件,提高了处理效率。
在实际应用中,开发者需要根据具体场景和需求选择合适的I/O模型和复用技术。对于需要处理大量并发连接的应用,非阻塞I/O和I/O复用技术通常是更好的选择。而对于简单的、不需要处理大量并发连接的应用,阻塞I/O模型可能更加适合。


数据的传输路径
数据报文收发的总体流程
在计算机网络中,数据的传输路径涉及多个层次和组件,从物理层到应用层。数据报文(或称数据包)的收发遵循一个总体流程,包括发送报文和接收报文两个主要阶段。
1. 发送报文
发送报文的过程通常涉及以下步骤:
应用层处理:
- 应用程序生成要发送的数据。
- 应用程序使用适当的协议(如HTTP、FTP、SMTP等)对数据进行封装,添加必要的头部信息(如目标地址、端口号等)。
传输层处理:
- 传输层(通常是TCP或UDP)接收来自应用层的数据段,并添加传输层头部信息(如序列号、窗口大小、校验和等)。
- 如果是TCP连接,传输层负责将数据分割成适当大小的数据段,并处理流量控制和拥塞控制。
网络层处理:
- 网络层(通常是IP层)接收来自传输层的数据包,并添加网络层头部信息(如源IP地址、目标IP地址等)。
- 路由器根据数据包中的IP地址信息进行路由选择,将数据包转发到下一个目标地址。
数据链路层处理:
- 数据链路层(如以太网)将网络层传来的数据包封装成帧,添加帧头部和尾部(如MAC地址、帧类型等)。
- 帧通过物理介质(如网线、无线信号等)传输到相邻的节点。
物理层传输:
- 物理层负责在物理介质上传输比特流。这涉及到电信号、光信号或无线信号的传输。
2. 接收报文
接收报文的过程是发送过程的逆向操作,通常涉及以下步骤:
物理层接收:
- 物理层接收到来自物理介质的比特流,并将其转换为数据链路层可以理解的信号。
数据链路层处理:
- 数据链路层从接收到的信号中提取帧,验证帧的完整性(如CRC校验)。
- 如果帧有效,数据链路层将其传递给网络层。
网络层处理:
- 网络层从帧中提取数据包,并根据数据包中的IP地址信息进行路由处理。
- 如果数据包的目标地址与本地节点匹配,网络层将其传递给传输层。
传输层处理:
- 传输层(TCP或UDP)接收数据包,并验证其完整性和正确性(如序列号、校验和等)。
- 如果是TCP连接,传输层负责重新排序收到的数据段,并进行流量控制和拥塞控制。
应用层处理:
- 应用层从传输层接收数据,并去除协议头部信息。
- 应用程序处理接收到的数据,并根据需要执行相应的操作(如显示网页、保存文件等)。
在实际的网络通信中,发送和接收报文的过程可能涉及多个中间节点(如路由器、交换机等)的转发和处理。此外,为了保证数据传输的可靠性和效率,网络协议栈中的各个层次通常会使用各种算法和机制(如差错控制、流量控制、拥塞控制等)来优化数据传输过程。4
整理工具:
- 绘图工具:VISO
- 截图工具:Photor
- 文本工具:typora
- 书签工具:pocket
参考文献:
Linux 的 SOCKET 编程详解,hguisu ↩︎
《openEuler操作系统(第2版)》,任炬、张尧学 ↩︎
Socket 编程基础,比特冬哥 ↩︎
Socket 编程详解:从基本概念到实例应用(TCP|UDP C语言实例详解) ,二进制coder ↩︎
相关文章:
DP读书:《openEuler操作系统》(十)套接字 Socket 数据传输的基本模型
10min速通Socket 套接字简介数据传输基本模型1.TCP/IP模型2.UDP模型 套接字类型套接字(Socket)编程Socket 的连接1.连接概述(1)基本概念(2)连接状态(3)连接队列 2.建立连接3.关闭连接 socket 编程接口介绍数据的传输1. 阻塞与非阻塞2. I/O复用 数据的传输…...
抓住母亲节销售机会:Shopee 平台选品策略大揭秘
母亲节,作为一个重要的购物节日,为卖家带来了巨大的销售机会。在Shopee这样的电商平台上,如何通过有效的选品策略吸引消费者、提高销量呢?下面将介绍一些关键策略,帮助卖家在母亲节期间实现销售突破。 先给大家推荐一…...
Mysql如何优化数据查询方案
mysql做读写分离 读写分离是提高mysql并发的首选方案。 Mysql主从复制的原理 mysql的主从复制依赖于binlog,也就是记录mysql上的所有变化并以二进制的形式保存在磁盘上,复制的过程就是将binlog中的数据从主库传输到从库上。 主从复制过程详细分为3个阶段…...
SwiftUI 更自然地向自定义视图传递参数的“另类”方式
概览 在 SwiftUI 中,正是自定义视图让我们的 App 变得与众不同!然而,除了传统的视图接口定义方式以外,我们其实还可以有更“银杏化”的选择。 如上图所示:对于 SubView 子视图所需的参数我们一开始并没有操之过急&…...
Word第一课
文章目录 1. 文件格式1.1 如何显示文件扩展名1.2 Word文档格式的演变1.3 常见的Word文档格式 3. 文档属性理解文档属性查看文档属性 1. 文件格式 1.1 如何显示文件扩展名 文档格式指的是文件的扩展名,例如下图 对于该文件,.docx就是文件扩展名&#x…...
【Vue3】路由传参的几种方式
路由导航有两种方式,分别是:声明式导航 和 编程式导航 参数分为query参数和params参数两种 声明式导航 query参数 一、路径字符串拼接(不推荐) 1.传参 在路由路径后直接拼接?参数名:参数值 ,多组参数间使用&分隔。 <RouterLink …...
突破编程_C++_面试(高级特性(1))
面试题1:什么是线程以及它在并发编程中的作用是什么 线程( Thread )是操作系统能够进行运算调度的最小单位,它被包含在进程之中,是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流,一个进…...
django请求生命周期流程图,路由匹配,路由有名无名反向解析,路由分发,名称空间
django请求生命周期流程图 浏览器发起请求。 先经过网关接口,Django自带的是wsgiref,请求来的时候解析封装,响应走的时候打包处理,这个wsgiref模块本身能够支持的并发量很少,最多1000左右,上线之后会换成u…...
@ 代码随想录算法训练营第8周(C语言)|Day54(动态规划)
代码随想录算法训练营第8周(C语言)|Day54(动态规划) Day53、动态规划(包含题目 ● 123.买卖股票的最佳时机III ● 188.买卖股票的最佳时机IV ) 123.买卖股票的最佳时机III 题目描述 给定一个数组&#…...
Flask 学习100-Flask-SocketIO 结合 xterm.js 实现网页版Xshell
前言 xterm.js 是一个使用 TypeScript 编写的前端终端组件,可以直接在浏览器中实现一个命令行终端应用。 可以实现 web-terminal 功能,类似于Xshell 操作服务器。 Flask-SocketIO 快速入门与使用基础参考前面这篇https://www.cnblogs.com/yoyoketang/p/18022139 前后端交互…...
Springboot AOP开发
Springboot AOP开发 一 AOP概述 AOP,即面向切面编程,简言之,面向方法编程。 针对方法,在方法的执行前或执行后使用,用于增强方法,或拓展。 二 AOP开发 1.引入 spring-boot-starter-aop 在SpringBoot项…...
office的excel中使用,告诉我详细的解决方案,如何变成转化为金额格式
在Office的Excel中,如果你想将名为"MEREFIELD"的公式结果转换为金额格式,你可以遵循以下详细步骤来实现: 书写MEREFIELD公式: 首先,在Excel中输入或确认你的MEREFIELD公式。例如,假设这个公式是用…...
灾后重建中GIS技术的关键作用与案例分析
地质灾害是指全球地壳自然地质演化过程中,由于地球内动力、外动力或者人为地质动力作用下导致的自然地质和人类的自然灾害突发事件。由于降水、地震等自然作用下,地质灾害在世界范围内频繁发生。我国除滑坡灾害外,还包括崩塌、泥石流、地面沉…...
java环境安装
java环境安装 一、官网下载: jdk,下载jdk,解压到D:\JAVA\Java\jdk目录下。 二、配置: 配置环境变量 鼠标右键我的电脑->属性->高级系统设置->环境变量->系统变量新建变量名JAVA_HOME,变量值为刚才解压的…...
如何在iStoreOS软路由系统中安装cpolar实现公网远程本地电脑桌面
文章目录 简介一、配置远程桌面公网地址二、家中使用永久固定地址 访问公司电脑**具体操作方法是:** 简介 软路由是PC的硬件加上路由系统来实现路由器的功能,也可以说是使用软件达成路由功能的路由器。 使用软路由控制局域网内计算机的好处:…...
appium实现自动化测试原理
目录 1、Appium原理 1.1、Android Appium原理图文解析 1.1.2、原理详解 1.1.2.1、脚本端 1.1.2.2、appium-server 1.1.2.3、中间件bootstrap.jar 1.1.2.4、驱动引擎uiautomator 1.2、 IOS Appium原理 1、Appium原理 1.1、Android Appium原理图文解析 执行测试脚本全过…...
Linux:docker搭建redis集群(3主3从扩容缩容 哈希槽分配)
操作系统:centos7 docker-ce版本:24.0.7 1.准备redis镜像 我这里使用redis 6.0.8 镜像进行操作,如果你也需要镜像,在网络正常情况下直接使用 docker pull redis:6.0.8 即可进行下载,如果你没配置国内加速器&#x…...
Linux程序性能分析60秒+
Linux性能分析大师Brendan Gregg有一篇非常著名的博客,介绍在性能分析开始的60秒内,利用标准的Linux命令行工具,执行一次充分的性能检查,获得系统资源利用率和进程运行情况的整体概念,查看是否存在异常、评估饱和度。本…...
mmap映射文件使用示例
mmap 零拷贝技术可以应用于很多场景,其中一个典型的应用场景是网络文件传输。 假设我们需要将一个大文件传输到远程服务器上。在传统的方式下,我们可能需要将文件内容读入内存,然后再将数据从内存复制到网络协议栈中,最终发送到远…...
Linux命令:stat命令
目录 1 简介2 说明3 实例-L:显示链接指向的文件的信息-f:显示文件系统信息-t:以简洁的形式输出 1 简介 stat命令:显示文件或文件系统的状态 2 说明 使用:stat [OPTION]… FILE 常用选项: -L, --derefer…...
MPNet:旋转机械轻量化故障诊断模型详解python代码复现
目录 一、问题背景与挑战 二、MPNet核心架构 2.1 多分支特征融合模块(MBFM) 2.2 残差注意力金字塔模块(RAPM) 2.2.1 空间金字塔注意力(SPA) 2.2.2 金字塔残差块(PRBlock) 2.3 分类器设计 三、关键技术突破 3.1 多尺度特征融合 3.2 轻量化设计策略 3.3 抗噪声…...
rknn优化教程(二)
文章目录 1. 前述2. 三方库的封装2.1 xrepo中的库2.2 xrepo之外的库2.2.1 opencv2.2.2 rknnrt2.2.3 spdlog 3. rknn_engine库 1. 前述 OK,开始写第二篇的内容了。这篇博客主要能写一下: 如何给一些三方库按照xmake方式进行封装,供调用如何按…...
C++:std::is_convertible
C++标志库中提供is_convertible,可以测试一种类型是否可以转换为另一只类型: template <class From, class To> struct is_convertible; 使用举例: #include <iostream> #include <string>using namespace std;struct A { }; struct B : A { };int main…...
大数据零基础学习day1之环境准备和大数据初步理解
学习大数据会使用到多台Linux服务器。 一、环境准备 1、VMware 基于VMware构建Linux虚拟机 是大数据从业者或者IT从业者的必备技能之一也是成本低廉的方案 所以VMware虚拟机方案是必须要学习的。 (1)设置网关 打开VMware虚拟机,点击编辑…...
Qwen3-Embedding-0.6B深度解析:多语言语义检索的轻量级利器
第一章 引言:语义表示的新时代挑战与Qwen3的破局之路 1.1 文本嵌入的核心价值与技术演进 在人工智能领域,文本嵌入技术如同连接自然语言与机器理解的“神经突触”——它将人类语言转化为计算机可计算的语义向量,支撑着搜索引擎、推荐系统、…...
ESP32 I2S音频总线学习笔记(四): INMP441采集音频并实时播放
简介 前面两期文章我们介绍了I2S的读取和写入,一个是通过INMP441麦克风模块采集音频,一个是通过PCM5102A模块播放音频,那如果我们将两者结合起来,将麦克风采集到的音频通过PCM5102A播放,是不是就可以做一个扩音器了呢…...
如何为服务器生成TLS证书
TLS(Transport Layer Security)证书是确保网络通信安全的重要手段,它通过加密技术保护传输的数据不被窃听和篡改。在服务器上配置TLS证书,可以使用户通过HTTPS协议安全地访问您的网站。本文将详细介绍如何在服务器上生成一个TLS证…...
让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...
【JavaWeb】Docker项目部署
引言 之前学习了Linux操作系统的常见命令,在Linux上安装软件,以及如何在Linux上部署一个单体项目,大多数同学都会有相同的感受,那就是麻烦。 核心体现在三点: 命令太多了,记不住 软件安装包名字复杂&…...
Android Bitmap治理全解析:从加载优化到泄漏防控的全生命周期管理
引言 Bitmap(位图)是Android应用内存占用的“头号杀手”。一张1080P(1920x1080)的图片以ARGB_8888格式加载时,内存占用高达8MB(192010804字节)。据统计,超过60%的应用OOM崩溃与Bitm…...
