【Linux/C++网络篇(一) 】网络编程入门:一文搞懂 TCP/UDP 编程模型与 Socket 网络编程

【Linux/C++网络篇(一) 】网络编程入门:一文搞懂 TCP/UDP 编程模型与 Socket 网络编程

⭐️在这个怀疑的年代,我们依然需要信仰

个人主页:YYYing.

⭐️Linux/C++进阶系列专栏:【从零开始的linux/c++进阶编程】

系列上期内容:【Linux/C++多线程篇(二) 】同步互斥机制& C++ 11下的多线程

系列下期内容:暂无


目录

引言:程序如何“联网”?

网络编程基本概念

一、字节序

二、IP地址

IP地址的分类

特殊的IP地址

点分十进制

三、端口号

端口号的分类

网络编程基础

一、套接字(socket)的概念

二、基于TCP面向连接的通信方式

 📖 bind函数

 📖 listen函数

 📖 accept函数

 📖 recv、send数据收发

 📖 close关闭套接字

 📖 connect连接函数

三、TCP的C/S模型

服务端代码实现

客户端代码实现

四、基于UDP面向无连接的通信方式

 📖 recvfrom、sendto数据收发

五、UDP的C/S模型

服务端代码实现

客户端代码实现

结语

---⭐️封面自取⭐️---



引言:程序如何“联网”?

        你有没有想过,你写的 C/C++ 程序是怎么跟千里之外的服务器交换数据的?聊天软件、网页浏览、在线游戏……这些应用程序背后都依赖着网络编程。而网络编程的核心就是 Socket(套接字)——它就像程序打开的一扇“网络窗户”,数据从这扇窗户进出。

        那么我们现在就从零开始,通过代码实战,彻底搞懂网络通信的核心模型。

网络编程基本概念

        关于TCP和UDP的讲解我们计网篇章的传输层部分讲的内容已经足够了,我们本篇章的基础概念主要是围绕着字节序、ip和端口号来展开

一、字节序

        字节序:计算机在存储多字节整数时,根据主机的CPU处理架构不同,我们将主机分成大端存 储的主机和小端存储的主机

  • 大端存储:内存地址低位存储的是数据的高位
  • 小端存储:内存地址的低位存储的是数据的低位

        我们不妨可以来验证一下我们主机是大端存储还是小端存储

  • 使用指针来判断
#include <iostream> int main(int argc, const char *argv[]) { //定义一个整形变量 int num = 0x12345678; //定义一个字符类型的指针,指向整形变量的起始地址 char *ptr = (char *)&num; //对ptr所指向的字节中的内容进行判断,如果是0x12则说明是大端存储 //如果是0x78则说明是小端存储 if(*ptr == 0x12){ cout<<"big endian"<<endl; } else if(*ptr == 0x78){ cout << "little endian"<<endl; } std::cout << "Hello, World!" << std::endl; return 0; } 
  • 使用共用体来判断
#include <iostream> #include <cstdio> using namespace std; //定义一个共用体类型:多个成员共享一个成员的空间,共享的是所占内存空间最大的那个成员 union Info{ int num; //四字节整数 char ch; //一字节 }; int main(int argc, const char *argv[]) { //定义一个共用体变量 union Info temp; //给其整形成员赋值 temp.num = 0x12345678; //判断其ch成员 if(temp.ch == 0x12){ cout<<"big endian"<<endl; }else if(temp.ch == 0x78){ cout<<"little endian"<<endl; } std::cout << "Hello, World!" << std::endl; return 0; }

        而由于不同主机之间存储方式不同,可能会出现,小端存储的主机中的多字节整数,在网络传输过程中,明明没有出现任何问题,但是,由于大小端存储问题,导致,多字节整数传输出现错误。

        基于此,我们引入的网络字节序的概念,规定网络字节序都是大端存储的。

        无论发送端是大端存储还是小端存储,在传输多字节整数时,一律先转换为网络字节序。经由网络传输后,到达目的主机后,在转换为主机字节序即可。

        系统给大家提供了一套有关网络字节序和主机字节序之间相互转换的函数

  • 主机:host
  • 网络:network
  • 转换:to
#include <arpa/inet.h> //将4字节整数主机字节序转换为网络字节序,参数是主机字节序,返回值是网络字节序 uint32_t htonl(uint32_t hostlong); //将2字节整数主机字节序转换为网络字节序,参数是主机字节序,返回值是网络字节序 uint16_t htons(uint16_t hostshort); //将4字节整数的网络字节序转换为主机字节序,参数是网络字节序,返回值是主机字节序 uint32_t ntohl(uint32_t netlong); //将2字节整数的网络字节序转换为主机字节序,参数是网络字节序,返回值是主机字节序 uint16_t ntohs(uint16_t netshort); 

        那么我们何时使用网络字节序转换函数呢?

  • 在进行多字节整数网络传输时,需要使用字节序转换函数,最典型的就是我们的端口号
  • 在进行单字节整数传输时,不需要使用
  • 在网络中传输字符串时,也不需要使用

二、IP地址

        此处在我们之前计网篇章也讲过了,但此处我们再提及下个别概念

  • ip地址是主机在网络中的唯一标识,由两部分组成,分别是网络号和主机号。
    • 网络号:确定计算机所从属的网络
    • 主机号:标识该设备在该网络中的一个编号

        在网络传输过程中,给网络传输载体必须添加的信息,指定源ip地址和目的ip地址,以便于找到目的主机

IP地址的分类

        IPv4:是使用4字节无符号整数表示的一个ip地址,取值范围 [0, 2^32-1] 一共有四十多亿个,很明显不够,我们采用相关技术进行扩充局域网扩充:为了解决ip地址不够用,让多个主机共享一个ip地址 WAN:wide area network (广域网) LAN:local area network(局域网)

        IPv6:是使用16字节无符号整数表示的一个ip地址,取值范围 【0, 2^128-1】

        注意:IPv6是不兼容IPv4的。

网络类型取值范围网络号个数主机号个数用途
A类网络1.0.0.0 --- 127.255.255.2552^72^24已经保留不供给使用
B类网络128.0.0.0 --- 191.255.255.2552^142^16名地址网管中心
C类网络192.0.0.0 --- 223.255.255.2552^212^8家庭、校园、公司使用
D类网络224.0.0.0 --- 239.255.255.255------组播IP
E类网络240.0.0.0 --- 255.255.255.255------保留、实验室使用

特殊的IP地址

1、网络号 + 全为0的主机号:表示该网络,不分配给任何主机使用,例如:192.168.10.0



2、网络号 + 全为1的主机号:表示当前网络的广播地址,也不分配给任何主机使用,例如: 192.168.10.255



3、网络号 + 主机号为1:默认表示网关,当然可以自己制定网关ip



4、127.0.0.0:本地环回ip,当没有网络时,用于测试当前主机的ip



5、0.0.0.0:表示当前局域网中的任意一个主机号



6、255.255.255.255:一般表示广播地址

点分十进制

        为了方便记忆,我们将ip地址的每一个字节单独计算出十进制数据,并用点进行分割,这种方式,称为点分十进制,在程序中使用的是字符串来存储的。但是,ip地址的本质是4字节无符号整数,在网络中进行传输时,需要使用的是4字节无符号整数,而不是点分十进制的字符串。此时,就需要引入关于点分十进制数据向4字节无符号整数转换的相关函数

        地址:address

        网络:network

        转换:to

#include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> //将点分十进制的ip地址转换为4字节无符号整数的网络字节序, //参数时点分十进制数据,返回值时4字节无符号整数 in_addr_t inet_addr(const char *cp); //将4字节无符号整数的网络字节序,转换为点分十进制的字符串 char *inet_ntoa(struct in_addr in);

三、端口号

        端口号(port)是一个 2 字节的无符号整数表示的数字,取值范围 【0, 65535】,为了区分同一个主机之间的每个进程的,使用端口号来进行标识

        为什么不使用进程号标识,而使用端口号?

        答:因为进程号是进程的唯一标识,当同一个应用程序,关闭再打开后,并不是同一个进程号了,但是是同一个应用程序

        所以,端口号标识的是我们的应用程序,当一个应用程序关闭再打开后,端口号不变

        引入端口号后,网络通信的两个重要因素就集结完毕:ip 地址 : 端口号ip地址可以在网络中,唯一确定对端的主机地址通过端口号能够找到该主机中指定的对端应用程序

端口号的分类

        1、0~1023 :众所周知的 “VIP”端口号:被特殊的应用程序已经占用了的。

        2、1024 ~ 49151:用户可分配的端口号

        3、49152~65535:动态分配或系统自动分配的端口号


网络编程基础

一、套接字(socket)的概念

        Socket 是操作系统提供的一个接口,它封装了底层网络细节。程序员通过 Socket 函数创建、绑定、连接、发送、接收数据,就像操作文件一样方便。

        我们可以调用函数:socket(),创建一个用于通信的套接字端点,并返回该端点对应的文件描述符

        在通信端点中,有两个缓冲区,分别对应发送缓冲区和接受缓冲区

函数原型int socket(int domain, int type, int protocol);
头文件

sys/types.h

sys/socket.h

功能为通信创建一个端点,并返回该端点对应的文件描述符,文件描述符的使用原则是最小未分配 原则
参数说明

参数1:协议族,常用的协议族如下

        AF_UNIX, AF_LOCAL     本地通信,同一主机的多进程通信

        AF_INET             提供IPv4的相关通信方式

        AF_INET6               提供IPv6的相关通信方式

具体可以看man 7 ip

参数2:通信类型,指定通信语义,常用的通信类型如下

        SOCK_STREAM     支持TCP面向连接的通信协议

        SOCK_DGRAM       支持UDP面向无连接的通信协议

参数3:通信协议,当参数2中明确指定特定协议时,参数3可以设置为0,但是有多个协议共同使用时,需要用参数3指定当前套接字确定的协议

返回值成功返回创建的端点对应的文件描述符,失败返回-1并置位错误码

二、基于TCP面向连接的通信方式

        在网络通信过程中,有两种通信方式,分别是基于BS模型的,即浏览器服务器模型,和基于CS模型,即客户端服务器模型,我们本篇章,使用的是基于CS模型

        那么通信原理就是下图

        值得一提的是,我们此处服务器用完listen启动监听状态,并不是我们服务器自身在等,而是相当于我们的服务器招了一位经理,这位经理来帮我们处理发来的连接请求,且这位经理还有着队列结构,当有人发来连接后,我们的经理就会看此时服务器有没有人在连接中,如果有就将其放在队列中,直到服务器的对端关闭了后再进行接受连接。

        造成这一点的主要原因是上面的示例是单线程阻塞模型,同一时间只能服务一个客户端(此处的 TCP 示例中无法同时处理多个新连接,需要配合多进程/多线程/IO多路复用)。

        你可以把这一套操作想象成电话机:

  • 创建 Socket = 买一部电话机
  • Bind = 给电话机分配一个号码(端口)
  • Listen/Accept = 等待别人打进来
  • Connect = 主动拨打别人的号码
  • Send/Recv = 通话内容

 📖 bind函数

函数原型int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
头文件

sys/types.h

sys/socket.h

功能为套接字分配名称,给套接字绑定ip地址和端口号
参数说明

参数1:要被绑定的套接字文件描述符

参数2:通用地址信息结构体,对于不同的通信域而言,使用的实际结构体是不同的,该结构体的目 的是为了强制类型转换,防止警告

        通信域为:AF_INET而言,ipv4的通信方式

        struct sockaddr_in {

              sa_family_t    sin_family; /* 地址族: AF_INET */      

              in_port_t      sin_port;   /* 端口号的网络字节序 */

              struct in_addr sin_addr;   /* 网络地址 */

        };

        /* Internet address. */

        struct in_addr {

              uint32_t       s_addr;     /* ip地址的网络字节序 */

        };

        通信域为:AF_UNIX而言,本地通信

        struct sockaddr_un {

              sa_family_t sun_family;               /* 通信域:AF_UNIX */

              char        sun_path[UNIX_PATH_MAX];    /* 通信使用的文件 */

        };

参数3:参数2的大小

返回值成功返回0,失败返回-1并置位错误码

 📖 listen函数

函数原型int listen(int sockfd, int backlog);
头文件

sys/types.h

sys/socket.h

功能将套接字设置成被动监听状态
参数说明

参数1:套接字文件描述符

参数2:挂起队列能够增长的最大长度,一般为128

返回值成功返回0,失败返回-1并置位错误码

 📖 accept函数

函数原型int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
头文件

sys/types.h

sys/socket.h

功能阻塞等待客户端的连接请求,如果已连接队列中有客户端,则从连接队列中拿取第一个,并创 建一个用于通信的套接字
参数说明

参数1:服务器套接字文件描述符

参数2:通用地址信息结构体,用于接受已连接的客户端套接字地址信息的

参数3:接收参数2的大小

返回值成功发那会一个新的用于通信的套接字文件描述符,失败返回-1并置位错误码

 📖 recv、send数据收发

函数原型ssize_t recv(int sockfd, void *buf, size_t len, int flags);ssize_t send(int sockfd, const void *buf, size_t len, int flags);
头文件

sys/types.h

sys/socket.h

sys/types.h

sys/socket.h

功能从套接字中读取消息放入到buf中向套接字文件描述符中将buf这个容器中的内容写入
参数说明

参数1:通信的套接字文件描述符

参数2:要存放数据的起始地址

参数3:读取的数据的大小

参数4:读取标识位,是否阻塞读取

      0:表示阻塞等待

      MSG_DONTWAIT:非阻塞

参数1:通信的套接字文件描述符

参数2:要发送的数据的起始地址

参数3:发送的数据的大小

参数4:发送标识位,是否阻塞发送

      0:表示阻塞等待

      MSG_DONTWAIT:非阻塞

返回值

可以是大于0:表示成功读取的字节个数

可以是等于0:表示对端已经下线(针对于TCP通信)

失败返回-1,并置位错误码

成功返回发送字节的个数

失败返回-1,并置位错误码


 📖 close关闭套接字

函数原型int close(int fd);
头文件

unistd.h

功能关闭套接字文件描述符
参数说明要关闭的套接字文件描述符
返回值成功返回0,失败返回-1并置位错误码

 📖 connect连接函数

函数原型int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);
头文件

sys/types.h

sys/socket.h

功能将指定的套接字,连接到给定的地址上
参数说明

参数1:要连接的套接字文件描述符

参数2:通用地址信息结构体

参数3:参数2的大小

返回值成功返回0,失败返回-1并置位错误码

三、TCP的C/S模型

服务端代码实现

#include<iostream> #include<cstdio> #include<cstring> #include <sys/types.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> #define SER_PORT 8888 #define SER_IP "192.168.160.129" int main(int argc, const char *argv[]){ // 1、创建用于连接的套接字文件描述符 int sfd = socket(AF_INET, SOCK_STREAM, 0); if(sfd == -1){ perror("socket error"); return -1; } printf("socket success sfd = %d\n", sfd); // 2、绑定ip地址与端口号 struct sockaddr_in sin; sin.sin_family = AF_INET; // 此处绑定数字都为网络字节序 sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP); if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1){ perror("bind error"); return -1; } printf("bind success\n"); // 3、启动监听 if(listen(sfd, 128) == -1){ perror("listen error"); return -1; } printf("listen success\n"); // 4、阻塞等待客户端的连接请求 struct sockaddr_in cin; socklen_t socklen = sizeof(cin); int newfd = accept(sfd, (struct sockaddr*)&cin, &socklen); if(newfd == -1){ perror("accept error"); return -1; } printf("[%s:%d]:已连接成功!!!\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port)); // 5、数据收发 char rbuf[128] = ""; while(1){ bzero(rbuf, sizeof(rbuf)); int res = recv(newfd, rbuf, sizeof(rbuf), 0); if(res == 0){ printf("对端已下线\n"); break; } printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), rbuf); strcat(rbuf, "*_*"); if(send(newfd, rbuf, strlen(rbuf), 0) == -1){ perror("send error"); return -1; } printf("send success\n"); } // 6、关闭套接字 close(newfd); close(sfd); return 0; }

客户端代码实现

#include<iostream> #include<cstdio> #include<cstring> #include <sys/types.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> using namespace std; #define SER_PORT 8888 #define SER_IP "192.168.160.129" #define CLI_PORT 9999 #define CLI_IP "192.168.160.129" int main(int argc, const char *argv[]){ // 1、创建用于通信的客户端套接字文件描述符 int cfd = socket(AF_INET, SOCK_STREAM, 0); if(cfd == -1){ perror("socket error"); return -1; } printf("socket success cfd = %d\n", cfd); // 3 // 2、绑定ip与端口号(可选) struct sockaddr_in cin; cin.sin_family = AF_INET; cin.sin_port = htons(CLI_PORT); cin.sin_addr.s_addr = inet_addr(CLI_IP); if(bind(cfd, (struct sockaddr*)&cin, sizeof(cin)) == -1){ perror("bind error"); return -1; } printf("绑定成功\n"); // 3、链接服务器 struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP); if(connect(cfd, (struct sockaddr*)&sin, sizeof(sin)) == -1){ perror("connect error"); return -1; } printf("连接服务器成功\n"); // 4、数据收发 char wbuf[128] = ""; while(1){ bzero(wbuf, sizeof(wbuf)); fgets(wbuf, sizeof(wbuf), stdin); wbuf[strlen(wbuf) - 1] = 0; // 将换行改掉 if(send(cfd, wbuf, sizeof(wbuf), 0) == -1){ perror("send error"); return -1; } if(recv(cfd, wbuf, sizeof(wbuf), 0) == 0){ printf("对端已下线\n"); break; } printf("收到服务器消息为:%s\n",wbuf); } // 5、关闭套接字 close(cfd); return 0; }

四、基于UDP面向无连接的通信方式

        udp通信是面向无连接的,不可靠的,尽最大努力传输的通信方式,传输过程中,可能会出现数据的丢失、重复、失序、乱序等现象创建通信套接字是,使用的传输层名称为:SOCK_DGRAM

        此处不难发现我们两种模型的函数其实也就只有数据的收发不一样

 📖 recvfrom、sendto数据收发

函数原型ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,struct sockaddr *src_addr, socklen_t *addrlen);ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,const struct sockaddr *dest_addr, socklen_t addrlen);
头文件

sys/types.h

sys/socket.h

sys/types.h

sys/socket.h

功能从套接字中读取消息放入到buf中,并接受对端的地址信息结构体向套接字中发送消息,并且指定对方的地址信息结构体
参数说明

参数1:套接字文件描述符

参数2:存放数据的容器起始地址

参数3:读取的数据大小

参数4:是否阻塞

        0表示阻塞

        MSG_DONTWAIT表示非阻塞

参数5:接收对端地址信息结构体的容器

参数6:参数5的大小

参数1:套接字文件描述符

参数2:要发送的数据的起始地址

参数3:要发送的数据大小

参数4:是否阻塞

        0表示阻塞

        MSG_DONTWAIT表示非阻塞

参数5:要发送的对端地址信息结构体

参数6:参数5的大小

返回值成功返回读取字节的个数,失败返回-1并置位错误码成功返回发送的字节的个数,失败返回-1并置位错误码

五、UDP的C/S模型

服务端代码实现

#include<iostream> #include<cstdio> #include<cstring> #include <sys/types.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> using namespace std; #define SER_PORT 8888 #define SER_IP "192.168.160.129" int main(int argc, const char *argv[]){ // 1、创建用于连接的套接字文件描述符 int sfd = socket(AF_INET, SOCK_DGRAM, 0); if(sfd == -1){ perror("socket error"); return -1; } printf("socket success sfd = %d\n", sfd); // 2、绑定ip地址与端口号 struct sockaddr_in sin; sin.sin_family = AF_INET; // 此处绑定数字都为网络字节序 sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP); if(bind(sfd, (struct sockaddr*)&sin, sizeof(sin)) == -1){ perror("bind error"); return -1; } printf("bind success\n"); // 3、数据收发 struct sockaddr_in cin; socklen_t socklen = sizeof(cin); char rbuf[128] = ""; while(1){ bzero(rbuf, sizeof(rbuf)); if(recvfrom(sfd, rbuf, sizeof(rbuf), 0, (struct sockaddr*)&cin, &socklen) == -1){ perror("recvfrom error"); break; } printf("[%s:%d]:%s\n", inet_ntoa(cin.sin_addr), ntohs(cin.sin_port), rbuf); strcat(rbuf, "*_*"); sendto(sfd, rbuf, strlen(rbuf), 0, (struct sockaddr*)&cin, sizeof(cin)); printf("send success\n"); } // 4、关闭套接字 close(sfd); return 0; }

客户端代码实现

#include<iostream> #include<cstdio> #include<cstring> #include <sys/types.h> #include <unistd.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/socket.h> using namespace std; #define SER_PORT 8888 #define SER_IP "192.168.160.129" #define CLI_PORT 9999 #define CLI_IP "192.168.160.129" int main(int argc, const char *argv[]){ // 1、创建用于连接的套接字文件描述符 int cfd = socket(AF_INET, SOCK_DGRAM, 0); if(cfd == -1){ perror("socket error"); return -1; } printf("socket success sfd = %d\n", cfd); // 2、绑定ip地址与端口号 struct sockaddr_in cin; cin.sin_family = AF_INET; cin.sin_port = htons(CLI_PORT); cin.sin_addr.s_addr = inet_addr(CLI_IP); if(bind(cfd, (struct sockaddr*)&cin, sizeof(cin)) == -1){ perror("bind error"); return -1; } printf("绑定成功\n"); // 3、数据收发 struct sockaddr_in sin; sin.sin_family = AF_INET; sin.sin_port = htons(SER_PORT); sin.sin_addr.s_addr = inet_addr(SER_IP); char wbuf[128] = ""; while(1){ bzero(wbuf, sizeof(wbuf)); fgets(wbuf, sizeof(wbuf), stdin); wbuf[strlen(wbuf) - 1] = 0; // 将换行改成换行 sendto(cfd, wbuf, strlen(wbuf), 0, (struct sockaddr*)&sin, sizeof(sin)); printf("send success\n"); recvfrom(cfd, wbuf, sizeof(wbuf), 0, NULL, NULL); printf("服务器发来的消息为:%s\n", wbuf); } // 4、关闭套接字 close(cfd); return 0; }

结语

网络编程其实并不神秘,核心就是:创建 Socket -> 绑定/连接 -> 收发数据 -> 关闭

  • TCP 像打电话,建立连接后放心聊,适合重要数据。
  • UDP 像寄信,扔进邮筒就不管了,适合实时性要求高的场景。

        掌握了这些基础 API,你就拿到了通往高性能服务器开发的大门钥匙,但我们目前并不能让我们的TCP服务器实现多台客户端的连接,那么下一步我将带着大家进入TCP并发服务器的学习。

我是YYYing,后面还有更精彩的内容,希望各位能多多关注支持一下主包。

无限进步,我们下次再见!


---⭐️封面自取⭐️---

Read more

宇树科技Go2机器人强化学习(RL)开发实操指南

宇树科技Go2机器人强化学习(RL)开发实操指南

在Go2机器人的RL开发中,环境配置、模型训练、效果验证与策略部署的实操步骤是核心环节。本文基于宇树科技官方文档及开源资源,以Isaac Gym和Isaac Lab两大主流仿真平台为核心,提供从环境搭建到实物部署的全流程操作步骤,覆盖关键命令与参数配置,帮助开发者快速落地RL开发。 一、基础准备:硬件与系统要求 在开始操作前,需确保硬件与系统满足RL开发的基础需求,避免后续因配置不足导致训练中断或性能瓶颈。 类别具体要求说明显卡NVIDIA RTX系列(显存≥8GB)需支持CUDA加速,Isaac Gym/Isaac Lab均依赖GPU进行仿真与训练操作系统Ubuntu 18.04/20.04/22.04推荐20.04版本,兼容性最佳,避免使用Windows系统(部分依赖不支持)显卡驱动525版本及以上需与CUDA版本匹配(如CUDA 11.3对应驱动≥465.19.01,CUDA 11.8对应驱动≥520.61.05)软件依赖Conda(

By Ne0inhk

一、FPGA到底是什么???(一篇文章让你明明白白)

一句话概括 FPGA(现场可编程门阵列) 是一块可以通过编程来“变成”特定功能数字电路的芯片。它不像CPU或GPU那样有固定的硬件结构,而是可以根据你的需求,被配置成处理器、通信接口、控制器,甚至是整个片上系统。 一个生动的比喻:乐高积木 vs. 成品玩具 * CPU(中央处理器):就像一个工厂里生产好的玩具机器人。它的功能是固定的,你只能通过软件(比如按不同的按钮)来指挥它做预设好的动作(走路、跳舞),但你无法改变它的机械结构。 * ASIC(专用集成电路):就像一个为某个特定任务(比如只会翻跟头)而专门设计和铸造的金属模型。性能极好,成本低(量产时),但一旦制造出来,功能就永远无法改变。 * FPGA:就像一盒万能乐高积木。它提供了大量基本的逻辑单元(逻辑门、触发器)、连线和接口模块。你可以通过“编程”(相当于按照图纸搭建乐高)将这些基本模块连接起来,构建出你想要的任何数字系统——可以今天搭成一个CPU,明天拆了重新搭成一个音乐播放器。 “现场可编程”

By Ne0inhk
基于FPGA的CLAHE自适应限制对比度直方图均衡算法硬件verilog实现

基于FPGA的CLAHE自适应限制对比度直方图均衡算法硬件verilog实现

基于FPGA的CLAHE自适应限制对比度直方图均衡算法硬件verilog实现 摘要:本文详细阐述了基于 FPGA 的 CLAHE(自适应限制对比度直方图均衡)算法的硬件verilog实现方案。CLAHE是一种强大的图像增强算法,广泛应用于医学影像、红外成像、低照度增强等领域。本文将从算法原理出发,深入讲解各模块的RTL架构设计,包括坐标计数器、直方图统计、CDF计算、双线性插值映射以及乒乓RAM管理等核心模块的实现细节。 项目开源地址:https://github.com/Passionate0424/CLAHE_verilog 开源不易,辛苦各位看官点点star!! 一、CLAHE算法基本原理 1.1 算法背景 CLAHE(Contrast Limited Adaptive Histogram Equalization,对比度受限的自适应直方图均衡)是对传统自适应直方图均衡(AHE)的改进。AHE通过将图像划分为多个子区域(称为 “Tiles”),对每个Tile独立进行直方图均衡化,从而适应图像的局部特性。然而,AHE在噪声较大的平坦区域(如天空、

By Ne0inhk

Telegram搜索机器人推荐——查找海量资源,提升信息检索效率

大家好,本文首发于 ZEEKLOG 博客,主要面向需要在 Telegram 中高效检索资源的同学。我结合自己的实测体验,总结了几款实用的搜索机器人与完整操作流程,帮助大家解决“怎么快速找到频道、群组、文件”的痛点。如果你也在为信息筛选耗时头疼,建议耐心读完并亲手试试,收获会很大。觉得有帮助别忘了给个点赞、收藏和关注支持一下 🙂 📚 本文目录 * 使用准备 * 什么是Telegram搜索机器人? * Telegram搜索机器人的核心功能 * 推荐的Telegram搜索机器人 * 如何使用Telegram搜索机器人? * Telegram搜索机器人的应用场景 * 总结 在信息爆炸的时代,如何高效获取自己想要的资源?Telegram搜索机器人为你带来全新解决方案,无需翻找频道、群组,只需输入关键词,即可一键查找海量内容。无论是影视剧、电子书、图片还是优质群组,Telegram搜索机器人都能帮你轻松找到。推荐搜索机器人:@soso、@smss、@jisou 使用准备 1. 能访问外网,不会魔法的同学请参考:这里 2. 安装 Telegram

By Ne0inhk