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

网络基础(2)

目录

    • 1. 端口号
    • 2. 套接字socket
    • 3. 网络通信
      • 3.1 sockaddr与sockaddr_in
      • 3.2 接口
        • 服务端
        • 3.2.1 创建套接字,打开网络文件
        • 3.2.2 给该服务器绑定端口和ip(特殊处理)
        • 3.2.3 初始化相关服务器
        • 3.2.4 提供服务
        • 客户端
        • 3.2.5 绑定
        • 3.2.6 使用服务
    • 4. makefile实现:
    • 5. 整体代码

1. 端口号

IP能够唯一地标识互联网中的一台主机;而端口号能够唯一地标识一台机器上的唯一一个进程。

那么这两个因素加起来,就构成了能够标识互联网上的唯一一个进程。这就是端口号与IP的联系。

2. 套接字socket

整个网络看作是一个大的OS,所有网络上网行为,都是在这里面实现进程间通信的。

IP地址+port端口号 = socket

端口号之于进程PID,就相当于身份证号之于学生学号。

  • 为什么有了PID,在网络里还需要端口号来进行标识?

这就好比学校不用身份证来对学生信息进行管理、录入。因为学校内部会有更合适的管理系统,采用自己一套的管理方式能够使管理更加得心应手,也不惧怕外部环境的改变,实现与外部解耦。PID和端口号也是这个原理。

3. 网络通信

3.1 sockaddr与sockaddr_in

要实现网络通信,首先是要找到目标主机,其次是找到该主机上的目标进程。

而进程具有独立性,要实现网络通信需要让两个进程看到同一份资源,这份资源就是网络。

应用层的下一层就是传输层,传输层有TCP/UDP协议。

  • 统一接口

对于将要介绍的sock接口,要先说明一个概念,就是sockarr结构:由于各种网络协议的实现方式不同,导致接口需要对应设计,所以干脆就设计出来了一个通用的接口,该参数统一为sockaddr。

socket API是一层抽象的网络编程接口,适用于各种底层网络协议,如IPv4、IPv6,以及UNIX DomainSocket。 然而, 各种网络协议的地址格式并不相同。

目前我们采用的是struct sockaddr,这是一个通用接口,也即是说,可以兼容后两个接口类型的参数。

通用接口与其他两个不同协议的接口:

在这里插入图片描述

虽然socket api的接口是sockaddr, 但是我们真正在基于IPv4编程时, 使用的数据结构是sockaddr_in; 这个结构里主要有三部分信息: 地址类型, 端口号, IP地址。

sockaddr与sockaddr_in其实都是结构体类型:

  1. sockaddr

它把目标地址和端口信息实现在一起:

struct sockaddr{sa_family_t sin_family;//16位地址类型家族char sa_data[14];//14位长度地址数据,包含套接字中的目标地址和端口信息
}
  1. sockaddr_in

sockaddr_in的头文件包含在:

#include <netinet/in.h>
#include <arpa/inet.h>

该结构体解决了sockaddr的缺陷,把port和addr 分开储存在两个变量中,如下

typedef uint16_t in_port_t;//十六位数字struct sockaddr_in{sa_family_t   sin_family;//16位地址类型家族in_port_t sin_port;//16位端口号struct in_addr    sin_addr;//32位ip地址unsigned char sin_zero;//未使用
}

in_addr其实就是32位的IPv4地址:

typedef uint32_t in_addr_t;struct in_addr{In_addr_t  s_addr;    //32位IPv4地址
};
  1. sockaddr_un
struct sockaddr_un{//16位地址类型//108字节路径名
}

IPv4、IPv6地址类型分别定义为常数AF_INET、AF_INET6. 这样,只要取得某种sockaddr结构体的首地址,不需要知道具体是哪种类型的sockaddr结构体,就可以根据地址类型字段确定结构体中的内容。

socket API可以都用struct sockaddr * 类型表示, 在使用的时候需要强制转化成sockaddr_in; 这样的好处是程序的通用性, 可以接收IPv4, IPv6, 以及UNIX Domain Socket各种类型的sockaddr结构体指针做为参数。

3.2 接口

服务端

3.2.1 创建套接字,打开网络文件

// 创建 socket 文件描述符 (TCP/UDP, 客户端 + 服务器)
int socket(int domain, int type, int protocol);
  1. 参数:

domain就是用来区分传入的是哪个协议种类。

type代表协议特性,是套接字类型。比如流式套结、原始套结等。

proyocol在tcp中只需要全部设置为0。

  1. 返回值:创建成功后,新的套接字会返回一个文件描述符。
#include <iostream>
#include <cerrno>
int main()
{int sock = socket(AF_INET,SOCK_DGRAM,0);if(sock < 0){std::cout<<"socket create error!"<<errno<<std::endl;return 1;}std::cout<<"sock:"<<sock<<std::endl;return 0;
}

运行结果:

在这里插入图片描述

3.2.2 给该服务器绑定端口和ip(特殊处理)

作为一个服务器,对应的服务器地址(IP+port)是必须要被客户(人、软件、浏览器)所知晓的,并且不能轻易地被改变。

  • 首先要绑定端口号
// 绑定端口号 (TCP/UDP, 服务器)
int bind(int sockfd, const struct sockaddr *addr,
socklen_t address_len);
  1. 参数

sockfd代表创建好的套接字的文件描述符;addr代表需要用户指定服务器的相关socket信息,address_len代表传入的结构体大小。

  1. 返回值

绑定成功返回0,失败返回-1,也可以是错误码。

3.2.3 初始化相关服务器

这里的参数addr是相关服务器的信息,所以要bind,就要先初始化好addr的信息:

首先定义一个sockaddr_in类型的结构体,因为我们平时传的addr其实是sockaddr_in类型的,只需要在绑定的时候做一下强转即可。

那么接下来就是将它里面的对应字段全部初始化:

const uint16_t port = 8080;
struct sockaddr_in local;
local.sin_family = AF_INET; //IPV4
local.sin_port = htons(port); //该端口号是主机序列,要改成网络序列
local.sin_addr.s_addr = INADDR_ANY;

其中port是主机序列,需要转为网络序列,使用的是htons函数:

h是host代表主机,n代表network网络:

在这里插入图片描述

剩下最后一个字段初始化为INADDR_ANY的意思是:由该服务器发送的,到任意主机(IP)上的数据都能被你的网络进程接收。

如果不这样初始化,而是绑定了确定的IP,例如:

//inet_addr函数将地址从数点法转化为整数IP
//网络需要的是整数表示IP而不是数点法表示的地址
local.sin_addr.s_addr = inet_addr("xx.xxx.xx.xxx");

这样的话,只有发送到该IP的数据才能被你进程接收,所以一般不这么写。

并且云服务器不直接绑定公网IP。

然后就可以进行绑定:

if(bind(sock,(struct sockaddr*)&lockal,sizeof(local)) < 0){std::cout << "bind error : " << errno << std::endl;return 2;
}

3.2.4 提供服务

绑定完成以后,等待他人给自己的服务器发送信息,是接收的过程。

  • recvfrom

udp的数据读取不是用文件的接口,而是有专门的接口recvfrom:

在这里插入图片描述

参数:分别是自身套接字fd、读完的数据放到buf、读len大小、读的方式flag(默认0),剩下最后两个代表的是和你的服务器通信的客户端的信息,是一个输出型参数,作用是如需要返回数据给它,就可以拿到其地址返回。

  • sendto

给对端发信息,用的是sendto

其他参数都一样,最后两个参数代表的是要给谁发:

在这里插入图片描述

由此可以提供服务:

// 3. 提供服务
#define NUM 1024
char buffer[NUM];
bool quit = false;
while(!quit){struct sockaddr_in send;socklen_t len = sizeof(send);//开始读recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&send,&len);std::cout << "client" << buffer << std::endl;//收到以后回复hellostd::string echo = "hello";sendto(sock,echo.c_str(),echo.size(),0,(struct sockaddr*)&send,len);}

客户端

3.2.5 绑定

客户端不需要显示绑定。

对于客户端来说,也必须要有套接字,但是它不需要显示绑定。因为一旦绑定了,它就要和某一个端口关联了,该端口不一定会存在,或者被占用,那么该客户端将无法使用。

对于服务端来说,端口必须要明确,并且不能变更;而对于客户端,只要有端口就行,因为是客户端访问他人。所以客户端一般都是由OS在发送数据的时候自动绑定,采用的是随机端口。

// 1. 创建
int sockfd = socket(AF_INET,SOCK_DGRAM,0);
if(sockfd < 0){std::cout<<"socket create error!"<<errno<<std::endl;return 1;
}
std::cout<<"sock:"<<sockfd<<std::endl;

3.2.6 使用服务

实现基本和服务端一样,但是由于自定义遵守./udp_client server_ip server_port的格式,所以要用到命令行参数来规范行为,不规范的话会打印使用手册。

void Usage(std::string proc)
{std::cout << "Usage: \n\t"<< proc << " server_ip server_port" << std::endl;
}
int main(int argc,char* argv[])
{if(argc != 3){Usage(argv[0]);return 0;}// 1. 创建int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(sockfd < 0){std::cout<<"socket create error!"<<errno<<std::endl;return 1;}std::cout<<"client:sock:"<<sockfd<<std::endl;//客户端也必须要有套接字//不需要显示bind// 2. 使用服务// ./udp_client server_ip server_port的格式struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(atoi(argv[2]));server.sin_addr.s_addr = inet_addr(argv[1]);while(1){// 1. 数据来源 std::string s;std::cout<<"输入# ";std::cin>>s;// 2. 发给谁sendto(sockfd,s.c_str(),s.size(),0,(struct sockaddr*)&server,sizeof(server));//此处tmp就是一个”占位符“struct sockaddr_in tmp;socklen_t len = sizeof(tmp);char buffer[1024];recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&tmp, &len);std::cout << "server echo# " << buffer << std::endl;}return 0; 
}

由于云服务器需要手动在后台开通窗口才可通信,所以IP使用127.0.0.1来测试

下列运行结果:

客户端:
在这里插入图片描述

服务端:

在这里插入图片描述

4. makefile实现:

.PHONY:all
all:udp_server udp_clientudp_server:udp_server.ccg++ -o $@ $^ -std=c++11udp_client:udp_client.ccg++ -o $@ $^ -std=c++11.PHONY:clean
clean:rm -f udp_server udp_client

5. 整体代码

服务端:

#include <netinet/in.h>
#include <arpa/inet.h>
#include <iostream>
#include <cerrno>const uint16_t port = 8080;
int main()
{// 1. 创建套接字,打开网络文件int sock = socket(AF_INET,SOCK_DGRAM,0);if(sock < 0){std::cout<<"socket create error!"<<errno<<std::endl;return 1;}std::cout<<"server:sock:"<<sock<<std::endl;// 2. 给该服务器绑定端口和ipstruct sockaddr_in local;local.sin_family = AF_INET; //IPV4local.sin_port = htons(port); //该端口号是主机序列,要改成网络序列local.sin_addr.s_addr = INADDR_ANY;if(bind(sock,(struct sockaddr*)&local,sizeof(local)) < 0){std::cout << "bind error : " << errno << std::endl;return 2;}// 3. 提供服务#define NUM 1024char buffer[NUM];bool quit = false;while(!quit){struct sockaddr_in send;socklen_t len = sizeof(send);recvfrom(sock,buffer,sizeof(buffer)-1,0,(struct sockaddr*)&send,&len);std::cout << "client say# " << buffer << std::endl;//收到以后回复hellostd::string echo = "hello";sendto(sock,echo.c_str(),echo.size(),0,(struct sockaddr*)&send,len);}return 0;
}

客户端:

#include <iostream>
#include <string>
#include <cerrno>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>//正确用法
// ./udp_client server_ip server_port的格式
void Usage(std::string proc)
{std::cout << "Usage: \n\t"<< proc << " server_ip server_port" << std::endl;
}
int main(int argc,char* argv[])
{if(argc != 3){Usage(argv[0]);return 0;}// 1. 创建int sockfd = socket(AF_INET,SOCK_DGRAM,0);if(sockfd < 0){std::cout<<"socket create error!"<<errno<<std::endl;return 1;}std::cout<<"client:sock:"<<sockfd<<std::endl;//客户端也必须要有套接字//不需要显示bind// 2. 使用服务// ./udp_client server_ip server_port的格式struct sockaddr_in server;server.sin_family = AF_INET;server.sin_port = htons(atoi(argv[2]));server.sin_addr.s_addr = inet_addr(argv[1]);while(1){// 1. 数据来源 std::string s;std::cout<<"输入# ";std::cin>>s;// 2. 发给谁sendto(sockfd,s.c_str(),s.size(),0,(struct sockaddr*)&server,sizeof(server));//此处tmp就是一个”占位符“struct sockaddr_in tmp;socklen_t len = sizeof(tmp);char buffer[1024];recvfrom(sockfd, buffer, sizeof(buffer), 0, (struct sockaddr*)&tmp, &len);std::cout << "server echo# " << buffer << std::endl;}return 0; 
}

相关文章:

网络基础(2)

目录1. 端口号2. 套接字socket3. 网络通信3.1 sockaddr与sockaddr_in3.2 接口服务端3.2.1 创建套接字&#xff0c;打开网络文件3.2.2 给该服务器绑定端口和ip&#xff08;特殊处理&#xff09;3.2.3 初始化相关服务器3.2.4 提供服务客户端3.2.5 绑定3.2.6 使用服务4. makefile实…...

掌握Spring Cloud Gateway:构建高性能API网关的原理和实践

Spring Cloud Gateway 是一个基于 Spring Boot 的 API 网关&#xff0c;用于构建微服务架构中的网关服务。它提供了统一的路由、请求转发、过滤器、负载均衡、熔断等功能&#xff0c;帮助开发者更好地管理和控制微服务系统的请求流量。 本文将介绍 Spring Cloud Gateway 的原理…...

NAST概述

一、NATS介绍 NATS是由CloudFoundry的架构师Derek开发的一个开源的、轻量级、高性能的&#xff0c;支持发布、订阅机制的分布式消息队列系统。它的核心基于EventMachine开发&#xff0c;代码量不多&#xff0c;可以下载下来慢慢研究。 不同于Java社区的kafka&#xff0c;nats…...

【JS知识点】——原型和原型链

文章目录原型和原型链构造函数原型显式原型&#xff08;prototype&#xff09;隐式原型&#xff08;\_\_proto\_\_&#xff09;原型链总结原型和原型链 在js中&#xff0c;原型和原型链是一个非常重要的知识点&#xff0c;只有理解原型和原型链&#xff0c;才能深刻理解JS。在…...

c盘怎么清理到最干净?有什么好的清理方法

c盘怎么清理到最干净?有什么好的清理方法&#xff1f;清理C盘空间是电脑维护的重要步骤之一。C盘是Windows操作系统的核心部分&#xff0c;保存了许多重要的系统文件&#xff0c;因此空间不足会影响计算机的性能和稳定性。下面是一些清理C盘空间的方法 一.清理临时文件 在使用…...

day26_HTML

今日内容 上课同步视频:CuteN饕餮的个人空间_哔哩哔哩_bilibili 同步笔记沐沐霸的博客_CSDN博客-Java2301 零、 复习昨日 一、二阶段介绍 二、HTML 零、 复习昨日 见代码 一、二阶段介绍 第一阶段: 基础入门 java基本语法编程基础(方法,数组)面向对象编程常用类高级(IO,线程,新…...

深度剖析C语言预处理

致前行的人&#xff1a; 人生像攀登一座山&#xff0c;而找寻出路&#xff0c;却是一种学习的过程&#xff0c;我们应当在这过程中&#xff0c;学习稳定冷静&#xff0c;学习如何从慌乱中找到生机。 目录 1.程序翻译过程&#xff1a; 2.字符串宏常量 3.用宏定义充当注释符号 4…...

【WPF 值转换器】ValueConverter 进阶用法

【WPF 值转换器】ValueConverter 进阶用法介绍基类实现子类实现效果介绍 值转换器在WPF开发中是非常常见的&#xff0c;当然不仅仅是在WPF开发中。值转换器可以帮助我们很轻松地实现&#xff0c;界面数据展示的问题&#xff0c;如&#xff1a;模块隐藏显示、编码数据展示为可读…...

Vue2的基本使用

一、vue的基本使用 第一步 引入vue.js文件 <script src"https://cdn.staticfile.org/vue/2.7.0/vue.min.js"></script> 或者<script src"./js/vue.js"></script> 第二步 在body中设置一个挂载点 {{msg}} <div id"app…...

【云原生kubernetes】k8s数据存储之Volume使用详解

目录 一、什么是Volume 二、k8s中的Volume 三、k8s中常见的Volume类型 四、Volume 之 EmptyDir 4.1 EmptyDir 特点 4.2 EmptyDir 实现文件共享 4.2.1 关于busybox 4.3 操作步骤 4.3.1 创建配置模板文件yaml 4.3.2 创建Pod 4.3.3 访问nginx使其产生访问日志 4.3.4 …...

SerDes---CDR技术

1、为什么需要CDR 时钟数据恢复主要完成两个工作&#xff0c;一个是时钟恢复&#xff0c;一个是数据重定时&#xff0c;也就是数据的恢复。时钟恢复主要是从接收到的 NRZ&#xff08;非归零码&#xff09;码中将嵌入在数据中的时钟信息提取出来。 2、CDR种类 PLL-Based CDROve…...

如何实现在on ethernetPacket中自动回复NDP response消息

对于IPv4协议来说,如果主机想通过目标ipv4地址发送以太网数据帧给目的主机,需要在数据链路层填充目的mac地址。根据目标ipv4地址查找目标mac地址,这是ARP协议的工作原理 对于IPv6协议来说,根据目标ipv6地址查找目标mac地址,它使用的不是ARP协议,而是邻居发现NDP(Neighb…...

CSS清楚浮动

先看看关于浮动的一些性质 浮动使元素脱离文档流 浮动元素可以设置宽高&#xff0c;在CSS中&#xff0c;任何元素都可以浮动&#xff0c;浮动元素会生成一个块级框&#xff0c;而不论其本身是何种元素。 如果没有给浮动元素指定高度&#xff0c;&#xff0c;那么它会以内容的…...

HTTPS详解(原理、中间人攻击、CA流程)

摘要我们访问浏览器也经常可以看到https开头的网址&#xff0c;那么什么是https&#xff0c;什么是ca证书&#xff0c;认证流程怎样&#xff1f;这里一一介绍。原理https就是httpssl&#xff0c;即用http协议传输数据&#xff0c;数据用ssl/tls协议加密解密。具体流程如下图&am…...

EventLoop机制

JavaScript 是单线程的语言 JavaScript 是一门单线程执行的编程语言。也就是说&#xff0c;同一时间只能做一件事情。 单线程执行任务队列的问题&#xff1a; 如果前一个任务非常耗时&#xff0c;则后续的任务就不得不一直等待&#xff0c;从而导致程序假死的问题。 同步任…...

倒立摆建模

前言 系统由一辆具有动力的小车和安装在小车上的倒立摆组成&#xff0c;系统是不稳定&#xff0c;我们需要通过控制移动小车使得倒立摆保持平衡。 具体地&#xff0c;考虑二维情形如下图&#xff0c;控制力为水平力FFF&#xff0c;输出为角度θ\thetaθ以及小车的位置xxx。 力…...

SpringSecurity支持WebAuthn认证

WebAuthn是无密码身份验证技术&#xff0c;解决了密码泄露的风险&#xff0c;主流的浏览器都支持。有很多开源的类库实现了WebAuthn规范&#xff0c;Java下流行的类库有&#xff1a;webauthn4jjava-webauthn-serververtx-authSpring Security官方暂时未支持WebAuthn&#xff0c…...

深度学习技巧应用3-神经网络中的超参数搜索

大家好&#xff0c;我是微学AI&#xff0c;今天给大家带来深度学习技巧应用3-神经网络中的超参数搜索。 在深度学习任务中&#xff0c;一个算法模型的性能往往受到很多超参数的影响。超参数是指在模型训练之前需要我们手动设定的参数&#xff0c;例如&#xff1a;学习率、正则…...

【信号量机制及应用】

水善利万物而不争&#xff0c;处众人之所恶&#xff0c;故几于道&#x1f4a6; 目录 一、信号量机制 二、信号量的应用 >利用信号量实现进程互斥   >利用信号量实现前驱关系   >利用记录型信号量实现同步 三、例题 四、参考 一、信号量机制 信号量是操作系统提…...

围棋高手郭广昌的“假眼”棋局

&#xff08;图片来源于网络&#xff0c;侵删&#xff09;文丨熔财经作者|易不二2022年&#xff0c;在复星深陷债务压顶和变卖资产漩涡的而立之年&#xff0c;“消失”已久的郭广昌&#xff0c;在质疑与非议声中回国稳定军心&#xff0c;强调复星将在未来的五到十年迎来一个全新…...

K8S认证|CKS题库+答案| 11. AppArmor

目录 11. AppArmor 免费获取并激活 CKA_v1.31_模拟系统 题目 开始操作&#xff1a; 1&#xff09;、切换集群 2&#xff09;、切换节点 3&#xff09;、切换到 apparmor 的目录 4&#xff09;、执行 apparmor 策略模块 5&#xff09;、修改 pod 文件 6&#xff09;、…...

基于服务器使用 apt 安装、配置 Nginx

&#x1f9fe; 一、查看可安装的 Nginx 版本 首先&#xff0c;你可以运行以下命令查看可用版本&#xff1a; apt-cache madison nginx-core输出示例&#xff1a; nginx-core | 1.18.0-6ubuntu14.6 | http://archive.ubuntu.com/ubuntu focal-updates/main amd64 Packages ng…...

ServerTrust 并非唯一

NSURLAuthenticationMethodServerTrust 只是 authenticationMethod 的冰山一角 要理解 NSURLAuthenticationMethodServerTrust, 首先要明白它只是 authenticationMethod 的选项之一, 并非唯一 1 先厘清概念 点说明authenticationMethodURLAuthenticationChallenge.protectionS…...

微服务商城-商品微服务

数据表 CREATE TABLE product (id bigint(20) UNSIGNED NOT NULL AUTO_INCREMENT COMMENT 商品id,cateid smallint(6) UNSIGNED NOT NULL DEFAULT 0 COMMENT 类别Id,name varchar(100) NOT NULL DEFAULT COMMENT 商品名称,subtitle varchar(200) NOT NULL DEFAULT COMMENT 商…...

Ascend NPU上适配Step-Audio模型

1 概述 1.1 简述 Step-Audio 是业界首个集语音理解与生成控制一体化的产品级开源实时语音对话系统&#xff0c;支持多语言对话&#xff08;如 中文&#xff0c;英文&#xff0c;日语&#xff09;&#xff0c;语音情感&#xff08;如 开心&#xff0c;悲伤&#xff09;&#x…...

A2A JS SDK 完整教程:快速入门指南

目录 什么是 A2A JS SDK?A2A JS 安装与设置A2A JS 核心概念创建你的第一个 A2A JS 代理A2A JS 服务端开发A2A JS 客户端使用A2A JS 高级特性A2A JS 最佳实践A2A JS 故障排除 什么是 A2A JS SDK? A2A JS SDK 是一个专为 JavaScript/TypeScript 开发者设计的强大库&#xff…...

LLMs 系列实操科普(1)

写在前面&#xff1a; 本期内容我们继续 Andrej Karpathy 的《How I use LLMs》讲座内容&#xff0c;原视频时长 ~130 分钟&#xff0c;以实操演示主流的一些 LLMs 的使用&#xff0c;由于涉及到实操&#xff0c;实际上并不适合以文字整理&#xff0c;但还是决定尽量整理一份笔…...

4. TypeScript 类型推断与类型组合

一、类型推断 (一) 什么是类型推断 TypeScript 的类型推断会根据变量、函数返回值、对象和数组的赋值和使用方式&#xff0c;自动确定它们的类型。 这一特性减少了显式类型注解的需要&#xff0c;在保持类型安全的同时简化了代码。通过分析上下文和初始值&#xff0c;TypeSc…...

LOOI机器人的技术实现解析:从手势识别到边缘检测

LOOI机器人作为一款创新的AI硬件产品&#xff0c;通过将智能手机转变为具有情感交互能力的桌面机器人&#xff0c;展示了前沿AI技术与传统硬件设计的完美结合。作为AI与玩具领域的专家&#xff0c;我将全面解析LOOI的技术实现架构&#xff0c;特别是其手势识别、物体识别和环境…...

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡

何谓AI编程【02】AI编程官网以优雅草星云智控为例建设实践-完善顶部-建立各项子页-调整排版-优雅草卓伊凡 背景 我们以建设星云智控官网来做AI编程实践&#xff0c;很多人以为AI已经强大到不需要程序员了&#xff0c;其实不是&#xff0c;AI更加需要程序员&#xff0c;普通人…...