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)以及不同计算机之间的网络通信。
图1 Socket 逻辑链示意图

数据传输基本模型

套接字通常基于两种基本的数据传输模型:面向连接的传输(TCP,Transmission Control Protocol)和无连接的传输(UDP,User Datagram Protocol)。

1.TCP/IP模型

TCP/IP模型是一个四层模型,它包括应用层、传输层、网络层和链路层。

  1. 应用层:负责处理用户的应用程序,如Web浏览器、FTP客户端等。应用层协议定义了应用程序如何通过套接字接口使用网络服务。常见的应用层协议有HTTP、FTP、SMTP等。

  2. 传输层:负责数据的可靠传输。TCP是传输层的主要协议,它提供了面向连接的、可靠的、基于字节流的传输服务。TCP通过三次握手建立连接,并通过序列号确保数据的顺序性和可靠性。

  3. 网络层:负责将数据包从源地址路由到目的地址。IP(Internet Protocol)是网络层的主要协议,它定义了数据包的结构,并提供了地址和路由功能。

  4. 链路层:负责数据在物理介质(如以太网、Wi-Fi等)上的传输。链路层协议(如Ethernet、PPP等)定义了如何在物理网络上发送和接收数据。

2.UDP模型

UDP是另一种重要的传输层协议,与TCP不同,UDP提供的是无连接的、不可靠的数据传输服务。UDP数据包包含应用程序数据、源端口、目的端口和UDP长度等信息。由于UDP不提供可靠性保证,它通常用于对实时性要求较高或能够容忍偶尔丢包的场景,如视频流、VoIP等。

套接字类型

在openEuler或其他类Unix系统中,套接字通常分为三种类型:

  1. 流套接字(SOCK_STREAM):提供面向连接的、可靠的、基于字节流的传输服务,通常用于TCP协议。

  2. 数据报套接字(SOCK_DGRAM):提供无连接的、不可靠的、基于数据报的传输服务,通常用于UDP协议。

  3. 原始套接字(SOCK_RAW):允许直接访问底层协议,通常用于开发新的网络协议或进行网络调试。

套接字(Socket)编程

套接字编程通常涉及以下几个步骤:

  1. 创建套接字:使用socket()函数创建一个套接字,并指定协议族(如AF_INET用于IPv4协议)、套接字类型和协议。
 创建Socket:- 使用系统调用(如`socket()`函数)创建一个新的Socket描述符。- 指定协议族(如`AF_INET`表示IPv4协议)、Socket类型(如`SOCK_STREAM`表示流式Socket)以及协议(通常为0,表示使用默认协议)。
  1. 绑定地址和端口:使用bind()函数将套接字绑定到一个本地地址和端口上,以便其他进程或计算机可以通过该地址和端口访问该套接字。
 绑定地址:- 使用`bind()`函数将Socket与本地地址和端口号绑定。- 这样,当远程主机尝试连接时,系统就知道将连接路由到这个Socket。
  1. 监听和接受连接(对于服务器端):使用listen()函数使套接字进入监听状态,并使用accept()函数接受客户端的连接请求。
监听连接:- 对于服务端Socket,使用`listen()`函数将其置于监听状态。- 这将允许远程主机发起连接请求。
  1. 连接和发送/接收数据(对于客户端):使用connect()函数连接到服务器端的套接字,并使用send()write()函数发送数据,使用recv()read()函数接收数据。
 接受连接:- 当有远程主机发起连接请求时,服务端使用`accept()`函数接受连接。- `accept()`会创建一个新的Socket描述符,用于与远程主机通信。- 原来的Socket(监听Socket)继续处于监听状态,等待新的连接请求。
  1. 关闭套接字:使用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在常规应用中较少使用,因为它需要对网络协议有深入的了解。
  1. 流式Socket(Stream Socket)基于TCP,要三次握手的那个,可靠的字节流。
  2. 数据报Socket(Dategram Socket)基于UDP,基于数据报的非可靠数据传输服务
  3. 原始Socket(Raw Socket)绕过内核协议栈,填充各级协议头直接构造数据包,常规应用不使用。

TCP通信需要先建立虚拟链路(通信双方的一个连接,connection),TCP/IP通讯下,Socket采用四元组(源IP、源端口、目的IP、目的端口)标识(identity)
i d e n t i t y identity identity

新连接
创建Socket
绑定地址
监听连接
接受连接
数据传输
关闭连接
(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 用于指定一个通信域;这将选择将用于通信的协议族。
参数 domain 用于指定一个通信域

对于 TCP/IP 协议来说,通常选择 AF_INET 就可以了,当然如果你的 IP 协议的版本支持 IPv6,那么可以选择 AF_INET6。

t y p e type 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 参数告诉内核使用这个数值作为队列的上限。而当一个客户端的连接请求到达并且该队列为满时,客户端可能会收到一个表示连接失败的错误,本次请求会被丢弃不作处理。

  1. 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 以指示错误原因。

  1. 发送和接收函数
    一旦客户端与服务器建立好连接之后,我们就可以通过套接字描述符来收发数据了(对于客户端使用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 可以指定一些标志用于控制如何接收数据。
rec

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

在这里插入图片描述

数据的传输

在网络编程中,数据的传输是核心功能之一。数据传输涉及到许多概念和技术,包括阻塞非阻塞I/O、I/O复用等。这些概念和技术对于理解和优化网络性能至关重要。

1. 阻塞与非阻塞

阻塞I/O:在阻塞I/O模型中,当应用程序发起一个读或写请求时,如果数据还没有准备好,应用程序将被阻塞,直到数据准备好为止。在这个过程中,应用程序无法执行其他任务。这种模型简单易懂,但对于需要同时处理多个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

参考文献:


  1. Linux 的 SOCKET 编程详解,hguisu ↩︎

  2. 《openEuler操作系统(第2版)》,任炬、张尧学 ↩︎

  3. Socket 编程基础,比特冬哥 ↩︎

  4. Socket 编程详解:从基本概念到实例应用(TCP|UDP C语言实例详解) ,二进制coder ↩︎

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

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

相关文章

虹科方案丨低负载ECU老化检测解决方案:CANCAN FD总线“一拖n”

来源&#xff1a;虹科汽车智能互联 虹科方案丨低负载ECU老化检测解决方案&#xff1a;CANCAN FD总线“一拖n” 原文链接&#xff1a;https://mp.weixin.qq.com/s/4tmhyE5hxeLFCiaeoRhlSg 欢迎关注虹科&#xff0c;为您提供最新资讯&#xff01; #汽车总线 #ECU #CAN卡 导读 …

Linix与Windows上使用nc命令测试某一个服务器端口网络是否正常可访问详细安装及测试步骤

一、windows 1、下载nc安装包 https://nszyf.lanzoum.com/ihtqS0v0lwwh 2、下载后解压放置在自己电脑合适的位置&#xff0c;并且配置到环境变量中 3、配置成功环境变量&#xff0c;winr打开运行&#xff0c;输入cmd&#xff0c;回车&#xff0c;打开一个终端测试 测试成功…

【9-1】实验——Neo4j实战操作

目录 一、Neo4j操作——CQL 1、常用CQL命令 2.常用CQL函数 3.图数据的形式 二、实战代码1.create命令 2. MATCH命令 三、使用neo4j工具导入知识图谱 1、工具&#xff1a;neo4j-admin 2、图谱导入&#xff1a; 3、更新图谱&#xff1a; 一、Neo4j操作——CQL 1、常用…

JAVA高并发——锁的优化

文章目录 1、减少锁持有时间2、减小锁粒度3、用读写分离锁来替换独占锁4、锁分离5、锁粗化 锁是最常用的同步方法之一。在高并发的环境下&#xff0c;激烈的锁竞争会导致程序的性能下降&#xff0c;因此我们有必要讨论一些有关锁的性能的问题&#xff0c;以及一些注意事项&…

CSS-基础-MDN文档学习笔记

CSS构建基础 查看更多学习笔记&#xff1a;GitHub&#xff1a;LoveEmiliaForever MDN中文官网 CSS选择器 选择器是什么 CSS 选择器是 CSS 规则的第一部分&#xff0c;它用来选择HTML元素&#xff0c;选择器所选择的元素&#xff0c;叫做选择器的对象 选择器列表 如果有多…

Android轻量级进程间通信Messenger源码分析

一. 概述 Android中比较有代表性的两大通信机制&#xff1a;1. 线程间Handler通信 2. 进程间Binder通信&#xff0c;本篇文章中我们在理解AIDL原理的基础上来解读一下Messenger的源代码&#xff0c; 并结合示例Demo加深理解。 在看本篇文章前&#xff0c;建议先查阅一下笔者的…

举例说明什么是人机耦合

在呼叫中心行业&#xff0c;人机耦合是指将计算机自动化技术与人工服务相结合&#xff0c;以提高呼叫中心的效率和服务质量。具体来说&#xff0c;它包括通过智能语音识别、自然语言处理、机器学习等技术实现自动应答、自动导航、自动响应等功能&#xff0c;以及将人工客服与智…

【C++】类与对象(构造函数、析构函数、拷贝构造函数、常引用)

&#x1f308;个人主页&#xff1a;秦jh__https://blog.csdn.net/qinjh_?spm1010.2135.3001.5343&#x1f525; 系列专栏&#xff1a;http://t.csdnimg.cn/eCa5z 目录 类的6个默认成员函数 构造函数 特性 析构函数 特性 析构的顺序 拷贝构造函数 特性 常引用 前言 &…

力扣94 二叉树的中序遍历 (Java版本) 递归、非递归

文章目录 题目描述递归解法非递归解法 题目描述 给定一个二叉树的根节点 root &#xff0c;返回 它的 中序 遍历 。 示例 1&#xff1a; 输入&#xff1a;root [1,null,2,3] 输出&#xff1a;[1,3,2] 示例 2&#xff1a; 输入&#xff1a;root [] 输出&#xff1a;[] 示…

微信小程序-绑定数据并在后台获取它

如图 遍历列表的过程中需要绑定数据&#xff0c;点击时候需要绑定数据 这里是源代码 <block wx:for"{{productList}}" wx:key"productId"><view class"product-item" bindtap"handleProductClick" data-product-id"{{i…

Vue3实现带动画效果的tab栏切换

效果图如下所示&#xff1a; 实现思路&#xff1a; 其实很简单 1、首先切换tab栏时tab标签激活下标与对应显示内容下标要一致。 2、其次点击tab栏切换时更新下标 3、最后就是css添加动画效果 这样就了&#xff01;&#xff01;&#xff01; 上全部代码 <template><…

Profibus转ModbusRS485网关在空调系统应用

随着我国工业自动化整体水平的不断提高&#xff0c;企业中的控制系统和控制设备的种类越来越多&#xff1b;同时随着市场经济的发展&#xff0c;各个企业也对DCS系统能将控制系统的各个运行参数实时传送到上位机的系统中进行加工处理&#xff0c;这对DCS系统提出了通讯问题。开…

《Solidity 简易速速上手小册》第5章:智能合约的安全性(2024 最新版)

文章目录 5.1 安全性的重要性5.1.1 基础知识解析深入理解安全性的多维度影响智能合约安全的关键要素 5.1.2 重点案例&#xff1a;防止重入攻击案例 Demo&#xff1a;构建一个防重入的提款合约案例代码WithdrawContract.sol 测试和验证拓展功能 5.1.3 拓展案例 1&#xff1a;预防…

Day50 739每日温度 496下一个更大元素I 503下一个更大元素II

739 每日温度 请根据每日 气温 列表&#xff0c;重新生成一个列表。对应位置的输出为&#xff1a;要想观测到更高的气温&#xff0c;至少需要等待的天数。如果气温在这之后都不会升高&#xff0c;请在该位置用 0 来代替。 例如&#xff0c;给定一个列表 temperatures [73, 7…

面试经典150题——生命游戏

​"Push yourself, because no one else is going to do it for you." - Unknown 1. 题目描述 2. 题目分析与解析 2.1 思路一——暴力求解 之所以先暴力求解&#xff0c;是因为我开始也没什么更好的思路&#xff0c;所以就先写一种解决方案&#xff0c;没准写着写…

22-k8s中pod的调度-亲和性affinity

一、概述 在k8s当中&#xff0c;“亲和性”分为三种&#xff0c;节点亲和性、pod亲和性、pod反亲和性&#xff1b; 亲和性分类名称解释说明nodeAffinity节点亲和性通过【节点】标签匹配&#xff0c;用于控制pod调度到哪些node节点上&#xff0c;以及不能调度到哪些node节点上&…

Linux-ls命令

目录 ls&#xff1a;查看目录下文件/文件夹 ls -l&#xff1a;列表显示文件 ls -a&#xff1a;显示所有文件正常情况下‘ . ’开头的文件是隐藏的 ls -la&#xff1a;以列表形式显示所有文件包括隐藏文件 ls -lt&#xff1a;按时间倒序查看文件 ls -R&#xff1a;递归方式…

【git 使用】超级好用的 git reset 和 git revert 功能对比和使用方法

首先你要知道 git 区分暂存区和工作区&#xff0c;如果你用过 sourcetree 你就会知道 git reset 超级好用 git reset 命令用于将当前分支的 HEAD 指针移动到指定的提交&#xff0c;并且可以选择性地修改工作区和暂存区的状态。git reset 命令有几种常用的用法&#xff0c;主要…

突破百度地图Web API的配额限制,实现接口调用自由!

声明 本文章中所有内容仅供学习交流,抓包内容、敏感网址、数据接口均已做脱敏处理,严禁用于商业用途和非法用途,否则由此产生的一切后果均与作者无关,若有侵权,请联系我立即删除! 引言 好久没用百度地图开放平台,最近发现平台调整了接口调用的策略,增加了实名认证,…

STM32 USART详细解读(理论知识)

文章目录 前言一、同步传输和异步传输二、UART协议三、UART硬件结构1.波特率&#xff0c;数据位&#xff0c;校验位&#xff0c;停止位设置2.数据发送流程3.数据接收流程4.中断控制 总结 前言 本篇文章来给大家讲解一下STM32中的USART&#xff0c;USART是STM32中非常重要的一个…