好的企业型网站模板,鹰潭网站设计,建程网的工程好拿钱吗,1688登陆首页登录10min速通Socket 套接字简介数据传输基本模型1.TCP/IP模型2.UDP模型 套接字类型套接字#xff08;Socket#xff09;编程Socket 的连接1.连接概述(1)基本概念(2)连接状态(3)连接队列 2.建立连接3.关闭连接 socket 编程接口介绍数据的传输1. 阻塞与非阻塞2. I/O复用 数据的传输… 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以及不同计算机之间的网络通信。
数据传输基本模型
套接字通常基于两种基本的数据传输模型面向连接的传输TCPTransmission Control Protocol和无连接的传输UDPUser Datagram Protocol。
1.TCP/IP模型
TCP/IP模型是一个四层模型它包括应用层、传输层、网络层和链路层。 应用层负责处理用户的应用程序如Web浏览器、FTP客户端等。应用层协议定义了应用程序如何通过套接字接口使用网络服务。常见的应用层协议有HTTP、FTP、SMTP等。 传输层负责数据的可靠传输。TCP是传输层的主要协议它提供了面向连接的、可靠的、基于字节流的传输服务。TCP通过三次握手建立连接并通过序列号确保数据的顺序性和可靠性。 网络层负责将数据包从源地址路由到目的地址。IPInternet 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也就是 流式SocketStream Socket 基于TCPTransmission Control Protocol传输控制协议的Socket。提供面向连接的、可靠的、基于字节流的传输服务。数据在发送和接收时保持顺序无重复并且无丢失。需要三次握手建立连接四次挥手断开连接。 数据报SocketDatagram Socket 基于UDPUser Datagram Protocol用户数据报协议的Socket。提供无连接的、不可靠的、基于数据报的服务。数据在发送和接收时可能不保持顺序也可能出现重复或丢失。不需要建立和维护连接适用于对实时性要求较高但数据可靠性要求不高的场景。 原始SocketRaw Socket 允许应用程序直接操作底层协议绕过内核协议栈。开发者可以自定义协议头直接构造数据包。这类Socket在常规应用中较少使用因为它需要对网络协议有深入的了解。 流式SocketStream Socket基于TCP要三次握手的那个可靠的字节流。数据报SocketDategram Socket基于UDP基于数据报的非可靠数据传输服务原始SocketRaw Socket绕过内核协议栈填充各级协议头直接构造数据包常规应用不使用。 TCP通信需要先建立虚拟链路通信双方的一个连接connectionTCP/IP通讯下Socket采用四元组源IP、源端口、目的IP、目的端口标识identity i d e n t i t y identity identity #mermaid-svg-0oLHrX5FvMKlxwET {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-0oLHrX5FvMKlxwET .error-icon{fill:#552222;}#mermaid-svg-0oLHrX5FvMKlxwET .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-0oLHrX5FvMKlxwET .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-0oLHrX5FvMKlxwET .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-0oLHrX5FvMKlxwET .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-0oLHrX5FvMKlxwET .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-0oLHrX5FvMKlxwET .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-0oLHrX5FvMKlxwET .marker{fill:#333333;stroke:#333333;}#mermaid-svg-0oLHrX5FvMKlxwET .marker.cross{stroke:#333333;}#mermaid-svg-0oLHrX5FvMKlxwET svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-0oLHrX5FvMKlxwET .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-0oLHrX5FvMKlxwET .cluster-label text{fill:#333;}#mermaid-svg-0oLHrX5FvMKlxwET .cluster-label span{color:#333;}#mermaid-svg-0oLHrX5FvMKlxwET .label text,#mermaid-svg-0oLHrX5FvMKlxwET span{fill:#333;color:#333;}#mermaid-svg-0oLHrX5FvMKlxwET .node rect,#mermaid-svg-0oLHrX5FvMKlxwET .node circle,#mermaid-svg-0oLHrX5FvMKlxwET .node ellipse,#mermaid-svg-0oLHrX5FvMKlxwET .node polygon,#mermaid-svg-0oLHrX5FvMKlxwET .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-0oLHrX5FvMKlxwET .node .label{text-align:center;}#mermaid-svg-0oLHrX5FvMKlxwET .node.clickable{cursor:pointer;}#mermaid-svg-0oLHrX5FvMKlxwET .arrowheadPath{fill:#333333;}#mermaid-svg-0oLHrX5FvMKlxwET .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-0oLHrX5FvMKlxwET .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-0oLHrX5FvMKlxwET .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-0oLHrX5FvMKlxwET .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-0oLHrX5FvMKlxwET .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-0oLHrX5FvMKlxwET .cluster text{fill:#333;}#mermaid-svg-0oLHrX5FvMKlxwET .cluster span{color:#333;}#mermaid-svg-0oLHrX5FvMKlxwET div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-0oLHrX5FvMKlxwET :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 新连接 创建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. **SYNSYN1, seqx**客户端发送一个SYN报文给服务器并进入SYN_SENT状态等待服务器的确认。2. **SYN-ACKSYN1, ACK1, seqy, ackx1**服务器收到SYN报文后发送一个SYN-ACK报文给客户端并进入SYN_RECV状态等待客户端的确认。3. **ACKACK1, seqx1, acky1**客户端收到SYN-ACK报文后发送一个ACK报文给服务器并进入ESTABLISHED状态表示连接已建立。服务器收到ACK报文后也进入ESTABLISHED状态。 3.关闭连接
关闭连接 当数据传输完成后使用close()函数关闭Socket。对于服务端可能需要显式关闭监听Socket以停止接受新的连接。
TCP连接的关闭通常通过四次挥手来完成1. **FINFIN1, sequ**应用程序调用close()函数关闭连接客户端发送一个FIN报文给服务器并进入FIN_WAIT_1状态等待服务器的确认。2. **ACKACK1, seqv, acku1**服务器收到FIN报文后发送一个ACK报文给客户端并进入CLOSE_WAIT状态表示已知道客户端要关闭连接。3. **FINFIN1, seqw**当服务器准备好关闭连接时发送一个FIN报文给客户端并进入LAST_ACK状态等待客户端的确认。4. **ACKACK1, sequ1, ackw1**客户端收到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.h1. 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 ProtocolTCP 协议
在 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。 Tipsbind()函数并不是总是需要调用的只有用户进程想与一个具体的 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操作的轮询和调度增加了编程的复杂性。 #mermaid-svg-MgqzxjGdxHPJBCVo {font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;fill:#333;}#mermaid-svg-MgqzxjGdxHPJBCVo .error-icon{fill:#552222;}#mermaid-svg-MgqzxjGdxHPJBCVo .error-text{fill:#552222;stroke:#552222;}#mermaid-svg-MgqzxjGdxHPJBCVo .edge-thickness-normal{stroke-width:2px;}#mermaid-svg-MgqzxjGdxHPJBCVo .edge-thickness-thick{stroke-width:3.5px;}#mermaid-svg-MgqzxjGdxHPJBCVo .edge-pattern-solid{stroke-dasharray:0;}#mermaid-svg-MgqzxjGdxHPJBCVo .edge-pattern-dashed{stroke-dasharray:3;}#mermaid-svg-MgqzxjGdxHPJBCVo .edge-pattern-dotted{stroke-dasharray:2;}#mermaid-svg-MgqzxjGdxHPJBCVo .marker{fill:#333333;stroke:#333333;}#mermaid-svg-MgqzxjGdxHPJBCVo .marker.cross{stroke:#333333;}#mermaid-svg-MgqzxjGdxHPJBCVo svg{font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:16px;}#mermaid-svg-MgqzxjGdxHPJBCVo .label{font-family:"trebuchet ms",verdana,arial,sans-serif;color:#333;}#mermaid-svg-MgqzxjGdxHPJBCVo .cluster-label text{fill:#333;}#mermaid-svg-MgqzxjGdxHPJBCVo .cluster-label span{color:#333;}#mermaid-svg-MgqzxjGdxHPJBCVo .label text,#mermaid-svg-MgqzxjGdxHPJBCVo span{fill:#333;color:#333;}#mermaid-svg-MgqzxjGdxHPJBCVo .node rect,#mermaid-svg-MgqzxjGdxHPJBCVo .node circle,#mermaid-svg-MgqzxjGdxHPJBCVo .node ellipse,#mermaid-svg-MgqzxjGdxHPJBCVo .node polygon,#mermaid-svg-MgqzxjGdxHPJBCVo .node path{fill:#ECECFF;stroke:#9370DB;stroke-width:1px;}#mermaid-svg-MgqzxjGdxHPJBCVo .node .label{text-align:center;}#mermaid-svg-MgqzxjGdxHPJBCVo .node.clickable{cursor:pointer;}#mermaid-svg-MgqzxjGdxHPJBCVo .arrowheadPath{fill:#333333;}#mermaid-svg-MgqzxjGdxHPJBCVo .edgePath .path{stroke:#333333;stroke-width:2.0px;}#mermaid-svg-MgqzxjGdxHPJBCVo .flowchart-link{stroke:#333333;fill:none;}#mermaid-svg-MgqzxjGdxHPJBCVo .edgeLabel{background-color:#e8e8e8;text-align:center;}#mermaid-svg-MgqzxjGdxHPJBCVo .edgeLabel rect{opacity:0.5;background-color:#e8e8e8;fill:#e8e8e8;}#mermaid-svg-MgqzxjGdxHPJBCVo .cluster rect{fill:#ffffde;stroke:#aaaa33;stroke-width:1px;}#mermaid-svg-MgqzxjGdxHPJBCVo .cluster text{fill:#333;}#mermaid-svg-MgqzxjGdxHPJBCVo .cluster span{color:#333;}#mermaid-svg-MgqzxjGdxHPJBCVo div.mermaidTooltip{position:absolute;text-align:center;max-width:200px;padding:2px;font-family:"trebuchet ms",verdana,arial,sans-serif;font-size:12px;background:hsl(80, 100%, 96.2745098039%);border:1px solid #aaaa33;border-radius:2px;pointer-events:none;z-index:100;}#mermaid-svg-MgqzxjGdxHPJBCVo :root{--mermaid-font-family:"trebuchet ms",verdana,arial,sans-serif;} 非阻塞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在处理大量文件描述符时性能较低因为它采用轮询的方式来检查每个文件描述符的状态。
pollpoll是select的改进版本它解决了select在处理大量文件描述符时性能低下的问题。poll使用一个链表来存储文件描述符而不是使用位图这使得它在处理大量文件描述符时更加高效。
epollepoll是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 ↩︎