当前位置: 首页 > article >正文

【网络编程】网络编程基础:TCP/UDP 协议

一、什么是网络?

网络是信息传输,接收和共享的虚拟世界,通过把网络上的信息汇聚在一起,将这些资源进行共享。

初衷:知识共享。这里不得不提到Internet 的历史-它其实是“冷战”的产物:

  • 1957年10月和11月,前苏联先后有两颗“Sputnik”卫星上天。
  • 1958年美国总统艾森豪威尔向美国国会提出建立DARPA (Defense Advanced Research Project Agency),即国防部高级研究计划署,简称ARPA。
  • 1968年6月DARPA提出“资源共享计算机网络” (Resource Sharing Computer Networks),目的在于让DARPA的所有电脑互连起来,这个网络就叫做ARPAnet,即“阿帕网”,是 Interne 的最早雏形。

二、计算机中的软件层面,网络是由什么组成的?

1>IP
    //定义
    IP地址是一种因特网上的主机编址方式,也称为网际协议地址IP,是任意一台主机
    在网络中的唯一标识。
    
    //格式
    点分十进制:用户编写,给人看的 192.168.30.100
    网络二进制:给系统看的 0 1
    
    //分类
    IPv4:(主要集中在电脑)
        点分十进制:4个字节
        网络二进制:32个位(42亿个IP地址)
        由网络地址(子网ID)和主机地址(主机ID)构成


    IPv6:(主要用在手机WiFi,目前电脑上用得不多)
        点分十进制:16个字节
        网络二进制:128个位

    IPv4地址分类:A/B/C/D/E
    分别应用于大型网、中型网、中小型网、组播型、待用型
	
    //A类地址:政府机关或者学校等大型网络
   #规则:以0开头,8位的网络地址(子网ID),24位的主机地址(主机ID)
    网络二进制:0000 0000 0000 0000 0000 0000 0000 0001 - 0111 1111 1111 1111 1111 1111 1111 1110
    点分十进制:                                0.0.0.1 - 127.255.255.254
   
    //B类地址:中等规模的企业使用
    #规则:以10开头,16位的网络地址(子网ID),16位的主机地址(主机ID)
    网络二进制:1000 0000 0000 0000 0000 0000 0000 0001 - 1011 1111 1111 1111 1111 1111 1111 1110
    点分十进制:                              128.0.0.1 - 191.255.255.254

    //C类地址:任意个人使用	*
    #规则:以110开头,24位的网络地址(子网ID),8位的主机地址(主机ID)
    网络二进制:1100 0000 0000 0000 0000 0000 0000 0001 - 1101 1111 1111 1111 1111 1111 1111 1110
    点分十进制:                              192.0.0.1 - 223.255.255.254

    //D类地址(组播地址):4个字节都是网络地址	*
    #规则:以1110开头,32位的网络地址(子网ID),0位的主机地址(主机ID)
    网络二进制:1110 0000 0000 0000 0000 0000 0000 0001 - 1110 1111 1111 1111 1111 1111 1111 1110
    点分十进制:                              224.0.0.1 - 239.255.255.254
    
    //E类地址:未使用的地址 -- 测试地址
    #规则:以11110开头,留着待用
	
		前三位:判断类别
		A: 000
		B: 100
		C: 110
		D: 111
		
	注意:有两个地址不能使用
    /**
        主机地址(主机ID)全为0,它是网段号、网络号
        主机地址(主机ID)全为1,它是广播地址
    **/
	
	端口号:标识计算机上的进程
	取值范围: 0~2^16 -1
	
	//特点
	网络地址不同的网络不能直接通信,如果要通信须通过路由器进行转发

2>子网掩码
    //定义
    子网掩码又叫网络掩码、地址掩码,是一个32位由1和0组成的数值,并且1和0都是连续
    
    //作用
    指明IP地址中哪些位表示为子网ID,哪些位表示为主机ID
    
    //特点
    必须结合IP地址一起使用,不能单独存在
    IP地址中由子网掩码中1覆盖的连续位为子网ID,其余为主机ID
    
    //以C类地址为例
    192.168.30.100/255.255.255.0
    192.168.30.100/24
    
    前缀长度:24
    
    3>网关
    用来管理当前网段下的信息传输、网络的门户,默认取值为1,192.168.30.1,随机取值(1 - 254)

    4>DNS域名解析器
    DNS的作用类似于电话簿,将域名和IP地址相对应,使得用户可以通过域名来访问网站,
    不要记忆复杂的IP地址
    
    www.baidu.com  -  183.2.172.42

网关与网段

三、网络体系结构

网络采用分而治之的方法设计,将网络的功能划分为不同的模块,以分层的形式有机组合在一起。每层实现不同的功能,其内部实现方法对外部其他层次来说是透明的。每层向上层提供服务,同时使用下层提供的服务。网络体系结构即指 网络的层次结构和每层所使用协议的集合

两类非常重要的体系结构:OSI 与 TCP/IP

OSI开放系统互联模型

OSI模型相关的协议已经很少使用,但模型本身非常通用;OSI模型是一个理想化的模型,尚未有完整的实现,OSI模型共有七层:

请添加图片描述
请添加图片描述

1>OSI七层模型

           应用层              应用程序:FTP、E-mail、Telnet
    ----------------------
           表示层              数据格式定义、数据转换/加密
    ----------------------
           会话层              建立通信进程的逻辑名字与物理名字之间的联系 
    ----------------------
           传输层              差错处理/恢复,流量控制,提供可靠的数据传输
    ----------------------
           网络层              数据分组、路由选择
    ----------------------
           链路层              数据组成可发送、接收的帧
    ----------------------
           物理层              传输物理信号、接口、信号形式、速率
           
           
    目的:将数据封装,形成一个约定好的通信协议
    协议:双方约定好的通信规则
    缺点:太复杂,太过理想化,有些层的功能重复

请添加图片描述
请添加图片描述

双方通信需要保证协议一致

请添加图片描述

2>TCP/IP协议族的体系结构
    
    重点学习的体系结构,网络协议中的世界语、标准语
    
           应用层              应用程序:FTP、E-mail、Telnet -- http(超文本传输协议) DNS POP3(邮件接收协议) STMP(邮件发送协议)
    ----------------------
           传输层              TCP/UDP -- 确定数据包交给主机上的那个进程
    ----------------------
           网络层              IP/ICMP/IGMP
    ----------------------
       网络接口和物理层         网卡驱动和物理接口

    /**
    传输层:TCP/UDP
    	TCP:微信视频电话 --- 保证对方接了之后才能通信 -- 文件传输、聊天
    	UDP:微信发消息 --- 只管发,不管对方有无接收 -- 视屏传输
    网络层:
        IP:主机的唯一标识
        ICMP:网络控制消息协议,用于ping命令的实现
        IGMP:网络组管理协议,用于广播、组播的实现             
    **/
    /**
    网络接口和物理层:
        网卡:让不同的计算机之间连接,从而实现数据的通信等功能(有线网卡和无线网卡)
    
        mac(物理地址):网卡的标识号(48位),类似于身份证号,理论上全球唯一
                        
                       物理地址:  00:0c:29:14:84:69      
                       前三组称为厂商ID,后三组为设备ID

        ARP            将IP地址转换为MAC地址 
        
        RARP           将MAC地址转换为IP地址     
    **/
    
3>数据的封装与传递过程
    见 数据的封装与传递过程(如下图所示)
    封装的目的:保证数据稳定可靠地传输

请添加图片描述
在这里插入图片描述

发送端 数据打包

请添加图片描述

接收端 数据解包请添加图片描述

数据传输过程:

层层封装 ---> 层层解封
	["疯狂星期四Vme50"] ---> 应用层
	[TCP/UDP 头]["疯狂星期四Vme50"] ---> 传输层
	[IP 头][TCP/UDP 头]["疯狂星期四Vme50"] ---> 网络层
	[ARP 头][IP 头][TCP/UDP 头]["疯狂星期四Vme50"] ---> 物理接口层
						|
				将打包好的数据发送出去
	数据在网络中都是以帧的形式发送  [ARP 头][IP 头][TCP/UDP 头]["疯狂星期四Vme50"] -- 1帧数据
	[IP 头][TCP/UDP 头]["疯狂星期四Vme50"]  -- 最大 1500个 字节

TCP/IP 协议下的数据包:
TCP/IP 协议下的数据包

4>端口号
    类似于PID表示一个进程,区分不同的网络应用程序
    
    例如:
    
    QQ 4399  微信 17173
    
    无符号短整型 unsigned short 2个字节 0 ~ 65535
    
    1 ~ 1023        系统进程使用
    1024 ~ 5000     系统分配的端口
    5001 ~ 65535    用户自己分配使用的
    
    问:为什么有进程PID了,还要搞一个端口号来区分不同的应用程序?
    答:根据进程运行被分配的进程号不固定,但想以固定的序号去访问指定的应用程序,便有了端口号

5>大小端序
        不同类型CPU主机中,内存存储的整数字节序分为以下两种:
        
        小端序(little-endian)
            低位字节存储在低位地址         linux,x86平台
            
        大端序(big-endian)
            低位字节存储在高位地址         网络字节序
            
            
        //用于大小端序转换的工具函数
       #include <arpa/inet.h>
       
       //主机字节序转换为网络字节序(小端序转换为大端序)
       uint32_t htonl(uint32_t hostlong);

       uint16_t htons(uint16_t hostshort);

       //网络字节序转换为主机字节序(大端序转换为小端序)
       uint32_t ntohl(uint32_t netlong);

       uint16_t ntohs(uint16_t netshort);

6>网络通信模型:
			C/S : 服务器 + 客户端
			B/S : 网页端 http https

四、TCP/UDP的特点和区别

都采用经典的C/S模型,即客户端(client)服务器(server)模型。
不同点:TCP:有连接,可靠;UDP:无连接,不保证可靠。

1》TCP: 面向链接的可靠协议 -- 1对1私聊
    //定义
    是一种面向连接的传输层协议,它能提供高可靠性通信(即数据无误、数据无丢失、数据无失序、数据无重复到达的通信)
	借助于C/S模型搭建TCP通信:
		服务器(while(1))										客户端
		1>建立socket连接 -- 买手机							1> 建立socket连接 -- 买手机
		2>绑定IP和端口号 -- 办电话卡							2> 连接服务器	
		3>监听			-- 开机								3> 收/发	
		4>等待连接		-- 等别人打电话						
		5>收/发 			 	
    
  //功能
    提供不同主机上的进程通信
    
  //特点
    1.建立连接->使用连接->释放连接
    2.TCP数据包中包含序号和确认序号
    3.对包进行排序并排错,而损坏的包可以被重传
    
  //适用情况:
    适合于对传输质量要求较高,以及传输大量数据的通信。
    在需要可靠数据传输的场合,通常使用TCP协议s
    MSN/QQ等即时通讯软件的用户登录账户管理相关的功能通常采用TCP协议
	
    /**********************************************************************
    TCP的建立连接和断开连接
    
    标识符:
            ACK:确认标识符
            FIN:断开标识符
            SYN:请求标识符 -- 请求服务器连接
            URG:紧急标识符
            PSH:推标识
            
            
    TCP的三次握手:        (如何打通电话)
                        1.客户端给服务器发送SYN连接请求
                        2.服务器回复ACK,并给客户端发送连接请求
                        3.客户端回复ACK    
                        
    TCP的四次挥手:        (如何挂断电话)
                        1.客户端给服务器发送FIN请求
                        2.服务器回复ACK
                        3.服务器给客户端发送FIN请求
                        4.客户端回复请求ACK                                  
    
    
    **********************************************************************/
2》UDP
    //定义
    是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行高效率的数据传输。

    //功能
    提供不同主机上的进程通信
        
    //特点
    1.发送数据之前不需要建立连接
    2.不对数据包的顺序进行检查
    3.没有错误检测和重传机制
    
    //适用情况
    发送小尺寸数据(如对DNS服务器进行IP地址查询时)

    在接收到数据,给出应答较困难的网络中使用UDP。(如:无线网络)

    适合于广播/组播式通信中。

    MSN/QQ/Skype等即时通讯软件的点对点文本通讯以及音视频通讯通常采用UDP协议

    流媒体、VOD、VoIP、IPTV等网络多媒体服务中通常采用UDP方式进行实时数据传输
TCP传输

在这里插入图片描述
TCP协议的三次握手
TCP协议的四次挥手

五、基于TCP协议的网络通信模型

1》框架
/**框架**/
    服务器(server)                                     客户端(client)
        
1.创建流式套接字:socket();
    |
2.绑定本地地址:bind();
    |
3.设置监听套接字:listen();                          1.创建流式套接字:socket();
    |                                                      |
4.等待客户端连接:accept();                          2.发送连接请求:connect();
    |                                                      |
5.开始和客户端通信:read()/recv();                   3.与服务器通信:write()/send();
    |                                                      |
6.断开连接:close();                                 4.发送断开请求:close(); 

在这里插入图片描述

2》TCP编程相关的API函数

   1>socket创建套接字
   	#include <sys/types.h>          /* See NOTES */
   	#include <sys/socket.h>

   int socket(int domain, int type, int protocol);
   /**********************************************************************
    @brief:     创建一个特殊的文件描述符(具有网络属性)
    
    @domain:   地址族
                AF_UNIX, AF_LOCAL   用于本地进程间通信,域通信
                AF_INET             IPv4 Internet protocols         
                AF_INET6            IPv6 Internet protocols  

    @type:     SOCK_STREAM   // 流式套接字
                SOCK_DGRAM    // 数据报套接字
                SOCK_RAW      //  原始套接字
        
    @protocol:  一般默认设置为0,表示前面两个参数有效
    
    @retval:    成功:返回具有网络属性的文件描述符
                失败:返回-1,并且设置全局错误码
   
   **********************************************************************/

    2>connect主动连接服务器(绑定IP和端口号)
	bind()
   	#include <sys/types.h>          /* See NOTES */
   	#include <sys/socket.h>

   int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
   /**********************************************************************
    @brief:     主动连接服务器
    
    @sockfd:   客户端的唯一文件描述符

    @addr:     声明服务器的结构体指针
        
    @addrlen:   结构体的长度
    
    @retval:    成功:返回0
                失败:返回-1,并且设置全局错误码
   
   **********************************************************************/
   /**********************************************************************************
    grep查询struct sockaddr{}的具体实现
    grep struct\ sockaddr\ { /usr/src/linux-headers-5.4.0-150-generic/include/* -rn
    
    vim /usr/src/linux-headers-5.4.0-150-generic/include/linux/socket.h +31
    =================================================================================
    ctags查询struct sockaddr{}的具体实现
    1》安装ctags
        sudo apt-get install ctags
    2》去到头文件所在的目录
        cd /usr/src/linux-headers-5.4.0-150-generic/include/
    3》生产ctags标签
        sudo ctags -R
    4》查询
        vim -t 类(例如:sockaddr)
    =================================================================================
    locate 文件名 用于查询该文件所在路径
    
   **********************************************************************/

    #通用地址结构
    struct sockaddr {
            sa_family_t     sa_family;      /* address family, AF_xxx       */	//地址族 --> ip地址
            char            sa_data[14];    /* 14 bytes of protocol address */	//IP + 端口号
    }; 
    
    struct sockaddr
    {    
           u_short  sa_family;    // 地址族, AF_xxx
           char  sa_data[14];     // 14字节协议地址
    };
    // 该结构体不适用: 1>IP + Prot = 6 个字节,sa_data有14个字节,多的字节没法填充
	//				   2>无法判断IP在前面还是Prot在前面
	
    /*
        问题:
        1.IP地址和端口号谁在前谁在后?
        2.IP地址和端口号总共占6个字节,多出来的8个字节怎么处理?
    */
          
    #Internet协议地址结构
    struct sockaddr_in {
          __kernel_sa_family_t  sin_family;     /* Address family               */
          __be16                sin_port;       /* Port number                  */
          struct in_addr        sin_addr;       /* Internet address             */

          /* Pad to size of `struct sockaddr'. */
          unsigned char         __pad[__SOCK_SIZE__ - sizeof(short int) -
                                sizeof(unsigned short int) - sizeof(struct in_addr)];
    }; 
    
    struct sockaddr_in {           
           u_short sin_family;      // 地址族, AF_INET,2 bytes
           u_short sin_port;      // 端口,2 bytes
           struct in_addr sin_addr;  // IPV4地址,4 bytes 	
           char sin_zero[8];        // 8 bytes unused,作为填充
    }; 
    
    // internet address  
    struct in_addr
    {
         in_addr_t  s_addr;            // u32 network address 
    };

   /**点分十进制和网络二进制的相互转换函数**/ 
   in_addr_t inet_addr(const char *cp);
   /**********************************************************************
    @brief:     点分十进制转换为网络二进制
    
    @cp:       字符串点分十进制
    
    @retval:    成功:返回网络二进制数
                失败:返回-1,并且设置全局错误码
   
   **********************************************************************/

   char *inet_ntoa(struct in_addr in);
   /**********************************************************************
    @brief:     网络二进制转换为点分十进制
    
    @cp:       网络二进制的结构体变量
    
    @retval:    成功:返回字符串点分十进制
                失败:返回-1,并且设置全局错误码
   
   **********************************************************************/
                                                                  
    3>send发送
   	#include <sys/types.h>
   	#include <sys/socket.h>

   ssize_t send(int sockfd, const void *buf, size_t len, int flags);
   /**********************************************************************
    @brief:     发送数据
    
    @sockfd:   套接字文件描述符
    
    @buf:      发送数据的容器
    
    @len:      buf的长度(用strlen)
    
    @flags:    一般为0
    
    @retval:    成功:返回实际发送的字节数
                失败:返回-1,并且设置全局错误码
   
   **********************************************************************/
   
   4>close关闭
   #include <unistd.h>

   int close(int fd);
    
   5>recv接收
   #include <sys/types.h>
   #include <sys/socket.h>

   ssize_t recv(int sockfd, void *buf, size_t len, int flags);
   /**********************************************************************
    @brief:     接收数据
    
    @sockfd:   套接字文件描述符,注意如果是服务器应使用accept对应的套接字
    
    @buf:      接收数据的容器
    
    @len:      buf的长度(用sizeof)
    
    @flags:    一般为0
    
    @retval:    成功:返回实际接收的字节数
                   0:异常退出
                失败:返回-1,并且设置全局错误码
   
   **********************************************************************/
   6>bind绑定       
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>

    int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
   /**********************************************************************
    @brief:     将IP地址和端口号与套接字绑定在一起
    
    @sockfd:   套接字文件描述符
    
    @addr:     服务器的地址信息的结构体指针
    
    @addrlen:  结构体的长度
        
    @retval:    成功:返回0
                失败:返回-1,并且设置全局错误码
   
   **********************************************************************/
   
    7>listen监听 
   #include <sys/types.h>          /* See NOTES */
   #include <sys/socket.h>

   int listen(int sockfd, int backlog);
   /**********************************************************************
    @brief:     保护服务器,限制同一时间客户端最大的连接数量
    
    @sockfd:   套接字文件描述符
    
    @backlog:  同一时间最大连接数量
            
    @retval:    成功:返回实际发送的字节数
                失败:返回-1,并且设置全局错误码
   
   **********************************************************************/
   
   8>accept等待客户端的连接
    #include <sys/types.h>          /* See NOTES */
    #include <sys/socket.h>

    int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
   /**********************************************************************
    @brief:     等待客户端连接(阻塞)
    
    @sockfd:   套接字文件描述符
    
    @addr:     客户端的结构体指针
    
    @addrlen:  结构体的长度的指针
            
    @retval:    成功:返回与客户端进行连接通信的文件描述符
                失败:返回-1,并且设置全局错误码
   
   **********************************************************************/

基于 TCP协议 的服务器-客户端通信模型的 C 语言代码示例

该代码实现了基本的网络通信框架,其中服务器监听客户端连接,接受连接后进行数据收发,客户端与服务器建立连接并进行数据交换。

TCP 服务器端代码(server.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define PORT 8080  // 服务器监听端口
#define BUFFER_SIZE 1024

int main() {
    int server_fd, new_socket;
    struct sockaddr_in address;
    int addrlen = sizeof(address);
    char buffer[BUFFER_SIZE] = {0};

    // 1. 创建流式套接字
    if ((server_fd = socket(AF_INET, SOCK_STREAM, 0)) == 0) {
        perror("Socket failed");
        exit(EXIT_FAILURE);
    }

    // 2. 绑定本地地址
    address.sin_family = AF_INET;
    address.sin_addr.s_addr = INADDR_ANY;
    address.sin_port = htons(PORT);

    if (bind(server_fd, (struct sockaddr *)&address, sizeof(address)) < 0) {
        perror("Bind failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }

    // 3. 设置监听套接字
    if (listen(server_fd, 3) < 0) {
        perror("Listen failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("Server listening on port %d...\n", PORT);

    // 4. 等待客户端连接
    if ((new_socket = accept(server_fd, (struct sockaddr *)&address, (socklen_t*)&addrlen)) < 0) {
        perror("Accept failed");
        close(server_fd);
        exit(EXIT_FAILURE);
    }
    printf("Client connected!\n");

    // 5. 开始和客户端通信
    while (1) {
        memset(buffer, 0, BUFFER_SIZE);
        int bytes_read = read(new_socket, buffer, BUFFER_SIZE);
        if (bytes_read <= 0) {
            printf("Client disconnected.\n");
            break;
        }

        printf("Received: %s\n", buffer);

        // 发送回复
        char *message = "Message received.";
        send(new_socket, message, strlen(message), 0);
    }

    // 6. 断开连接
    close(new_socket);
    close(server_fd);
    return 0;
}
TCP 客户端代码(client.c)
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <arpa/inet.h>

#define SERVER_IP "127.0.0.1"  // 服务器地址
#define PORT 8080              // 服务器端口
#define BUFFER_SIZE 1024

int main() {
    int sock;
    struct sockaddr_in serv_addr;
    char buffer[BUFFER_SIZE] = {0};

    // 1. 创建流式套接字
    if ((sock = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket creation error");
        exit(EXIT_FAILURE);
    }

    serv_addr.sin_family = AF_INET;
    serv_addr.sin_port = htons(PORT);

    // 2. 设置服务器地址
    if (inet_pton(AF_INET, SERVER_IP, &serv_addr.sin_addr) <= 0) {
        perror("Invalid address / Address not supported");
        close(sock);
        exit(EXIT_FAILURE);
    }

    // 3. 发送连接请求
    if (connect(sock, (struct sockaddr *)&serv_addr, sizeof(serv_addr)) < 0) {
        perror("Connection failed");
        close(sock);
        exit(EXIT_FAILURE);
    }
    printf("Connected to server.\n");

    // 4. 与服务器通信
    while (1) {
        printf("Enter message: ");
        fgets(buffer, BUFFER_SIZE, stdin);
        buffer[strcspn(buffer, "\n")] = 0;  // 去掉换行符

        send(sock, buffer, strlen(buffer), 0);

        memset(buffer, 0, BUFFER_SIZE);
        int bytes_read = read(sock, buffer, BUFFER_SIZE);
        if (bytes_read <= 0) {
            printf("Server disconnected.\n");
            break;
        }

        printf("Server: %s\n", buffer);
    }

    // 5. 发送断开请求
    close(sock);
    return 0;
}
运行步骤
  1. 编译代码
gcc server.c -o server
gcc client.c -o client
  1. 启动服务器
./server
  1. 启动客户端
./client
  1. 在客户端发送消息
    客户端输入消息后,服务器会接收到并回复 Message received.

该代码实现了基于 TCP 协议的网络通信模型:

服务器:
	socket() 创建套接字
	bind() 绑定 IP 和端口
	listen() 监听连接
	accept() 接受客户端连接
	read()/send() 进行数据通信
	close() 断开连接
客户端:
	socket() 创建套接字
	connect() 连接服务器
	send()/read() 进行数据通信
	close() 断开连接

综上。希望该内容能对你有帮助,感谢!

以上。仅供学习与分享交流,请勿用于商业用途!转载需提前说明。

我是一个十分热爱技术的程序员,希望这篇文章能够对您有帮助,也希望认识更多热爱程序开发的小伙伴。
感谢!


http://www.kler.cn/a/552820.html

相关文章:

  • 学习数据结构(10)栈和队列下+二叉树(堆)上
  • 计算机视觉:神经网络实战之手势识别(附代码)
  • Alluxio Enterprise AI 3.5 发布,全面提升AI模型训练性能
  • Docker 多阶段构建:优化镜像大小
  • C#_子窗体嵌入父窗体
  • YOLOv11-ultralytics-8.3.67部分代码阅读笔记-annotator.py
  • 【第3章:卷积神经网络(CNN)——3.7 数据增强与正则化技术】
  • go 树形结构转为数组
  • win11 labelme 汉化菜单
  • matlab质子磁力仪传感器线圈参数绘图
  • 确保设备始终处于最佳运行状态,延长设备的使用寿命,保障系统的稳定运行的智慧地产开源了
  • Effective C++读书笔记——item52(如果编写了 placement new,就要编写 placement delete)
  • Spring Security,servlet filter,和白名单之间的关系
  • 【前端ES】ECMAScript 2023 (ES14) 引入了多个新特性,简单介绍几个不为人知但却好用的方法
  • 【Python爬虫(14)】解锁Selenium:Python爬虫的得力助手
  • npm、yarn、pnpm 的异同及为何推荐 pnpm
  • DeepSeek AI 完全使用指南:从入门到精通
  • Node.js 版本与 npm 的关系及版本特性解析:从开源项目看演进
  • 腿足机器人之九- SLAM基础
  • 跳板机和堡垒机的区别