C++ (week6、7):Linux系统编程4:网络

文章目录

  • 四、网络和网络编程
    • (一) 网络协议
      • 1.基础概念
      • 2.网络协议和网络模型:OSI七层模型、TCP/IP四层协议
      • 3.TCP协议
        • (1)TCP协议的特点
        • (2)TCP协议的首部格式
        • (3)TCP状态图
        • (4)为什么要三次握手?2次行不行?
        • (5)为什么要四次挥手?
        • (6)快速重传
        • (7)滑动窗口机制
        • (8)WireShark抓包
        • (9)tcpdump抓包
    • (二) Socket网络编程
      • 1.网络地址的结构
      • 2.Socket编程基本原理
      • 3.TCP通信
        • (1)socket:创建监听套接字
        • (2)bind():服务器绑定IP地址和端口
        • (3)listen() :监听
          • ①DDOS攻击
        • (4)connect()
        • (5)accept()
        • (6)recv():接收数据
        • (7)send():发送数据
          • 写broken pipe:SIGPIPE信号的处理
        • (8)close()
        • (9)TIMEWAIT 和 setsockopt():设置套接字属性
        • (10)TCP通信的通用步骤
      • 4.UDP通信
        • (1)sendto()
        • (2)recvfrom()
        • (3)UDP通信
        • (4)UDP特点
    • (三) IO多路复用
      • 0.IO多路复用 (I/O multilplexing)
        • (1)5种IO模型
        • (2)读写事件(网络IO事件)
        • (3)实现大并发:多线程 / IO多路复用
          • ①一个进程最多能够创建多少个线程?
      • 1.select
        • (1)函数原型
        • (2)数据类型
        • (3)fd_set的原理
        • (4)返回值
        • (5)select的返回时机
        • (6)原理
        • (7)select的缺陷
        • (8)select 的应用场景
        • (9)select实现一对一的聊天
        • (10)select实现多人聊天室
      • 2.poll
      • 3.epoll
        • (1)epoll_create()、epoll_create1()
        • (2)epoll_ctl
        • (3)epoll_wait
        • (4)底层实现
        • (5)epoll_wait的底层实现与select有显著区别
        • (6)epoll_wait比起select的优势
        • (7)epoll_wait的两种就绪方式:水平触发和边缘触发
          • ①水平触发 (强监督)
          • ②边缘触发 (弱监督)
        • (8)设置为非阻塞式:fcntl()
        • (9)epoll实现多人聊天室
    • (四) HTTP
      • 0.互联网的诞生
      • 1.HTML
      • 2.URI/URL
      • 3.HTTP协议
      • 4.HTTP报文
        • (1)HTTP请求报文
        • (2)HTTP响应报文
        • (3)作业:http服务器,发送图片给客户端
      • 5.HTTPS的原理
        • (1)对称加密
        • (2)非对称加密
      • 6.Restful编程风格
    • (五) 进程池和线程池
      • 1.进程池
        • (1)进程池模型
        • (2)父进程的工作流程
        • (3)子进程的工作流程
      • 2.进程间传递文件描述符
        • (0)提出背景
        • (1)socketpair()
        • (2)sendmsg()
          • ①结构体msghdr
          • ②结构体 struct iovec
          • ③结构体 struct cmshdr
          • ④代码
        • (3)recvmsg()
      • 3.传输文件问题
        • (1)小文件传输:粘包问题
          • 1.TCP粘包问题
          • 2.小火车协议
        • (2)大文件传输:半包问题
          • 1.半包问题
          • 2.优化:零拷贝技术
          • 3.发送方:循环发送sendn、3种零拷贝技术
            • ①循环发送sendn
            • ②mmap
            • sendfile
            • ④splice
          • 4.接收方:循环接收recvn、waitall、mmap、splice
            • ①recvn
            • ②recv(waitall)
            • ③mmap
            • ④splice
      • 4.进程池的退出
        • (0)设计思想:异步拉起同步
        • (1)暴力退出:用kill()直接杀死子进程
        • (2)优雅退出:通知子进程exit
      • 5.线程池
  • 5 线程池
    • 5.1 模型
    • 5.2 流程
    • 5.3 数据结构
    • 5.4 接口实现
      • 5.4.1 任务队列
      • 5.4.2 线程池
      • 5.4.3 main()函数
    • 5.5 线程池退出

四、网络和网络编程

(一) 网络协议

1.基础概念

1.计算机网络发展历史

2.ifconfig命令(Windows版本是ipconfig)可以展示本地网卡的信息,其中ens33(有些系统是使用eth0)是表示以太网网卡,而lo表示loopback本地回环设备

3.发起请求的那一端称作客户端,响应的那一端称作服务端

4.ARP (地址解析协议):IP地址→MAC地址
RARP (逆地址解析协议):MAC地址→IP地址

5.DNS:域名系统,Domain Name System

6.网卡:网络接口卡,Network Interface Card,NIC

在这里插入图片描述

7.MAC帧
在这里插入图片描述


2.网络协议和网络模型:OSI七层模型、TCP/IP四层协议

这部分请查看计算机网络专栏的1-6章:https://blog.csdn.net/edward1027/category_11786323.html

1.TCP/IP协议族概览

2.以太网

3.IP协议
IPv4:Internet Protocol Version 4,互联网协议

4.TCP协议

5.UDP协议
UDP是一个数据报的协议


3.TCP协议

TCP:Transmission Control Protocol,传输控制协议

(1)TCP协议的特点

TCP是一个①可靠传输的、②面向连接的、③全双工、④具有流量控制的、⑤字节流的(流式协议)、⑥二进制的协议。

TCP协议是字节流协议,数据之间没有边界的,发生TCP粘包问题。


(2)TCP协议的首部格式

在这里插入图片描述

1.端口号

2.序号
序号:对要发送的数据进行编号,防止数据到达对端之后发生乱序的情况。
特指要发送的数据的第一个编号,之后的编号递增。
当连接建立的时候,第一个序号的生成采用的是随机算法得到。

3.确认号
TCP的确认号,是期待下一个的序号
GBN的确认号,是累计确认

剩余部分,见计网专栏第五章


(3)TCP状态图

11个状态:5 + 6
在这里插入图片描述

3次握手:5个状态
在这里插入图片描述
在这里插入图片描述

4次挥手:6个状态
在这里插入图片描述
在这里插入图片描述


(4)为什么要三次握手?2次行不行?

Q1:为什么要三次握手?2次行不行?
A1:三次握手确保了双方都能够收发数据并同步彼此的序列号,保证了通信的可靠性。
两次不行。防止失效的连接请求报文段被服务端接收,从而产生错误。

(1)情形一:客户端发送的SYN报文延迟到达
若只有2次握手,若因为网络拥塞,客户端发送的SYN报文已经超时还没收到ACK,则客户端会重发SYN报文。这次被正确接收并建立连接。连接断开后,此时第一次的SYN报文到达客户端,服务器会认为这是新的连接请求,于是发送ACK,认为连接已经建立,但此次ACK会被客户端丢弃。造成服务器空等客户端发送数据,造成资源浪费。

在这里插入图片描述


(2)情形二:服务器端发送的ACK报文丢失
客户端向服务器发送SYN报文后,服务器端返回ACK报文,并认为连接已经建立,开始等客户端传输数据。若此次ACK报文丢失,没有发送到客户端,则客户端认为连接没有建立,不会发送数据,造成服务器空等。客户端超时重发SYN,服务器也会因为认为连接已经建立而丢弃SYN报文,不予回复。

在这里插入图片描述


(5)为什么要四次挥手?

1.Q1:为什么要四次挥手?
A1:TCP是一个全双工的连接:
TCP连接是全双工的,即双向通信。每个方向都需要独立关闭,这意味着每个方向都需要发送和确认一个FIN报文段。这样可以确保双方都完成数据传输。

控制报文 + 数据报文。前两次挥手关闭一条通道,不能再发送数据报文,仅能发送控制报文。
三次握手和四次挥手,都是控制报文,不携带数据。

在这里插入图片描述


2.Q2:服务器可以主动断开连接吗?为什么?
A2:服务器可以主动断开连接,但是会进入到TIME_WAIT状态,该状态会持续2MSL的超时时间。此时服务器的资源会被浪费。因此一般情况下,不建议服务器主动断开连接。


3.Q3:为什么需要TIME_WAIT状态,该状态可以删除吗
A3:
(1)情况1:第四次挥手丢失,造成服务器无法关闭
不可以。若不等待2MSL时间,则若第四次挥手的ACK可能到达不了服务器,则服务器无法进入CLOSED状态。客户端发送ACK后,直接进入CLOSED状态,也无法接收到服务器重发的FIN。

(2)情况2:旧连接的数据被新连接接收,造成数蹿链
若不等待2MSL时间,又重新建立第二次连接。第二次建立的新连接的五元组信息,可能与第一次连接完全相同。而第一次重发的数据延迟到达,被认为是第二次的数据,造成数据蹿链


4.Q4:什么叫一个TCP连接?
A4:在操作系统看来,一个TCP连接由五元组信息组成:协议、源IP、源端口、目的IP、目的端口

在这里插入图片描述

在这里插入图片描述


5.Q5:当服务器后台出现大量的TIME_WAIT状态的连接,该怎么处理?
①缩短MSL的超时时间,RFC793规定是2分钟。实际使用时,可以修改为15秒、30秒、1分钟。
②排查服务器主动断开连接的原因。


(6)快速重传

收到三个冗余ACK,立即重传。
在这里插入图片描述

TCP的选择性确认SACK
在这里插入图片描述


(7)滑动窗口机制

在这里插入图片描述


(8)WireShark抓包

抓包的四次挥手:(实际上是按三次挥手来的,若无数据传输,则二三次挥手可以合并)

在这里插入图片描述


在这里插入图片描述


(9)tcpdump抓包

Xshell中:

1.tcpdump抓包

sudo tcpdump -i ens33 -S -w test.cap

2.下载到Windows本地

sz test.cap     

3.用wireshark解析抓的包


在这里插入图片描述



(二) Socket网络编程

1.网络地址的结构

1.网络地址结构体
(1)struct in_addr
struct in_addr:专门用于表示一个IPv4地址。

struct in_addr{uint32_t	s_addr;	  // 32-bit IPv4 address
};

(2)struct sockaddr
这是一个通用的地址结构体,用于表示各种协议族的地址,如IPv4、IPv6等。

struct sockaddr {sa_family_t sa_family; // 地址族。对于IPv4是AF_INETchar sa_data[14];      // 包含协议相关地址信息的数组
};

由于sa_data的长度固定且无法具体化为特定的地址格式,我们通常不直接使用struct sockaddr,而是使用它的具体实现,如struct sockaddr_in。


(3)struct sockaddr_in
struct sockaddr_in:用于表示一个完整的IPv4套接字地址,包括IP地址和端口号。
这是专门用于表示IPv4地址的结构体,继承了struct sockaddr,并添加了IPv4特有的字段。

struct sockaddr_in {sa_family_t sin_family;   // 地址族,IPv4使用AF_INETin_port_t sin_port;       // 端口号,使用网络字节序存储struct in_addr sin_addr;  // IPv4地址,使用struct in_addr结构体表示char sin_zero[8];         // 填充字段,使结构体大小与struct sockaddr一致
};struct in_addr{uint32_t	s_addr;	  // 32-bit IPv4 address
};

(4)填充一个网络地址

void test2(){//填充一个网络地址struct sockaddr_in addr;memset(&addr, 0, sizeof(addr));addr.sin_family = AF_INET;addr.sin_port = htons(8080);addr.sin_addr.s_addr = inet_addr("192.168.248.136");//打印网络地址printf("ip: %s, port: %d\n",inet_ntoa(addr.sin_addr),ntohs(addr.sin_port));
}

2.大端模式、小端模式
大端序(Big Endian、网络字节序):低地址低有效位,高地址高有效位
小端序(Little Endian、主机字节序):低地址低有效位,高地址高有效位

字符串不存在大小端模式


3.大小端转换:

#include <arpa/inet.h>
//h:host     n:net    s:short,16bit     l:long,32bit    
uint16_t htons(uint16_t hostshort);		//主机字节序->网络字节序
uint32_t htonl(uint32_t hostlong);		//主机字节序->网络字节序
uint16_t ntohs(uint16_t netshort);		//网络字节序->主机字节序
uint32_t ntohl(uint32_t netlong);		//网络字节序->主机字节序

4.32位网络字节序IP地址、点分十进制的IP地址 互相转换的函数
inet_ntoa():struct in_addr类型的IPv4地址 → 点分十进制字符串
inet_aton():点分十进制的IPv4地址字符串 → struct in_addr类型的二进制格式,转换的IPv4地址存储在第二个参数中。
inet_addr():点分十进制的IPv4地址字符串 → 网络字节序的二进制格式,返回值为IPv4地址
inet_ntop():二进制格式的 IPv4或IPv6 地址 → 点分十进制或冒号十六进制的字符串

#include <arpa/inet.h>char* inet_ntoa(struct in_addr in); 	
int inet_aton(const char* cp, struct in_addr* inp);
in_addr_t inet_addr(const char* cp);
const char* inet_ntop(int af, const void* src, char* dst, socklen_t size);

af:地址族(AF_INET 用于 IPv4,AF_INET6 用于 IPv6)。
src:指向包含二进制格式地址的缓冲区。
dst:指向用于存储结果字符串的缓冲区。
size:dst 缓冲区的大小。


5.域名
gethostbyname
(1)函数原型

#include <netdb.h>struct hostent *gethostbyname(const char* name);struct hostent {char  *h_name;        // 官方的主机名char **h_aliases;     // 主机名的别名列表int    h_addrtype;    // 主机地址的类型int    h_length;      // 地址的长度char **h_addr_list;   // 主机地址列表
};

(2)返回值
①成功,返回一个指向 hostent 结构的指针,该结构包含了主机名和地址信息
②解析失败,
。如果解析失败,则返回 NULL。


2.Socket编程基本原理

Socket编程基于BSD(Berkeley Software Distribution)套接字接口,它提供了一种通用的网络编程接口,使得应用程序能够通过网络进行通信。Socket编程基本原理包括以下几个步骤:

1.创建Socket:应用程序通过socket()系统调用创建一个Socket,该调用返回一个Socket描述符,用于后续的通信。
2.绑定地址:对于服务器端,需要使用bind()系统调用将Socket绑定到一个特定的IP地址和端口号上,以便客户端能够连接。
3.监听连接请求(仅适用于TCP):对于TCP服务器,需要使用listen()系统调用开始监听客户端的连接请求。
4.接受连接(仅适用于TCP):使用accept()系统调用接受客户端的连接请求,返回一个新的Socket描述符,用于与客户端进行通信。
5.发起连接(仅适用于TCP客户端):对于TCP客户端,使用connect()系统调用向服务器端发起连接请求。
6.发送和接收数据:通过send()recv()系统调用(或者对于UDP,sendto()recvfrom()系统调用)发送和接收数据。
7.关闭连接:通信结束后,使用close()系统调用关闭Socket连接。


3.TCP通信

1.服务器端:
①创建一个套接字:使用socket()系统调用创建一个TCP Socket。
②绑定地址:使用bind()系统调用将套接字Socket绑定到服务器的IP地址和端口号上。
③监听连接请求:使用listen()系统调用开始监听客户端的连接请求。
④接受连接:使用accept()系统调用接受客户端的连接请求,返回一个新的Socket描述符。
⑤发送和接收数据:使用send()recv()系统调用发送和接收数据。
⑥关闭连接:通信结束后,使用close()系统调用关闭Socket连接。

在这里插入图片描述

2.客户端:
①创建Socket:使用socket()系统调用创建一个TCP Socket。
②发起连接:使用connect()系统调用向服务器端发起连接请求。
③发送和接收数据:使用send()recv()系统调用发送和接收数据。
④关闭连接:通信结束后,使用close()系统调用关闭Socket连接。


在这里插入图片描述

在这里插入图片描述


(1)socket:创建监听套接字

1.函数原型

#include <sys/socket.h>
int socket(int domain, int type, int protocol);

①domain:AF_INET代表 IPv4,AF_INET6 代表 IPv6
②type:SOCK_STREAM代表TCP,SOCK_DGRAM代表UDP
③protocol:参数3通常为 0,表示使用默认协议。

//创建监听套接字
int listenfd = socket(AF_INET, SOCK_STREAM, 0);	//TCP协议,流式协议
socket(AF_INET, SOCK_DGRAM, 0);			   //UDP协议,包式协议,数据报

2.返回值:
①创建socket成功,返回 大于0的文件描述符fd,整型
②创建socket失败,返回-1,并设置errno

3.示意图
内核态下,服务器端和客户端各自有 接收缓冲区 和 发送缓冲区
一个套接字,对应到内核中的接收缓冲区和发送缓冲区
在这里插入图片描述


(2)bind():服务器绑定IP地址和端口

1.作用:服务器将套接字与网络地址进行绑定。
该函数只有服务器端需要进行调用,因为服务器需要将自己的网络地址暴露给客户端。
服务器的特点:被动等待请求
客户端不需要使用bind,操作系统会自动分配一个网络地址。

2.函数原型

int bind(int sockfd, const struct sockaddr* addr,socklen_t addrlen);
int ret = bind(listenfd, (const struct sockaddr*)&serveraddr, sizeof(serveraddr));

①sockfd:指定一个套接字
②addr:通用的sockaddr,实际绑定时,会使用struct sockaddr_in
③addrlen:sizeof(struct sockaddr_in)

3.返回值
①成功,返回0
②失败,返回-1,同时指定errno (全局变量)


(3)listen() :监听

1.作用:使套接字进入监听状态,等待客户端连接

listen 用于将套接字设置为监听模式,准备接受客户端连接。
accept 用于从监听套接字的连接队列中取出一个已完成的连接,并为该连接创建一个新的套接字进行通信。

2.函数原型

int listen(int sockfd, int backlog);

①参数1 sockfd:监听某一个套接字
②参数2 backlog:能够监听的连接个数,即半连接队列的数量 + 全连接队列的数量

设置为1,则可以有两个连接能建立(ESTABLISHED),第三个连接处于SYN_SENT
实际工作中,如果考虑高并发的情况,可以将backlog的值设置为一万以上的值。

3.返回值
①成功,返回0
②失败,返回-1,并设置errno

4.listen的底层原理
①半连接队列:完成第一次握手和第两次握手
②全连接队列:三次握手
在这里插入图片描述

一旦启用了 listen 之后,操作系统就知道该套接字是服务端的套接字,操作系统内核就不再启用其发
送和接收缓冲区,转而在内核区维护两个队列结构:半连接队列和全连接队列。半连接队列用于管理成功第一次握手的连接,全连接队列用于管理已经完成三次握手的队列。
backlog 在有些操作系统用来指明半连接队列和全连接队列的长度之和,一般填一个正数即可。如果队列已经满了,那么服务端受到任何再发起的连接都会直接丢弃(大部分操作系统中服务端不会回复RST,以方便客户端自动重传)


①DDOS攻击

攻击者只发送一次握手,不发第三次握手。当SYN队列中的连接数量等于backlog时,就不能再接收新的连接了。

在这里插入图片描述


(4)connect()

1.作用:客户端使用 connect 来建立和TCP服务端的连接。默认阻塞。

2.函数原型

int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);

①参数1 sockfd:客户端的套接字
②参数2 addr:指定服务器的网络地址
③参数3 addrlen:网络地址的长度

3.返回值
①成功,返回0
②失败,返回-1,并设置errno

netstat -ano | grep 8080


(5)accept()

默认阻塞,当没有客户端的连接请求,则阻塞。
accept 函数由服务端调用,用于从全连接队列中取出下一个已经完成的TCP连接。如果全连接队列为
空,那么 accept 会陷入阻塞。一旦全连接队列中到来新的连接,此时 accept 操作就会就绪。

1.函数原型

#include <sys/types.h>
#include <sys/socket.h>int accept(int sockfd, struct sockaddr* addr, socklen_t* addrlen);

①参数1 sockfd:指定监听的套接字 listenfd
②参数2 addr:传入传出参数,存放客户端的网络地址。一个指向 struct sockaddr 类型的指针,用于存储连接客户端的地址信息。如果不需要获取客户端地址信息,可以传递 NULL。
③参数3 addrlen:传入传出参数,addr的长度信息

假如不需要知道客户端的网络地址,将参数2和参数3设置为NULL

2.返回值
①成功,返回一个>0的套接字文件描述符peerfd,该套接字用于与客户端通信 (进行数据的接收和发送)。
②失败,返回-1,并设置errno

3.两类套接字:listenfd 和 peerfd
①listenfd:完成新连接的建立,执行三次握手。只有一个。[类似ftp控制连接]
②peerfd:执行数据的接收和发送。可以有多个。[类似ftp的数据连接]

int peerfd = accept(listenfd, (struct sockaddr*)&clientAddr, sizeof(clientAddr));

(6)recv():接收数据

1.作用:将内核态的接收缓冲区的数据,拷贝到用户态的接收缓冲区

2.函数原型

#include <sys/types.h>
#include <sys/socket.h>ssize_t recv(int sockfd, void *buf, size_t len, int flags);

(1)参数
①参数1 sockfd :Socket描述符。服务器端用peerfd接收数据; 客户端用clientfd来接收数据
②参数2 buf :指定用户态的接收缓冲区,用于存储接收数据的缓冲区。
③参数3 len:指定的是用户态接收缓冲区的大小
④参数4 flags:接收控制标志,通常为0。
特殊情况,MSG_PEEK、MSG_WAITALL、MSG_DONTWAIT等

(2)返回值
①成功,大于0,表示接收的数据长度
等于0,表示连接已断开
②失败,小于0,发生错误,并设置errno

3.recv也是阻塞式。当内核态的接收缓冲区没有数据时,recv()处于阻塞状态。

4.如何确定内核缓冲区中数据的长度:
recv的第四个参数设为MSG_PEEK
会从内核态缓冲区拷贝到用户态缓冲区,但不会移走内核缓冲区中的数据。


(7)send():发送数据

1.作用:将用户态发送缓冲区的数据,拷贝到内核态的发送缓冲区

2.函数原型

ssize_t send(int sockfd, const void *buf, size_t len, int flags);

①参数1 sockfd:服务器端用peerfd发送数据; 客户端用clientfd来发送数据
②参数2 buf:用户态的发送缓冲区的首地址
③参数3 len:用户态的发送缓冲区的长度
④参数4 flags:一般为0

3.返回值
①大于等于0,表示发送的字节数。(应该<=len)
②小于0,发生错误,并设置errno

4.send默认也是阻塞式函数。当内核态的发送缓冲区满,则send()处于阻塞状态。

5.send和recv的工作原理:
在用户态和内核态之间的接收缓冲区、发送缓冲区进行数据的拷贝

在这里插入图片描述

在这里插入图片描述


写broken pipe:SIGPIPE信号的处理

6.Q:往一个已经断开的连接上继续发送数据,会造成什么影响?
A:第一次send可以正常运行,第二次send使服务器程序直接崩溃。
原理:第一次send时,由于客户端已经断开,内核会给服务器返回一个RST
第二次send,返回SIGPIPE信号,是写broken pipe行为。导致进程崩溃。

原因:第一次send时,还不知道连接已经断开了。这时内核会给服务器返回一个RST报文。
故第二次send,服务器进程会收到SIGPIPE信号,默认处理方式:直接导致进程崩溃

解决方案:在服务器程序的开始位置,使用 signal()将SIGPIPE信号忽略

//直接忽略掉SIGPIPE信号 (注册SIGPIPE信号)
signal(SIGPIPE, SIG_IGN);

(8)close()

1.作用
①close 函数用于关闭文件描述符,包括套接字描述符,并释放与之相关的所有系统资源。
②套接字是用文件描述符表示的,文件描述符是一种稀缺资源,不用的情况下要记得回收,否则很可能会耗尽文件描述符

2.函数原型

int close(int fd);

3.返回值
成功:如果关闭成功,close 函数返回 0。
失败:如果关闭失败,close 函数返回 -1,并设置 errno 以指示错误类型。


(9)TIMEWAIT 和 setsockopt():设置套接字属性

1.现象:
当服务器主动断开连接时,会进入TIME_WAIT状态,此时再想启动服务器进程,会报错:bind:Address already in use

解决:
设置在bind()之前

#include <sys/types.h>
#include <sys/socket.h>int setsockopt(int sockfd, int level, int optname, const void *optval, socklen_t optlen);

举例:

//设置套接字属性,网络地址可重用
int reuse = 1; //1表示有效
int ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));
if(ret == -1)   error(1, errno, "setsockopt");

在这里插入图片描述
在这里插入图片描述


(10)TCP通信的通用步骤

1.server.c
①创建监听套接字
②填充网络地址
③设置网络地址可重用
④绑定网络地址
⑤监听

int tcpInit(const char* ip, const char* port){//创建监听套接字int listenfd = socket(AF_INET, SOCK_STREAM, 0);if(listenfd == -1)  error(1, errno, "socket");//填充网络地址struct sockaddr_in serveraddr;memset(&serveraddr, 0, sizeof(serveraddr));serveraddr.sin_family = AF_INET;serveraddr.sin_port = htons(atoi(port));serveraddr.sin_addr.s_addr = inet_addr(ip);//设置网络地址可重用int reuse = 1;int ret = setsockopt(listenfd, SOL_SOCKET, SO_REUSEADDR, &reuse, sizeof(reuse));if(ret == -1)   error(1, errno, "setsockopt");//绑定网络地址ret = bind(listenfd, (const struct sockaddr*)&serveraddr, sizeof(serveraddr));if(ret == -1)   error(1, errno, "bind");//监听ret = listen(listenfd, 10);if(ret == -1)   error(1, errno, "listen");return listenfd;
}

4.UDP通信

服务器端和客户端:
①创建Socket:使用socket()系统调用创建一个UDP Socket。
②绑定地址(服务器端):使用bind()系统调用将Socket绑定到服务器的IP地址和端口号上。
③发送和接收数据:使用sendto()recvfrom()系统调用发送和接收数据。
④关闭Socket:通信结束后,使用close()系统调用关闭Socket连接。

在这里插入图片描述


①UDP是一种保留消息边界的协议,无论用户态空间分配的空间是否足够 recvfrom 总是会取出
一个完整UDP报文,那么没有拷贝的用户态内存的数据会直接丢弃。
sendto几次,就需要recvfrom几次。
②服务器需要先接收一次客户端的消息,才能得到客户端的网络地址。


(1)sendto()

1.函数原型

ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);

举例:

int ret = sendto(serverfd, buff2, strlen(buff2), 0, (const struct sockaddr*)&clientaddr, sizeof(clientaddr));

(2)recvfrom()
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);

recvform()默认是阻塞式的


(3)UDP通信
(4)UDP特点

1.Q:UDP是一个无连接的协议,那如何保证数据的可靠到达呢?
A:UDP是一个传输层的协议,即在传输层无法解决数据可靠传输的特性,要达到数据可靠传输的效果,只能向上层抛出,交给上层处理,即交给应用层处理。因此UDP通信要比TCP难得多。

应用层必须要将TCP的功能实现一次。

2.Q2:UDP的使用场景
A:UDP无法保证数据的可靠到达,可以应用在允许数据丢失(丢包)的场景:做直播,实时会议,游戏 等 对网络要求不高的场合。



(三) IO多路复用

0.IO多路复用 (I/O multilplexing)

(1)5种IO模型

阻塞IO:[类似独占查询]
非阻塞式IO:配合轮询。[类似定时查询]
IO多路复用:监听多个I/O事件,将多个阻塞点变成一个阻塞点。select、poll、epoll
信号驱动IO:阻塞点主动发生信号,是异步方式。[类似中断]
异步IO:不需要CPU主动处理 [类似DMA方式]

在这里插入图片描述


(2)读写事件(网络IO事件)

①当内核的接收缓冲区中有数据时,表示该套接字可读,即读事件已就绪。
②当内核的发生缓冲区非满,还有空间,表示该套接字可写,即写事件已就绪。

标准输入:缓冲区有数据,可读
磁盘文件:open,fd读入内核缓冲区,表示可读


(3)实现大并发:多线程 / IO多路复用
①一个进程最多能够创建多少个线程?

每一个线程都有自己独立的栈空间,32位系统的虚拟地址空间为4G,内核态占1G,用户态占3G。则最多有3G/线程栈空间大小

ulimit -a,查看线程栈空间大小

在这里插入图片描述


1.select

一句话介绍:select是实现I/O多路复用的机制之一,将多个阻塞点变为一个阻塞点


(1)函数原型
#include <sys/select.h>
int select(int nfds, fd_set *readfds, fd_set *writefds,fd_set *exceptfds, struct timeval *timeout);
select(6, &readfds, &writefds, NULL, &timeout)

nfds:监听的最大文件描述符+1 [为了内核提升效率,只检查前几nfds个文件描述符]
readfds:监听的读事件的集合,不需要设为NULL。[传入(调用时),表示对哪些文件描述符的读事件感兴趣 ;传出(函数返回时),读事件已就绪的文件描述符。]
writefds:监听的写事件的集合,不需要设为NULL。[传入(调用时),表示对哪些文件描述符的写事件感兴趣 ;传出(函数返回时),写事件已就绪的文件描述符。]
exceptfds:监听异常事件的集合,不需要设为NULL。[传入(调用时),表示对哪些文件描述符的异常事件感兴趣 ;传出(函数返回时),发生异常事件的文件描述符。]
timieout:指定等待时间的结构体。如果为NULL,select将无限期等待;如果设置了时间值,select将在指定时间后返回。超时时间,最多阻塞的时间长度。[超过就不要了]。
i.定时等待:{秒,微秒}
ii.无限等待:若为NULL,则无限期阻塞
iii.立即返回:若为{0,0},则不阻塞,立刻返回

timieout也是传入传出参数,传入时是超时时间,传出时是剩余时间。返回值为0,则表示超时(时间用完,但没有时间就绪)。

fds:文件描述符集合 (file descriptor set)

select是同步的,当select返回时,说明有事件就绪了。【中途会切换到其他进程,到下次select检查时,若有一个或多个事件就绪,就返回。若超时,返回0。若未超时,继续切换其他进程等待(事件就绪)】
多个阻塞点,变成了select一个阻塞点


(2)数据类型

fd_set 是一个用于描述一组文件描述符(file descriptors)的数据结构,通常用于select系统调用,以监控多个文件描述符的状态(如可读、可写和异常条件)

select 会在返回时修改传入的 fd_set 集合,只保留那些发生了指定事件的文件描述符。对于没有事件发生的文件描述符,它们在集合中的位置会被清除(即置为 0)【刚开始监听的位图全部置为1,未就绪的事件会被select清除为0】

(1)fd_set:传入传出参数(指针),大小为1024的位图
①FD_ZERO(&set)
②FD_SET(fd, &set)
③FD_ISSET(fd, &set)
④FD_CLR(fd, &set)

(2)struct timeval:{tv_sec, tv_usec}


(3)fd_set的原理

fd_set是一个位图,大小为1024bit,实际上就是一个数组

typedef struct {unsigned long fds_bits[1024 / (8 * sizeof(unsigned long))];
} fd_set;

FD_ZERO:初始化 fd_set,将其所有位清零。

void FD_ZERO(fd_set *set);

FD_SET:在 fd_set 中添加一个文件描述符。

void FD_SET(int fd, fd_set *set);

FD_CLR:从 fd_set 中移除一个文件描述符。

void FD_CLR(int fd, fd_set *set);

FD_ISSET:检查 fd_set 中是否包含某个文件描述符。

int FD_ISSET(int fd, fd_set *set);

(4)返回值

①成功,返回就绪事件的个数。
如果超时,返回0
③失败,返回-1,并设置errno


(5)select的返回时机

①有事件就绪
②超时时间到达
③被信号中断
在这里插入图片描述

(6)原理

select 是一种系统调用,用于在多个文件描述符上进行多路复用,以便监视多个文件描述符的可读、可写或异常状态。


(7)select的缺陷

监听数量有限:监听的文件描述符的个数是有限的。fd_set是大小为1024的位图,最大只能监听1024个fd。
②内核中的轮询机制、用户态的轮询机制
效率低:返回值只能表示就绪的事件数量,但不知道具体是哪个事件就绪。需要遍历fd_set,找到就绪的文件描述符。时间复杂度为O(n)。(若场景为10万个事件在连接,但只有10个事件就绪,也需要遍历10万个事件)
③复制开销:每次调用 select 都需要将文件描述符集合fd_set从用户态空间复制到内核态空间,修改完后再从内核态空间复制回态用户空间,开销较大。

每次调用完select函数之后,rdset都会被修改
内核的轮询机制

在这里插入图片描述


(8)select 的应用场景

select 主要用于网络服务器和客户端,允许在单线程中高效处理多个连接或文件描述符的 I/O 操作。

select 主要用于需要同时监视多个文件描述符的场景,如网络服务器需要同时处理多个客户端连接。通过使用 select,程序可以在一个线程中处理多个连接,而不需要为每个连接创建一个线程,从而减少资源开销。


(9)select实现一对一的聊天

select监听,将多个阻塞点变为一个阻塞点。
①fd_set 通常与 select 函数一起使用,以实现非阻塞的 I/O 操作

github代码:https://github.com/WangEdward1027/MultiplayerChatRoom/tree/main/select


(10)select实现多人聊天室

用一个数组,保存 listenfd、已经建立连接的peerfd。
服务器只需要进行回显,不用监听STDIN_FILENO

int conn[1000] = {0};

引入数组的原因:比起位图,方便遍历。数组里直接保存里fd,而位图中不方便获得为1的那个fd是第几位。

数组保存所有活跃的文件描述符,而fd_set用于select函数的调用。这样在遍历和操作时使用数组,而在监视文件描述符状态变化时使用fd_set,两者结合可以充分利用各自的优势。


2.poll

底层实现:链表
监听数量没有上限。但是内核机制也是要遍历每一个fd


3.epoll

e是event事件,poll是轮询


(1)epoll_create()、epoll_create1()

1.函数原型
(1)epoll_create()、epoll_create1()
作用:创建epoll实例

#include <sys/epoll.h>int epoll_create(int size);
int epoll_create1(int flags);
int epfd = epoll_create1(0);

参数flags 一直指定为0
返回值:
①成功,返回大于0的文件描述符epfd
②失败,返回-1


(2)epoll_ctl

1.作用:增删改内核的红黑树

2.函数原型

#include <sys/epoll.h>int epoll_ctl(int epfd, int op, int fd, struct epoll_event* event);

举例:

struct epoll_event ev;
memset(&ev, 0, sizeof(ev));
ev.events = EPOLLIN;    //套接字可读, 关注listenfd的读事件
ev.data.fd = listenfd;
epoll_ctl(epfd, EPOLL_CTL_ADD, fd, &ev);

①参数1:epfd指定epoll实例
②参数2:op操作
EPOLL_CTL_ADD:增加一个监听,添加一个fd到监听红黑树中,
EPOLL_CTL_MOD:修改一个监听
EPOLL_CTL_DEL:删除一个监听
③参数3:fd 指定要处理的文件描述符
④参数4 :指的是fd对应的value。参数3和参数4是键值对信息,要挂到红黑树上。

typedef union epoll_data{void    *ptr;int      fd;uint32_t u32;uint64_t u64;
} epoll_data_t;struct epoll_event {uint32_t events;    // 要监听的事件类型epoll_data_t data;  // 用户数据
};

要监听的事件:
EPOLLIN 读事件,0x001
EPOLLIOUT 写事件,0x004
EPOLLIET 边缘触发,1u << 31
EPOLLERR 发生错误

联合体 union
n个字段,共享同一片地址空间。但同一时间只能表示一个字段。

3.返回值:
①成功,返回0
②失败,返回-1,并设置errno


(3)epoll_wait

1.作用:监听响应
阻塞等待,阻塞式函数

#include <sys/epoll.h>int epoll_wait(int epfd, struct epoll_event* events,int maxevents, int timeout);

①参数1:epfd 指定 epoll的实例
②参数2:events 代表的是用户态空间的struct epoll_event数组的首地址
③参数3:maxevents 数组的长度
④参数4:timeout是超时时间,-1为无限期等待。单位是毫秒ms

返回值:
①大于0,表示的是就绪的文件描述符的数量。
返回值n>0,需要处理两类文件描述符:
在这里插入图片描述

②等于0,epoll_wait超时
③小于0,发生错误,返回-1,并设置errno


(4)底层实现

内核态维护红黑树就绪链表

①用红黑树保存监听事件的集合,没有数量上限。
②用就绪链表保存已经就绪的事件集合
③epoll_wait不需要重复监听文件描述符,永久化注册文件描述符。因为红黑树在内核中。

events数组就是由内核的就绪链表而来的。

在这里插入图片描述


(5)epoll_wait的底层实现与select有显著区别

(1)select要在内核主动轮询所有fd,判断是否发生了事件
(2)epoll的底层实现有一个回调机制。一旦监听的某一个文件描述符发生了事件,会主动通知epoll进行处理。大大提升了效率。


(6)epoll_wait比起select的优势

①事件驱动:epoll 使用事件驱动机制,只有在有事件发生时才通知用户程序,避免了无效的扫描。
在用户态对于要处理的文件描述符可以精确的知道,不需要遍历所有已经建立好的连接。
②epoll_wait不需要重复监听文件描述符。
③由于内核使用红黑树进行存储,没有文件描述符上限的限制
④内核空间直接操作:epoll在内核态操作红黑树和就绪链表,减少了用户空间和内核空间之间的复制开销。
⑤边缘触发模式:epoll 支持边缘触发(edge-triggered)模式,进一步减少系统调用次数,提高效率。


(7)epoll_wait的两种就绪方式:水平触发和边缘触发
①水平触发 (强监督)

EPOLLLT,level trigger,强监督
内核缓冲区有数据,就一直通知:
当有事件发生时,内核的接收缓冲区有数据,如果不去处理,epoll_wait会一直返回,通知相应的连接进行处理

在这里插入图片描述


②边缘触发 (弱监督)

EPOLLET,edge-triggered,弱监督
内核缓冲区收到新数据,才通知一次:
当内核的接收缓冲区收到数据时,会通知一次。如果不去接收数据,epoll也不会再通知处理。
直到内核的接收缓冲区又收到了新的数据,才会再次触发,epoll_wait再返回一次。

ev.events = EPOLLIN | EPOLLET;

在这里插入图片描述

epoll_wait 的就绪触发有两种方式:一种是默认的水平触发方式(Level-triggered),另一种是边缘触发
模式(Edge-triggered)。
以读事件为例子:水平触发模式下,只要缓冲区当中存在数据,就可以使epoll_wait 就绪;在边缘触发的情况下,如果缓冲区中存在数据,但是数据一直没有增多,那么epoll_wait 就不会就绪,只有缓冲区的数据增多的时候,即下图中绿色的上升沿部分时,才能使epoll_wait 就绪。

在这里插入图片描述


(8)设置为非阻塞式:fcntl()

1.将一个文件描述符设置为非阻塞
一旦peerfd设置为非阻塞之后,调用recv/send就不会再阻塞
在这里插入图片描述


2.只希望某一次调用recv函数时,采用非阻塞,可以设置第四个参数MSG_DONTWAIT


(9)epoll实现多人聊天室

github代码:https://github.com/WangEdward1027/MultiplayerChatRoom/tree/main/epoll_new



(四) HTTP

0.互联网的诞生

1989年,蒂姆·伯纳斯-李(Tim Berners-Lee)提出了互联网超链接文档系统的三大技术:
①HTML:超文本标记语言,是一棵DOM(document)树,对文档进行展示
②URI/URL:统一资源定位符,对文档进行定位
③HTTP:超文本传输协议,对文档进行传输

蒂姆把这个系统称为“万维网”(World Wide Web),也就是我们现在所熟知的 Web。


1.HTML

超文本标记语言(HTML5,Hypertext Markup Language),是一棵DOM(文档, document)树,对文档进行展示

<html><head> <title> ... </title>... </head><body><form>...</form></body>
</html>

Web文档:一般用HTML文件描述,布局描述文件是CSS文件,网页脚本文件则是由JavaScript语言
编写的程序。

网页的构成:网页三剑客
(1)HTML:超文本标记语言(Hyper Text Markup Language),是一棵DOM (文档, document)树,有n个结点 [网页的骨架]
(2)CSS:层叠样式表(Cascading Style Sheets),来描述一个网页、HTML文件、XML文件的表现与展示效果,用来对文档进行布局,解决了结点的样式 (字体大小、颜色、位置) [网页的面貌、布局]
(3)JavaScript:脚本语言 (动态语言) [灵魂]
①脚本语言,动态地修改DOM树上结点的信息
②又是动态语言,不需要编译,直接解释执行


2.URI/URL

1.URI统一资源标识符,Uniform Resource Identifier。URI的两种子类型,URL和URN(Uniform Resource Name)。
URL统一资源定位符,Uniform Resource Locator。URL不仅标识资源,还提供了访问资源的具体方法。

2.URI/URL的基本结构:
(1)方案(scheme):指定使用的协议,如http、https、ftp等
(2)用户信息(User Information):包括用户名和密码,格式为username:password@,此部分经常被忽略
(3)主机(host):资源所在的主机,域名 或 IP地址
(4)端口号(port):指定访问资源的端口号
(5)路径(path):指定资源所在的具体路径,例如 /path/to/resource
①静态资源:直接存储在服务器上的文件,*.html、*.gif、*.jpg、*.mp4
②动态资源:并没有直接存储在服务器上,而需要经过业务逻辑的计算后得到的
(6)查询(querry):查询字符串,用于传递参数。以?开头,以键值对形式出现 key = value,多个查询词之间用 & 进行连接。例如:?key1=value1&key2=value2
(7)片段(fragment):指定资源内部的一个部分。前面的连接符是 # ,分段字段和服务端无关,客户端用其来定位网页的阅读起始位置

在这里插入图片描述



举例:
例1:

https://username:password@www.example.com:8080/path/to/resource?key1=value1&key2=value2#section1

①方案 (Scheme):https
://
②用户信息 (User Info):username:password@
③主机 (Host):www.example.com
:
④端口 (Port):8080
⑤路径 (Path):/path/to/resource
?
⑥查询 (Query):key1=value1&key2=value2
#
⑦片段 (Fragment):section1

例2:

https://www.baidu.com/s?wd=同济大学

s代表search,wd代表word



3.HTTP协议

1.HTTP,超文本传输协议,HyperText Transfer Protocol
(1)超文本,不仅仅是文本,还包含图片、视频、二进制信息
(2)传输:通信的双方基于C-S模型(Client-Server),一个请求对应一个响应。
(3)协议:HTTP是应用层协议,基于传输层的TCP协议


2.HTTP协议的特点:C/S模型、可靠、无状态、文本协议、明文传输的
(1)客户端-服务端模型 (C/S模型)
设计思想:函数调用/返回,本地和远程类似 RPC(远程过程调用,Remote Procedure Call)
在这里插入图片描述

(2)可靠性:基于TCP协议进行传输

(3)无状态协议:每一个HTTP请求都是独立的,不依赖于上下文。
将HTTP设计为无状态协议的好处:
①简化服务器的设计
②在进行扩展时,可以方便实现大并发
③MVC模型


①简化实现:每个请求独立,服务器无需维护复杂的会话管理逻辑,从而简化了服务器的实现和维护。
②易于扩展、负载均衡、故障恢复:易于横向扩展,即增加更多服务器来处理请求。易于实现负载均衡。某个服务器出现故障,客户端可以轻松重试请求或将请求发送到另一个服务器。
在这里插入图片描述

③系统架构:三层架构,MVC模型
1>视图展示层:VIEW
2>业务逻辑层:CONTROL
3>数据持久层:MODEL

在这里插入图片描述

客户端有需要一些技术支撑:
token技术
cookie技术
session技术
Local Storage技术


(4)文本协议:报文头是文本的(字符串)
在有些情况下,可以对HTTP传输的数据进行加密。 TLS/SSL就是一种加密工具,工作在HTTP和TCP之间。我们把基于TLS协议的HTTP称作HTTPS协议,它提供了更好的安全性。


HTTP协议与TCP协议的区别?
①HTTP是应用层协议,TCP是传输层协议
②HTTP协议无状态,TCP协议是有状态的
③HTTP协议本身是无连接的、无状态的。HTTP协议在发送请求时不需要建立持久连接,每个请求都是独立的。TCP协议是有连接的
④HTTP是文本协议,TCP协议是字节流的、二进制的



3.两种提升服务器性能的方式:
①垂直扩展:提升单台服务器的CPU、内存、磁盘
②水平扩展:买多台服务器,负载均衡


4.HTTP组件:客户端、服务器、代理
(1)客户端:
①浏览器:网址部分输入IP:端口号
②Postman:
③curl命令行 (Client URL):curl http://IP:端口号
(2)服务器:
①Nginx:基于事件驱动,支持高并发
②Apache:底层实现采用子进程模型。基于进程的模型,每一个请求都用一个子进程进行交互。

在这里插入图片描述

(3)代理:处于客户端与服务器之间的位置
①负载均衡:nginx本身就可以作为反向代理服务器,用来做负载均衡
②日志记录
③信息过滤
④权限控制


5.如何发起一个HTTP请求?
①通过URL发起
②通过HTML中的一些元素,如 表单
③通过JavaScript语言


6.实现一个简单的HTTP服务器
基于TCP协议,TCP套接字通信流程


4.HTTP报文

(1)HTTP请求报文

1.HTTP请求报文的组成部分:
起始行(start line) / 请求行 (Request line):方法、路径、协议版本
首部字段 (Request Headers)
空行 (empty line)
消息体 / 报文体 / 请求体 (Request Body)

HTTP请求报文的格式:

起始行   方法  路径  协议版本 \r\n
首部字段  key1:value1 \r\nkey2:value2 \r\n
\r\n (空行)
[报文体]

举例:

GET /index.html HTTP/1.1
Host: www.example.com
Accept: text/html
Connection: keep-alive

在这里插入图片描述

\r:表示回车(Carriage Return),在ASCII编码中对应的值是13,0dx
\n:表示换行(Line Feed),在ASCII编码中对应的值是10,0ax


2.HTTP常用的请求方法
①POST:向服务器提交一个新的信息 (Create)
②GET:查看服务器的一个资源 (Retrieve)。GET请求不携带消息体。
③PUT:修改服务器上的一个资源 (Update)
④DELETE:删除服务器上的一个资源 (Delete)
⑤HEAD:请求服务器的头信息。类似于GET方法,但服务器仅返回响应头,不返回响应主体。
常用于获取资源的元数据。
⑥OPTIONS
⑦PATCH
⑧CONNECT
⑨TRACE


3.POST请求与GET请求的区别
①POST请求比GET请求 多了消息体
②用途:GET用于请求数据,POST用于提交数据。
③参数位置:GET参数在URL中,POST参数在请求主体中。


4.HTTP协议版本
①0.9版本:只有一个GET方法,相当于测试版本。基于TCP。
②1.0版本:包含所有方法,但仅支持短连接。一次请求/响应结束后,TCP会断开连接。效率较低。
1.1版本:包含所有方法,支持长连接(持久连接,Connect: keep alive)。目前应用最普遍。当Connection: close 表示要断开TCP连接。复用TCP通道,在一个TCP通道中可以发生多次HTTP请求。
④2.0版本:支持二进制信息
⑤3.0版本:支持基于UDP协议传输数据。在网络稳定性越来越好的今天,不担心丢包的情况下,UDP比TCP传输更快,不需要建立连接,少了7次交互。
QUIC协议,QUIC 是 Quick UDP Internet Connections 的缩写,谷歌发明的新传输协议。


5.首部字段
Accept字段:表示客户端能够解析的媒体类型

*/*   代表能够接受任意类型
Accept:text/plain
Accept:text/html
Accept:image/png
Accept:application/json

MIME类型

类型/子类型

6.消息体 / 报文体(body)
(1)GET请求不携带报文体,发给服务器消息比较少时用GET请求,使用查询词

(2)POST请求携带消息体
消息体的三种类型:
raw类型:text/plain、application/json (JSON格式字符串)
[key:content-type,value是text/plain 或 application/json]
②application/x-www-form-urlencoded:携带的数据量较小
form-data:传传输文件可以有多个(文件、图片)。传递的数据量较大。

Q:form-data的边界信息(boundary)起什么作用,有什么限制?
A:当传递文件时,为了获取文件的起始位置和结束位置,就加上了边界信息。
boundary起到了分隔不同部分数据的作用。每个部分都包含自己的头部信息和内容,boundary字符串用来明确区分这些部分,以便接收方能够正确解析和处理每个部分的数据。
限制:文件内容不能包含边界

在这里插入图片描述


(2)HTTP响应报文

1.HTTP响应报文的组成:
起始行(start line) / 状态行(Status Line)
首部字段 / 响应头部字段 (Response Headers):Server、Content-Type、Content-Length
空行 (Empty Line)
消息体 / 响应主体 (Response Body)

HTTP响应报文的格式:

起始行  协议版本  状态码 原因短语 \r\n
响应头部字段 
Server:	\r\n
Content-Type: \r\n
Content-Length: \r\n
\r\n  (空行)
响应数据

在这里插入图片描述


2.HTTP状态码
①1xx:连接建立中的情况
②2xx:连接成功,200表示OK;
③3xx:重定向,301 Moved Permanently:请求的资源已永久移动到新位置
④4xx:客户端错误;400 Bad Request:服务器无法理解请求的格式;404 Not Found:请求的资源在服务器上不存在
⑤5xx:服务器错误

在这里插入图片描述


(3)作业:http服务器,发送图片给客户端

github代码:https://github.com/WangEdward1027/HTTP

效果展示:
在这里插入图片描述

Edge浏览器

在这里插入图片描述


5.HTTPS的原理

HTTP是明文传输的,为了保证数据的安全性,必须对HTTP数据进行加解密的功能/服务。
因此google提出了HTTPS = HTTP + TLS/SSL

在这里插入图片描述

HTTPS的实现原理:
将非对称加密算法和对称加密算法结合起来:
①先用非对称加密算法,传输通信双方的对称加密算法的密钥 [安全性高]
②后续再用对称加密算法进行通信 [效率高]


(1)对称加密

通信双方,只有1把密钥

优势:效率很高
劣势:不够安全,密钥的传输是有风险的
在这里插入图片描述

加密运算:异或运算
发送端:a^b = c,接收端:c^b =a


(2)非对称加密

RSA算法,公钥和私钥。通信双方共有4把密钥,双方都有公钥、私钥

过程:接收方产生公钥和私钥,将公钥传输给发送方。发送方根据公钥进行加密并发送。接收方根据私钥解密。[公钥和私钥具有数学相关性,但公钥无法推理出私钥]

举例:从gitee远程拷贝到xshell:Xshell作为接收方,生成公钥和私钥,公钥明文,私钥密文看不见。把公钥粘贴给gitee,这时候再使用git clone命令就可以远程拷贝了。gitee根据公钥加密,xshell根据私钥解密。

优势:安全,安全性很高
劣势:效率低

在这里插入图片描述

在这里插入图片描述


6.Restful编程风格

HTTP协议能够进行数据传输的方式很灵活:①URL中的查询词 ②路径 ③首部字段 ④报文体,不够统一。

业界提出了Restful编程风格:
用路径表示要访问的资源
②用方法表示对于资源的操作
③用请求报文的消息体来传递参数
④用响应报文的消息体来返回结果

看起来很像函数调用与返回。
实际工作中,以公司的约定为准:例如 阿里巴巴的云计算,OSS对象存储服务,严格遵循Restful风格。



(五) 进程池和线程池

1.进程池

(1)进程池模型

在这里插入图片描述
在这里插入图片描述


(2)父进程的工作流程

1.创建n个子进程,创建n条管道
2.创建监听套接字listenfd,进行监听
3.创建epoll实例,监听 listenfd 和 n条管道
4.当epoll_wait返回时,两种情况:
listenfd:获取新客户端连接 peerfd的到来,将peerfd交给空闲的子进程进行任务处理。父进程调用sendFd函数通知子进程。父进程还要更新子进程的状态为BUSY
管道:当管道中有数据时,就认为是可读。读取管道中的数据,父进程将子进程状态改为FREE。


(3)子进程的工作流程

1.当子进程运行时,就阻塞在管道上(recvFd),等待任务到来
2.recvFd返回后,子进程得到与客户端交互的peerfd
3.执行文件的发送操作
4.通知父进程,发送操作已经完成 (往管道上写了一个数据1)


在这里插入图片描述
在这里插入图片描述



2.进程间传递文件描述符

(0)提出背景

匿名管道pipe无法实现进程间文件描述符的传递,需要使用socketpair创建局限于父子进程间的全双工的套接字对 (可认为是一对全双工匿名管道),再利用sendmsgrecvmsg发送和接收数据

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述


(1)socketpair()

1.函数原型:

#include <sys/types.h>
#include <sys/socket.h>int socketpair(int domain, int type, int protocol, int sv[2]);

(1)参数:
①domain:表示通信域,为AF_LOCALAF_UNIX
②type:表示套接字类型,为SOCK_STREAMSOCK_DGRAM
③protocol:通常设置为 0,表示使用默认协议
④sv:一个整数数组,长度为 2。调用成功后,sv[0] 和 sv[1] 将成为一对相互连接的套接字的文件描述符

(2)返回值
①成功,返回0
②失败,返回-1,并设置errno

(3)举例

int pipefds[2];
socketpair(AF_LOCAL, SOL_SOCKET, 0, pipefds);

在这里插入图片描述


(2)sendmsg()

1.函数原型

#include <sys/types.h>
#include <sys/socket.h>ssize_t sendmsg(int sockfd, const struct msghdr* msg, int flags);

(1)参数:
①sockfd: 套接字文件描述符
②msg: 指向 msghdr 结构的指针,该结构包含了要发送的消息信息
③flags: 发送消息时使用的标志,可以是 0 或以下标志的组合,例如 MSG_DONTWAIT, MSG_EOR

(2)返回值
①成功,返回成功发送的字节数 (>=0)。这个值表示从 iovec 结构数组中实际发送的数据总字节数
②失败,返回-1,并设置errno


①结构体msghdr
struct msghdr {void*         msg_name;       //(1)可选的套接字地址socklen_t     msg_namelen;    //(1)地址的长度struct iovec* msg_iov;        //(2)描述消息数据的数组int           msg_iovlen;     //(2)msg_iov 数组的元素数量void*         msg_control;    //(3)附加的控制信息(可选)socklen_t     msg_controllen; //(3)控制信息的长度(所占用的总字节数)int           msg_flags;      //(4)消息的标志位
};

(1)第一组参数:套接字地址成员(未使用)
(2)第二组参数:IO向量缓冲区 (聚集写,需要用iovec结构体填充)
msg_iov:指向 struct iovec 结构体数组的指针。iovec 结构体用于描述一块或多块数据缓冲区。通过该字段可以实现分散/聚集 I/O
msg_iovlen:msg_iov 数组元素的个数,即数据缓冲区的数量
(3)第三组参数:附属数据缓冲区,控制信息 (需要用cmsghdr结构体填充)
msg_control:控制信息,用cmsghdr结构体来填充。[指向辅助数据(控制信息)的缓冲区指针,用于传递文件描述符、IP选项等额外信息。]
msg_controllen:控制信息缓冲区的长度。如果 msg_control 为 NULL,则应设置为 0
(4)第四组参数:接收信息标记位 (未使用)

在这里插入图片描述


②结构体 struct iovec

struct iovec 是一个缓冲区集合

struct iovec {void  *iov_base;  // 指向缓冲区的指针size_t iov_len;   // 缓冲区的长度
};

writev聚集写:
使用writev系统调用来高效地将多个内存区域写入同一个文件。这种方法比多次调用write更高效,因为它减少了系统调用的次数。减少了内核态和用户态的切换,提高了系统效率。

#include <func.h>
#include <sys/uio.h>int main()
{char buff1[64] = "hello";char buff2[64] = "world";struct iovec iovs[2];iovs[0].iov_base = buff1;iovs[0].iov_len = strlen(buff1);iovs[1].iov_base = buff2;iovs[1].iov_len = strlen(buff2);int fd = open("file.txt", O_RDWR);ERROR_CHECK(fd, -1, "open");//聚集写的操作,//writev是一个系统调用,它可以提高程序的执行效率//(如果不使用writev,那就需要多次write操作,//才能完成数据的写入)int ret = writev(fd, iovs, 2);printf("write %d bytes.\n", ret);return 0;
}

readv分散读:
将文件读入不同的缓冲区中,注意设置每个缓冲区的读入大小

#include <func.h>
#include <sys/uio.h>int main()
{char buff1[5] = {0};char buff2[6] = {0};struct iovec iovs[2];iovs[0].iov_base = buff1;iovs[0].iov_len = 5;iovs[1].iov_base = buff2;iovs[1].iov_len = 5;int fd = open("file.txt", O_RDWR);ERROR_CHECK(fd, -1, "open");int ret = readv(fd, iovs, 2);printf("read %d bytes.\n", ret);printf("buff1: %s\n", buff1);printf("buff2: %s\n", buff2);return 0;
}

③结构体 struct cmshdr

作用:传递文件描述符

struct cmsghdr {socklen_t cmsg_len;   // 控制消息数据的长度,包括头部int       cmsg_level; // 控制消息的协议层int       cmsg_type;  // 控制消息的类型// 后面跟着数据的实际内容/* followed byunsigned char cmsg_data[]; */
};

使用宏函数
在使用 struct cmsghdr 时,通常会用到一些宏函数来方便处理控制消息。这些宏函数包括:

CMSG_LEN:计算控制消息的总长度。
CMSG_DATA:获取控制消息数据的指针。
CMSG_FIRSTHDR:获取第一个控制消息头部。
CMSG_NXTHDR:获取下一个控制消息头部。
CMSG_SPACE:计算控制消息所需的内存空间。

在这里插入图片描述


④代码
int sendFd(int pipefd, int fd)
{char buff[4] = {0};//构建第二组成员struct iovec iov;iov.iov_base = buff;iov.iov_len = sizeof(buff);//构建第三组成员size_t len = CMSG_LEN(sizeof(fd));struct cmsghdr * cmsg = calloc(1, len);cmsg->cmsg_len = len;cmsg->cmsg_level = SOL_SOCKET;cmsg->cmsg_type = SCM_RIGHTS;int * p = (int *)CMSG_DATA(cmsg);*p = fd;struct msghdr msg;memset(&msg, 0, sizeof(msg));msg.msg_iov = &iov;msg.msg_iovlen = 1;msg.msg_control = cmsg;msg.msg_controllen = len;//发送数据int ret = sendmsg(pipefd, &msg, 0);printf("sendFd %d bytes.\n", ret);free(cmsg);//回收堆空间return 0;
}

(3)recvmsg()

1.函数原型

#include <sys/types.h>
#include <sys/socket.h>ssize_t recvmsg(int sockfd, struct msghdr* msg, int flags);

2.参数同sendmsg

3.代码

int recvFd(int pipefd, int * pfd)
{char buff[4] = {0};//构建第二组成员struct iovec iov;iov.iov_base = buff;iov.iov_len = sizeof(buff);//构建第三组成员size_t len = CMSG_LEN(sizeof(int));struct cmsghdr * cmsg = calloc(1, len);cmsg->cmsg_len = len;cmsg->cmsg_level = SOL_SOCKET;cmsg->cmsg_type = SCM_RIGHTS;struct msghdr msg;memset(&msg, 0, sizeof(msg));msg.msg_iov = &iov;msg.msg_iovlen = 1;msg.msg_control = cmsg;msg.msg_controllen = len;//接收数据, 默认情况下是阻塞的int ret = recvmsg(pipefd, &msg, 0);printf("recvFd %d bytes.\n", ret);*pfd = *(int*)CMSG_DATA(cmsg);free(cmsg);//回收堆空间return 0;
}

3.传输文件问题

(1)小文件传输:粘包问题
1.TCP粘包问题

服务器两次send之后,客户端收到数据时,不知道消息的边界在那里。导致数据解析出现bug
在这里插入图片描述


2.小火车协议

解决方案:在应用层认为设计消息的边界,自己定义 小火车协议
因为长度信息占用的字节是固定的,先解析长度信息,再读取对应字节数的文件名/文件内容。
在这里插入图片描述


(2)大文件传输:半包问题
1.半包问题

1.半包问题原因:传输大文件时,使用了send函数 或 recv函数
(1)使用send函数,但发送缓冲区不够:比如内核的发送缓冲区只有500字节可用,而此次send发送的数据是1000字节,则本次调用send函数只能拷贝500字节,造成有500字节漏掉未发生。所以一定要判断send函数的返回值。
改进(解决方案):sendn函数


(2)使用recv函数,但网络延迟:发送方的一次send,不能被接收方的一次recv接收 (因为网络问题MTU限制,包被切片成多个)。而send的后半个包延迟到达时,被第二个recv接收,被小火车协议错误解读为下一个包的长度。

第三个参数表示的是这一次调用最多能拷贝的字节数。所以也要判断recv函数的返回值。
改进(解决方案):recvn函数

在这里插入图片描述


2.优化:零拷贝技术

1.思想:减少了一次内核态到用户态空间的数据拷贝

2.统一的协议:大火车协议
先发总长度,后面跟的全是数据
在这里插入图片描述

3.三种零拷贝技术:
①mmap:将用户态直接映射到内核空间 [服务器端、客户端]
②sendfile:[仅服务器端]
③splice:[服务器端、客户端]


3.发送方:循环发送sendn、3种零拷贝技术
①循环发送sendn

在这里插入图片描述


②mmap

链接:https://blog.csdn.net/Edward1027/article/details/139006808

在这里插入图片描述


sendfile

优势:只有3个参数。(参数少,犯错概率小,发送方推荐使用sendfile)
限制:只能在服务器端使用;限制2GB以内的文件;仅支持将数据从普通文件拷贝到套接字

#include <sys/sendfile.h>ssize_t sendfile(int out_fd, int in_fd, off_t *offset, size_t count);

在这里插入图片描述


④splice

需要借助匿名管道

#include <fcntl.h>
#include <unistd.h>ssize_t splice(int fd_in, loff_t *off_in, int fd_out, loff_t *off_out,size_t len, unsigned int flags);

md5sum filename 计算文件的哈希值,判断两个文件是否相同。

在这里插入图片描述



4.接收方:循环接收recvn、waitall、mmap、splice
①recvn

在这里插入图片描述


②recv(waitall)

③mmap

mmap既可以发送端用,也可以接收端用

在这里插入图片描述


④splice

同上文,发送端splice



4.进程池的退出

(0)设计思想:异步拉起同步

异步的信号,进程同步

流程:
0.父进程先注册SIGUSR1信号,并设置信号处理函数 [信号处理函数的执行流程]
1.父进程设置一条退出的匿名管道,epoll要监听该匿名管道
2.通过终端给父进程发起SIGUSR1信号。信号处理函数的执行流程:往退出的匿名管道中写一个数据,比如1
3.在父进程的epoll循环中,会收到匿名管道就绪的通知。之后就去执行进程退出的流程。
暴力退出:给所有子进程发送SIGUSR1信号,暴力终结每一个子进程。
父进程再回收子进程的资源,父进程再退出。整个进程池就退出了。
温和退出:父进程通过sendFd函数发送一个进程池退出的标志位,子进程通过recvFd函数收到该标志位之后,在完成任务后,退出子进程的任务执行流。
父进程再回收子进程的资源,父进程再退出。整个进程池就退出了。
在这里插入图片描述


(1)暴力退出:用kill()直接杀死子进程

父进程直接强制使子进程退出,不管子进程是否正在执行任务


(2)优雅退出:通知子进程exit

父进程通知子进程退出,当子进程执行完任务后自行退出。


5.线程池

1.图

补ppt63页

2.内核通过LWP(轻量级进程,Light Weight Process)识别线程。进程内部通过pthread_t tid识别线程。

3.线程池的退出:
(1)父子进程,父进程接收kill信号,子进程执行线程池的退出



5 线程池

5.1 模型

image-20240607144100059

5.2 流程

主线程

  1. 初始化线程池
  2. 启动线程池
  3. 创建监听的套接字
  4. 创建epoll的实例,添加监听
  5. 接收客户端peerfd的到来
  6. 再将peerfd加入到任务队列中去

子线程:

  1. 先从任务队列中获取任务节点, 拿到peerfd

  2. 执行文件发送任务

  3. 文件发送完毕之后,进入等待状态,试图继续获取下一个任务节点

5.3 数据结构

  1. 任务节点

image-20240607144338973

  1. 任务队列

image-20240607144413470

接口

  • queueInit()

  • queueDestroy()

  • taskEnqueue()

  • taskDequeue()

  • taskSize()

  • queueEmpty()

image-20240607220638658

  1. 线程池

image-20240607144733351

接口

image-20240607220727808

5.4 接口实现

5.4.1 任务队列

void queueInit(task_queue_t* queue)

image-20240607220854538

void queueDestroy(task_queue_t* queue)

image-20240607221023789

taskSize() queueIsEmpty()

image-20240607221046525

void taskEnqueue(task_queue_t* queue, int peerfd)

image-20240607221155389

int taskDequeue(task_queue_t* queue)

image-20240607221237877

void broadcastAll(task_queue_t* queue)

image-20240607221310739

5.4.2 线程池

void* threadFunc(void* arg)

image-20240607221402928

void threadpoolInit(threadpool_t* threadpool, int num) void threadpoolDestroy(threadpool_t* threadpool)

image-20240607221501242

void threadpoolStart(threadpool_t* threadpool)

image-20240607221544366

void threadpoolStop (threadpool_t* threadpool)

image-20240607221608779

5.4.3 main()函数

int exitPipe[2];void sighandler(int num) {printf("sig %d is coming.\n", num);// 写入管道,通知子进程退出int one;write(exitPipe[1], &one, sizeof(one));
}int main(int argc, char* argv[]) {// ip port threadnumARGS_CHECK(argc, 4);// exitPipe 用于父子进程之间pipe(exitPipe);pid_t pid = fork();if (pid > 0) {// 父进程中// 只在父进程中注册10号信号signal(SIGUSR1, sighandler);wait(NULL); // 等待子进程执行完退出exit(0);}// 在子进程中进行线程池的操作// 初始化线程池threadpool_t threadpool;threadpoolInit(&threadpool, atoi(argv[3]));// 启动线程池threadpoolStart(&threadpool);// 创建TCP监听套接字int listenfd = tcpInit(argv[1], argv[2]);printf("server start listening.\n");// 创建epoll实例int epfd = epoll_create1(0);ERROR_CHECK(epfd, -1, "epoll_create1");// 添加监听epollAddFd(epfd, listenfd);epollAddFd(epfd, exitPipe[0]);struct epoll_event* pEventArr = (struct epoll_event*)calloc(EVENT_ARR_SIZE, sizeof(struct epoll_event));while (1) {int nready = epoll_wait(epfd, pEventArr, EVENT_ARR_SIZE, -1);for (int i = 0; i < nready; i++) {int fd = pEventArr[i].data.fd;if (fd == listenfd) {int peerfd = accept(listenfd, NULL, NULL);// 添加到任务队列taskEnqueue(&threadpool.queue, peerfd);} else if (fd == exitPipe[0]) {// 执行退出printf("read exitPipe[0]");int howmany = 0;// 读取管道中的数据,避免重复通知read(exitPipe[0], &howmany, sizeof(howmany));// 执行退出流程threadpoolStop(&threadpool);threadpoolDestroy(&threadpool);close(listenfd);close(epfd);exit(0);}}}return 0;
}

5.5 线程池退出

流程

image-20240607220341472

流程图

image-20240607220402260

  1. 在父进程注册SIGUSR1

image-20240607222635902

image-20240607222829949

  1. 子进程注册监听exitPipe[0],

image-20240607222910639

  1. 子进程监听到exitPipe[0]

image-20240607223112441

image-20240607221608779

image-20240607221310739

image-20240607223340341

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

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

相关文章

软件测试分类介绍

大家好&#xff0c;软件测试是确保软件质量的关键环节之一&#xff0c;通过对软件系统的各个方面进行测试&#xff0c;可以发现和解决潜在的问题&#xff0c;提高软件的稳定性、可靠性和用户满意度。在软件测试领域&#xff0c;根据测试的目的、方法和对象的不同&#xff0c;可…

Python业务规则引擎库之rules使用详解

概要 在软件开发中,业务规则引擎是一种重要的工具,可以帮助开发者将复杂的业务逻辑从代码中解耦出来,并以更直观的方式进行管理和维护。rules 是一个轻量级的 Python 库,专门用于定义和执行业务规则。它提供了一种简洁且强大的方式来管理应用程序中的规则逻辑,使代码更加…

C++ 引用 - 引用的特点|在优化程序上的作用

引用是C 的一个别名机制&#xff0c;所谓别名&#xff0c;就是同一块内存共用多个名字&#xff0c;每个名字都指的是这片空间&#xff0c;通过这些别名都能访问到同样的一块空间。 就像鲁迅和周树人是同一个人。 ——鲁迅 一、引用的基本用法 int a 10; int& ref a; // …

Django序列化器详解:普通序列化器与模型序列化器的选择与运用

系列文章目录 Django入门全攻略&#xff1a;从零搭建你的第一个Web项目Django ORM入门指南&#xff1a;从概念到实践&#xff0c;掌握模型创建、迁移与视图操作Django ORM实战&#xff1a;模型字段与元选项配置&#xff0c;以及链式过滤与QF查询详解Django ORM深度游&#xff…

充电桩出口:跨国贸易的机遇与挑战之旅

在新能源浪潮席卷全球的今天&#xff0c;充电桩作为电动汽车的“加油站”&#xff0c;正逐渐从幕后走向台前。 而在这场跨国贸易的舞台上&#xff0c;充电桩的出口之路&#xff0c;既充满了诱人的机遇&#xff0c;也伴随着不小的挑战。 机遇&#xff0c;源自日益增长的全球市场…

免费听歌,电脑或手机免费听歌,落雪音乐安装详细步骤

近年来&#xff0c;由于资本的力量导致各种收费&#xff0c;看个电视想听歌都必须要付费了&#xff0c;否则你听不完整&#xff0c;吃相非常难看&#xff0c;特别是电视&#xff0c;吸血鬼式吸收各种会员费&#xff0c;各种APP也是铺天盖地的广告&#xff0c;渐渐迷失了自我&am…

两个螺旋面的交线

已知轴截面齿形&#xff0c;先得到螺旋面 然后在计算出对应的端面齿形 在用端面齿形来计算和另一个螺旋面的相交曲线。 三维验证这个方法可行&#xff01;

喜大普奔,AI绘画SD3终于开源了,AI绘画又添新利器!【附模型下载和安装包】

sd3终于开源了&#xff01; 没错就是stablediffusion 3.0版本&#xff01;这是stability迄今为止最先进最复杂图像生成模型。 这次开源的是medium版本&#xff0c;总共有三个型号的模型&#xff0c;下面我们来详细的说下sd3的功能特点以及不同型号区别、安装方法&#xff01;…

HTML静态网页成品作业(HTML+CSS)—— 家乡山西介绍网页(3个页面)

&#x1f389;不定期分享源码&#xff0c;关注不丢失哦 文章目录 一、作品介绍二、作品演示三、代码目录四、网站代码HTML部分代码 五、源码获取 一、作品介绍 &#x1f3f7;️本套采用HTMLCSS&#xff0c;未使用Javacsript代码&#xff0c;共有6个页面。 二、作品演示 三、代…

专业设计师推荐的20条用户体验设计黄金准则

在当今的数字世界中&#xff0c;用户体验设计已经成为商业成功的关键因素。它可以实现或摧毁一个产品。今天&#xff0c;我们将讨论该领域的20个基本原则。这些用户体验设计原则不仅被顶级工厂设计师广泛使用&#xff0c;而且为您在设计过程中提供有价值的指导。 1. 以用户为中…

共绘财富与时间画卷,ATFX携手Haofa腕表,开启跨界新篇章

在当前激烈的市场竞争中&#xff0c;品牌创新和多元化、本地化发展已成为企业突破重围&#xff0c;赢得用户和市场份额的关键。作为差价合约行业领军品牌&#xff0c;ATFX勇于突破自我&#xff0c;为探索更多的市场可能性&#xff0c;通过跨界合作、联合营销的策略&#xff0c;…

物联网概念

物联网 物联网简介物联网体系结构物联网体系结构定义物联网体系结构设计原则物联网体系结构四层物联网体系结构感知控制层数据传输层数据处理层应用决策层 物联网关键技术感知标识技术网络与通信技术云计算技术安全技术 已有物联网相关应用架构无线传感器网络的体系结构EPC/UID…

同三维T80005JEHVA 4K视频解码器

同三维T80005JEHVA视频解码器 可解1路4K30HDMI/VGA/CVBS1路3.5音频 可解电台音频网络流&#xff0c;可同时解4个网络流&#xff0c;分割输出 可预设十个流&#xff0c;任意切换1路流输出 <!--[endif]----><!--[if !vml]--> <!--![endif]----> 介绍&…

逆向分析-Ollydbg动态跟踪Ransomware.exe恶意锁机程序

1.认识Ollydbg Ollydbg是一个新的动态追踪工具&#xff0c;将IDA与SoftICE结合起来的思想&#xff0c;Ring 3级调试器&#xff0c;非常容易上手&#xff0c;己代替SoftICE成为当今最为流行的调试解密工具了。同时还支持插件扩展功能&#xff0c;是目前最强大的调试工具。 Oll…

Java——面向对象进阶(二)

前言&#xff1a; 多态&#xff0c;包&#xff0c;final关键字&#xff0c;权限修饰符和代码块 文章目录 一、多态1.1 概念1.2 多态存在条件1.3 多态中调用成员的特点1.4 instanceof关键字 二、包三、权限修饰符四、final 关键字4.1 修饰类4.2 修饰方法4.3 修饰变量 五、代码块…

PHP开发的爱情盲盒交友系统网站源码

源码介绍 PHP开发的爱情盲盒交友系统网站源码 独立后台 源码截图 源码下载 PHP开发的爱情盲盒交友系统网站源码

CV预测:快速使用LeNet-5卷积神经网络

AI预测相关目录 AI预测流程&#xff0c;包括ETL、算法策略、算法模型、模型评估、可视化等相关内容 最好有基础的python算法预测经验 EEMD策略及踩坑VMD-CNN-LSTM时序预测对双向LSTM等模型添加自注意力机制K折叠交叉验证optuna超参数优化框架多任务学习-模型融合策略Transform…

vue3和ant-design 实现前端多种验证密码规则,最全的前端验证密码规则

1、小眼睛可以显示/隐藏明文密码&#xff08;无法用input typepassword&#xff0c;用css样式实现切换明文&#xff09; 2、输入长度统计&#xff08;不是自带的&#xff0c;用div写的&#xff0c;然后定位到框内的&#xff09; 3、每输入一个字符分别验证每一项规则&#xf…

神经网络 torch.nn---nn.LSTM()

torch.nn - PyTorch中文文档 (pytorch-cn.readthedocs.io) LSTM — PyTorch 2.3 documentation LSTM层的作用 LSTM层:长短时记忆网络层&#xff0c;它的主要作用是对输入序列进行处理&#xff0c;对序列中的每个元素进行编码并保存它们的状态&#xff0c;以便后续的处理。 …

藏品管理的发展历程

智能RFID藏品管理系统的发展是藏品管理领域的一项重大进步。它标志着从传统的手工记录方式向自动化、高效和智能化的管理方式的转变。通过RFID&#xff08;Radio Frequency Identification&#xff09;技术的应用&#xff0c;藏品管理系统实现了无接触、非视线范围内的数据读取…