【探索Linux】—— 强大的命令行工具 P.28(网络编程套接字 —— 简单的UDP网络程序模拟实现)

在这里插入图片描述

阅读导航

  • 引言
  • 一、UDP协议
  • 二、UDP网络程序模拟实现
    • 1. 预备代码
      • ⭕makefile文件
      • ⭕打印日志文件
      • ⭕打开指定的终端设备文件,并将其作为标准错误输出的目标文件描述符
    • 2. UDP 服务器端实现(UdpServer.hpp)
    • 3. UDP 客户端实现(main函数)
  • 温馨提示

引言

在前一篇文章中,我们详细介绍了UDP协议和TCP协议的特点以及它们之间的异同点。本文将延续上文内容,重点讨论简单的UDP网络程序模拟实现。通过本文的学习,读者将能够深入了解UDP协议的实际应用,并掌握如何编写简单的UDP网络程序。让我们一起深入探讨UDP网络程序的实现细节,为网络编程的学习之旅添上一份精彩的实践经验。

一、UDP协议

UDP(User Datagram Protocol)是一种无连接的、轻量级的网络传输协议,它提供了快速、简单的数据传输服务。下面是一个简单的UDP程序实现示例,包括一个UDP服务器和一个UDP客户端。详介绍可以看上一篇文章:UDP协议介绍 | TCP协议介绍 | UDP 和 TCP 的异同

二、UDP网络程序模拟实现

1. 预备代码

⭕makefile文件

.PHONY:all
all:udpserver udpclientudpserver:Main.ccg++ -o $@ $^ -std=c++11
udpclient:UdpClient.ccg++ -o $@ $^ -lpthread -std=c++11.PHONY:clean
clean:rm -f udpserver udpclient

这段代码是一个简单的 Makefile 文件,用于编译 UDP 服务器(udpserver)和 UDP 客户端(udpclient)的程序。在这个 Makefile 中定义了两个规则:

  1. all:表示默认的目标,依赖于 udpserver 和 udpclient 目标,即执行 make 命令时会编译 udpserver 和 udpclient。
  2. clean:用于清理生成的可执行文件 udpserver 和 udpclient。

在 Makefile 中使用了一些特殊的关键字和变量:

  • .PHONY:声明 all 和 clean 是伪目标,不是真正的文件名。
  • $@:表示目标文件名。
  • $^:表示所有依赖文件列表。
  • -std=c++11:指定 C++ 的编译标准为 C++11。
  • -lpthread:链接 pthread 库,用于多线程支持。

⭕打印日志文件

#pragma once#include <iostream>
#include <time.h>
#include <stdarg.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>#define SIZE 1024#define Info 0
#define Debug 1
#define Warning 2
#define Error 3
#define Fatal 4#define Screen 1
#define Onefile 2
#define Classfile 3#define LogFile "log.txt"class Log
{
public:Log(){printMethod = Screen; // 默认输出方式为屏幕打印path = "./log/"; // 默认日志文件存放路径}void Enable(int method){printMethod = method; // 设置日志输出方式(屏幕、单个文件、分类文件)}std::string levelToString(int level){switch (level){case Info:return "Info";case Debug:return "Debug";case Warning:return "Warning";case Error:return "Error";case Fatal:return "Fatal";default:return "None";}}void printLog(int level, const std::string &logtxt){switch (printMethod){case Screen:std::cout << logtxt << std::endl; // 屏幕打印日志信息break;case Onefile:printOneFile(LogFile, logtxt); // 将日志信息追加写入单个文件break;case Classfile:printClassFile(level, logtxt); // 将日志信息追加写入分类文件break;default:break;}}void printOneFile(const std::string &logname, const std::string &logtxt){std::string _logname = path + logname; // 构建日志文件的完整路径int fd = open(_logname.c_str(), O_WRONLY | O_CREAT | O_APPEND, 0666); // 打开文件,如果文件不存在则创建if (fd < 0)return;write(fd, logtxt.c_str(), logtxt.size()); // 将日志信息写入文件close(fd);}void printClassFile(int level, const std::string &logtxt){std::string filename = LogFile;filename += ".";filename += levelToString(level); // 构建分类文件名,例如"log.txt.Debug/Warning/Fatal"printOneFile(filename, logtxt); // 将日志信息追加写入分类文件}~Log(){}void operator()(int level, const char *format, ...){time_t t = time(nullptr);struct tm *ctime = localtime(&t);char leftbuffer[SIZE];snprintf(leftbuffer, sizeof(leftbuffer), "[%s][%d-%d-%d %d:%d:%d]", levelToString(level).c_str(),ctime->tm_year + 1900, ctime->tm_mon + 1, ctime->tm_mday,ctime->tm_hour, ctime->tm_min, ctime->tm_sec);va_list s;va_start(s, format);char rightbuffer[SIZE];vsnprintf(rightbuffer, sizeof(rightbuffer), format, s);va_end(s);// 格式:默认部分+自定义部分char logtxt[SIZE * 2];snprintf(logtxt, sizeof(logtxt), "%s %s", leftbuffer, rightbuffer);printLog(level, logtxt); // 打印日志信息}private:int printMethod; // 日志输出方式std::string path; // 日志文件存放路径
};

该代码实现了一个简单的日志记录类(Log),其中包括设置日志输出方式(屏幕、单个文件、分类文件)和打印日志信息的功能。

  • Log 类是一个用于记录日志的类。
  • Enable 函数用于设置日志输出方式,可以选择屏幕打印、单个文件或分类文件。
  • printLog 函数根据设置的日志输出方式,将日志信息打印到屏幕、追加写入单个文件或分类文件。
  • printOneFile 函数用于将日志信息追加写入单个文件。
  • printClassFile 函数用于将日志信息追加写入分类文件。
  • levelToString 函数将日志级别转换为对应的字符串表示。
  • operator() 函数是重载的函数调用运算符,用于打印日志信息。
  • path 是日志文件存放路径,默认为"./log/"。
  • printMethod 是日志输出方式,默认为屏幕打印。
  • SIZE 定义了缓冲区大小。
  • InfoDebugWarningErrorFatal 是日志级别的定义。
  • ScreenOnefileClassfile 是日志输出方式的定义。
  • LogFile 是单个文件名的定义。

⭕打开指定的终端设备文件,并将其作为标准错误输出的目标文件描述符

#include <iostream>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>// 定义要打开的终端设备文件路径
std::string terminal = "/dev/pts/6";// 打开指定的终端设备文件,并将其作为标准错误输出的目标文件描述符
int OpenTerminal()
{// 使用open函数以只写方式打开终端设备文件int fd = open(terminal.c_str(), O_WRONLY);if(fd < 0){// 如果打开终端设备文件失败,则输出错误信息到标准错误输出std::cerr << "open terminal error" << std::endl;return 1; // 返回错误代码}// 将终端设备文件的文件描述符复制给标准错误输出的文件描述符// 这样标准错误输出就会重定向到指定的终端设备上dup2(fd, 2);// 如果需要在此处输出信息到标准错误输出,可以使用printf等函数// 关闭文件描述符// close(fd);return 0; // 返回成功代码
}

这段代码的作用是打开一个终端设备文件 “/dev/pts/6”,将其作为标准错误输出(stderr)的目标文件描述符,实现将错误信息输出到指定的终端设备上。

  • terminal 变量存储了要打开的终端设备文件路径 “/dev/pts/6”。
  • OpenTerminal 函数尝试打开指定的终端设备文件,并将其作为标准错误输出的目标文件描述符。
    • 首先使用 open 函数打开终端设备文件,以只写方式(O_WRONLY)。
    • 如果成功打开终端设备文件,则将其文件描述符复制给标准错误输出的文件描述符(2),即 dup2(fd, 2),这样标准错误输出就会重定向到该终端设备上。
    • 如果打开终端设备文件失败,则输出错误信息到标准错误输出,并返回错误代码 1。
    • 最后函数返回0表示成功。

2. UDP 服务器端实现(UdpServer.hpp)

#pragma once#include <iostream>
#include <string>
#include <strings.h>
#include <cstring>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <functional>
#include <unordered_map>
#include "Log.hpp"// 使用Log类记录日志信息
Log lg;enum {SOCKET_ERR = 1,BIND_ERR
};uint16_t defaultport = 8080;
std::string defaultip = "0.0.0.0";
const int size = 1024;class UdpServer {
public:UdpServer(const uint16_t& port = defaultport, const std::string& ip = defaultip): sockfd_(0), port_(port), ip_(ip), isrunning_(false){}void Init() {// 1. 创建UDP socketsockfd_ = socket(AF_INET, SOCK_DGRAM, 0); // PF_INETif (sockfd_ < 0) {lg(Fatal, "socket create error, sockfd: %d", sockfd_);exit(SOCKET_ERR);}lg(Info, "socket create success, sockfd: %d", sockfd_);// 2. 绑定socketstruct sockaddr_in local;bzero(&local, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(port_); // 端口号需要转换为网络字节序local.sin_addr.s_addr = inet_addr(ip_.c_str()); // 将IP地址转换为网络字节序if (bind(sockfd_, (const struct sockaddr*)&local, sizeof(local)) < 0) {lg(Fatal, "bind error, errno: %d, err string: %s", errno, strerror(errno));exit(BIND_ERR);}lg(Info, "bind success, errno: %d, err string: %s", errno, strerror(errno));}void CheckUser(const struct sockaddr_in& client, const std::string clientip, uint16_t clientport) {// 检查用户是否已经存在在线用户列表中auto iter = online_user_.find(clientip);if (iter == online_user_.end()) {online_user_.insert({clientip, client});std::cout << "[" << clientip << ":" << clientport << "] add to online user." << std::endl;}}void Broadcast(const std::string& info, const std::string clientip, uint16_t clientport) {// 广播消息给所有在线用户for (const auto& user : online_user_) {std::string message = "[";message += clientip;message += ":";message += std::to_string(clientport);message += "]# ";message += info;socklen_t len = sizeof(user.second);sendto(sockfd_, message.c_str(), message.size(), 0, (struct sockaddr*)(&user.second), len);}}void Run() {isrunning_ = true;char inbuffer[size];while (isrunning_) {struct sockaddr_in client;socklen_t len = sizeof(client);// 接收客户端发送的消息ssize_t n = recvfrom(sockfd_, inbuffer, sizeof(inbuffer) - 1, 0, (struct sockaddr*)&client, &len);if (n < 0) {lg(Warning, "recvfrom error, errno: %d, err string: %s", errno, strerror(errno));continue;}// 获取客户端的IP地址和端口号uint16_t clientport = ntohs(client.sin_port);std::string clientip = inet_ntoa(client.sin_addr);// 检查用户是否已经存在在线用户列表中CheckUser(client, clientip, clientport);std::string info = inbuffer;// 将接收到的消息广播给所有在线用户Broadcast(info, clientip, clientport);}}~UdpServer() {if (sockfd_ > 0)close(sockfd_);}private:int sockfd_; // 网络文件描述符std::string ip_; // 服务器IP地址uint16_t port_; // 服务器端口号bool isrunning_; // 服务器运行状态std::unordered_map<std::string, struct sockaddr_in> online_user_; // 在线用户列表
};
  • Log.hpp 是用于记录日志信息的头文件。
  • lg 是一个 Log 类的对象,用于输出日志信息。
  • enum 定义了两个错误类型:SOCKET_ERRBIND_ERR,分别表示 socket 创建错误和绑定错误。
  • defaultportdefaultip 分别设置默认的端口号和 IP 地址。
  • size 定义接收缓冲区的大小为 1024 字节。
  • UdpServer 类封装了一个 UDP 服务器。
  • 构造函数 UdpServer 接受端口号和 IP 地址作为参数,并初始化成员变量。
  • Init 函数用于初始化 UDP 服务器,其中:
    • 创建 UDP socket,并检查创建是否成功。
    • 绑定 socket 到指定的 IP 地址和端口号,并检查绑定是否成功。
  • CheckUser 函数用于检查用户是否已经存在在线用户列表中,如果不存在则将其添加到列表中。
  • Broadcast 函数用于向所有在线用户广播消息,其中:
    • 消息格式为 [发送者IP:发送者端口号]# 消息内容
    • 使用 sendto 函数发送消息给每个在线用户。
  • Run 函数是 UDP 服务器的主循环,其中:
    • 循环接收客户端发送的消息,并将其广播给所有在线用户。
    • 对每个客户端,获取其 IP 地址和端口号,并进行用户检查和消息广播。
  • ~UdpServer 析构函数关闭网络文件描述符。
  • sockfd_ 是网络文件描述符,用于创建和管理网络连接。
  • ip_ 是服务器的 IP 地址。
  • port_ 是服务器的端口号。
  • isrunning_ 表示服务器的运行状态,用于控制循环退出。
  • online_user_ 是一个无序映射,用于保存在线用户的 IP 地址和对应的 sockaddr_in 结构体。

3. UDP 客户端实现(main函数)

#include <iostream>
#include <cstdlib>
#include <unistd.h>
#include <strings.h>
#include <string.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
#include "Terminal.hpp"using namespace std;// 函数声明:打印程序的使用方法
void Usage(std::string proc);// 结构体:用于传递线程参数
struct ThreadData
{struct sockaddr_in server; // 服务器地址结构体int sockfd; // socket 文件描述符std::string serverip; // 服务器 IP 地址
};// 线程函数:接收消息
void *recv_message(void *args);// 线程函数:发送消息
void *send_message(void *args);// 主函数
int main(int argc, char *argv[])
{if (argc != 3){Usage(argv[0]); // 打印使用方法exit(0);}// 解析命令行参数std::string serverip = argv[1]; // 服务器 IP 地址uint16_t serverport = std::stoi(argv[2]); // 服务器端口号// 初始化 ThreadData 结构体struct ThreadData td;bzero(&td.server, sizeof(td.server)); // 清零服务器地址结构体td.server.sin_family = AF_INET; // 设置地址族为 IPv4td.server.sin_port = htons(serverport); // 设置端口号(转换为网络字节序)td.server.sin_addr.s_addr = inet_addr(serverip.c_str()); // 设置服务器 IP 地址(转换为网络字节序)// 创建 UDP sockettd.sockfd = socket(AF_INET, SOCK_DGRAM, 0);if (td.sockfd < 0){cout << "socket error" << endl;return 1;}td.serverip = serverip; // 存储服务器 IP 地址pthread_t recvr, sender; // 定义接收消息和发送消息的线程pthread_create(&recvr, nullptr, recv_message, &td); // 创建接收消息线程pthread_create(&sender, nullptr, send_message, &td); // 创建发送消息线程// 等待接收消息和发送消息的线程退出pthread_join(recvr, nullptr);pthread_join(sender, nullptr);close(td.sockfd); // 关闭 socketreturn 0;
}// 函数实现:打印程序的使用方法
void Usage(std::string proc)
{std::cout << "\n\rUsage: " << proc << " serverip serverport\n" << std::endl;
}// 线程函数实现:接收消息
void *recv_message(void *args)
{ThreadData *td = static_cast<ThreadData *>(args); // 强制类型转换为 ThreadData 结构体指针char buffer[1024]; // 接收消息的缓冲区while (true){memset(buffer, 0, sizeof(buffer)); // 清空缓冲区struct sockaddr_in temp;socklen_t len = sizeof(temp);ssize_t s = recvfrom(td->sockfd, buffer, 1023, 0, (struct sockaddr *)&temp, &len); // 接收消息if (s > 0){buffer[s] = 0;cerr << buffer << endl; // 输出接收到的消息}}
}// 线程函数实现:发送消息
void *send_message(void *args)
{ThreadData *td = static_cast<ThreadData *>(args); // 强制类型转换为 ThreadData 结构体指针string message; // 存储用户输入的消息socklen_t len = sizeof(td->server); // 服务器地址的长度// 发送欢迎消息std::string welcome = td->serverip + " comming...";sendto(td->sockfd, welcome.c_str(), welcome.size(), 0, (struct sockaddr *)&(td->server), len);while (true){cout << "Please Enter@ ";getline(cin, message); // 获取用户输入的消息sendto(td->sockfd, message.c_str(), message.size(), 0, (struct sockaddr *)&(td->server), len); // 发送消息给服务器}
}

温馨提示

感谢您对博主文章的关注与支持!如果您喜欢这篇文章,可以点赞、评论和分享给您的同学,这将对我提供巨大的鼓励和支持。另外,我计划在未来的更新中持续探讨与本文相关的内容。我会为您带来更多关于Linux以及C++编程技术问题的深入解析、应用案例和趣味玩法等。如果感兴趣的话可以关注博主的更新,不要错过任何精彩内容!

再次感谢您的支持和关注。我们期待与您建立更紧密的互动,共同探索Linux、C++、算法和编程的奥秘。祝您生活愉快,排便顺畅!
在这里插入图片描述

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

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

相关文章

day04vue学习

day04 一、学习目标 1.组件的三大组成部分&#xff08;结构/样式/逻辑&#xff09; ​ scoped解决样式冲突/data是一个函数 2.组件通信 组件通信语法父传子子传父非父子通信&#xff08;扩展&#xff09; 3.综合案例&#xff1a;小黑记事本&#xff08;组件版&#xff09…

DeprecationWarning: isDaemon() is deprecated, get the daemon attribute instead

报错处理 # t.setDaemon(True) # 阙辉注释 t.daemonTrue # 阙辉新增

已有TensorFlow安装包新建相应python版本的虚拟环境

已有TensorFlow安装包新建虚拟环境 新建conda虚拟环境 新建的虚拟环境默认在Anaconda安装目录D:\Anaconda3\envs&#xff08;根据自己的安装目录看&#xff09; 切换到新建的虚拟环境 在这里可以直接安装下载好的TensorFlow安装包 检查是否安装好 输入python进入python环境…

YOLOv7 | 添加GSConv,VoVGSCSP等多种卷积,有效提升目标检测效果,代码改进(超详细)

⭐欢迎大家订阅我的专栏一起学习⭐ &#x1f680;&#x1f680;&#x1f680;订阅专栏&#xff0c;更新及时查看不迷路&#x1f680;&#x1f680;&#x1f680; YOLOv5涨点专栏&#xff1a;http://t.csdnimg.cn/QdCj6 YOLOv7专栏&#xff1a; http://t.csdnimg.cn/dy…

爬虫逆向sm3和sm4 加密 案例

注意&#xff01;&#xff01;&#xff01;&#xff01;某XX网站逆向实例仅作为学习案例&#xff0c;禁止其他个人以及团体做谋利用途&#xff01;&#xff01;&#xff01; 案例--aHR0cDovLzExMS41Ni4xNDIuMTM6MTgwODgvc3Vic2lkeU9wZW4 第一步&#xff1a;分析页面和请求方式 …

spark基本原理UI界面解读

这里是引用 1 八股文 1.1 基本原理 driver节点是整个应用程序的指挥所 指挥官是sparkcontext 环境&#xff1a;构建一个集群 应用程序提交 确定主节点&#xff0c;确定指挥所driver&#xff0c;确定指挥官sparkcontext sparkcontext会向资源管理器申请资源 会将作业分…

基于Java中的SSM框架实现快餐店线上点餐系统项目【项目源码+论文说明】

基于Java中的SSM框架实现快餐店线上点餐系统演示 摘要 随着计算机互联网的高速发展。餐饮业的发展也加入了电子商务团队。各种网上点餐系统纷纷涌现&#xff0c;不仅增加了商户的销售量和营业额&#xff0c;而且为买家提供了极大的方便&#xff0c;足不出户&#xff0c;就能订…

软件测评中心:进行科技成果鉴定测试的注意事项和好处简析

软件产品科技成果鉴定是有效评价科技成果质量和水平的方法之一&#xff0c;也是鼓励科技成果通过市场竞争等方式得到有效的评价和认可&#xff0c;可以推动科技成果的进步和转化。 一、进行科技成果鉴定测试时的注意事项&#xff1a;   1、应由具备一定资质和能力的专业机构…

Android Studio实现内容丰富的安卓外卖平台

获取源码请点击文章末尾QQ名片联系&#xff0c;源码不免费&#xff0c;尊重创作&#xff0c;尊重劳动 项目编号122 1.开发环境android stuido jdk1.8 eclipse mysql tomcat 2.功能介绍 安卓端&#xff1a; 1.注册登录 2.查看公告 3.查看外卖分类 4.购物车&#xff0c; 5.个人中…

深度学习_微调_7

目标 微调的原理利用微调模型来完成图像的分类任务 微调的原理 微调&#xff08;Fine-tuning&#xff09;是一种在深度学习中广泛应用的技术&#xff0c;特别是在预训练模型&#xff08;Pretrained-Models&#xff09;的基础上进行定制化训练的过程。微调的基本原理和步骤如下…

【项目】YOLOv5+PaddleOCR实现艺术字验证码识别

YOLOv5PaddleOCR实现艺术字类验证码识别 一、引言1.1 实现目标1.2 人手动点选验证码逻辑1.3 计算机点选逻辑 二、计算机验证方法2.1 PaddleOCR下方文字识别方法2.2 YOLOv5目标检测方法2.3 艺术字分类方法2.4 返回结果 三、代码获取 一、引言 1.1 实现目标 要识别的验证码类型…

c语言综合练习题

1.编写程序实现键盘输入一个学生的学分绩点 score&#xff08;合法的范围为:1.0—5.0&#xff09;&#xff0c;根据学生的学分绩点判定该学 生的奖学金的等级&#xff0c;判定规则如下表所示。 #include <stdio.h>int main() {float score;printf("请输入学生的学分…

Harbor-私有镜像仓库

目录 一、Harbor 原理说明 1.软件资源介绍 2.Harbor 特性 3.Harbor 认证过程 4.Harbor 认证流程 二、私有镜像仓库实验 1.环境准备 2.安装docker 3.配置镜像加速和私有仓库地址 4.搭建harbor仓库 5.本地windows浏览器访问配置 一、Harbor 原理说明 1.软件资源介绍 …

面试算法-62-盛最多水的容器

题目 给定一个长度为 n 的整数数组 height 。有 n 条垂线&#xff0c;第 i 条线的两个端点是 (i, 0) 和 (i, height[i]) 。 找出其中的两条线&#xff0c;使得它们与 x 轴共同构成的容器可以容纳最多的水。 返回容器可以储存的最大水量。 说明&#xff1a;你不能倾斜容器。…

CycleGAN训练及测试过程细节记录

CycleGAN训练及测试过程细节记录 文章目录 关于训练关于测试 关于训练 1、训练前将数据配置好&#xff0c;并在Pycharm中写好配置信息 2、关于训练过程的参数配置在 options/train_options.py options/base_options.py batch_size&#xff1a;批大小 crop_size&#xff1a;…

Android分区存储到底该怎么做

文章目录 一、Android存储结构二、什么是分区存储&#xff1f;三、私有目录和公有目录三、存储权限和分区存储有什么关系&#xff1f;四、我们应该该怎么做适配&#xff1f;4.1、利用File进行操作4.2、使用MediaStore操作数据库 一、Android存储结构 Android存储分为内部存储和…

支付宝手机网站支付,微信扫描二维码支付

支付宝手机网站支付 支付宝文档 响应示例 <form name"punchout_form" method"post" action"https://openapi.alipay.com/gateway.do?charsetUTF-8&methodalipay.trade.wap.pay&formatjson&signERITJKEIJKJHKKKKKKKHJEREEEEEEEEEEE…

MySQL 数据库设计范式

第一范式&#xff08;1NF&#xff09; 每一列都是不可分割的原子数据项第二范式&#xff08;2NF&#xff09; 在1NF的基础上&#xff0c;非码属性必须完全依赖于候选码(在1NF基础上消除非主属性对主码的部分函数依赖) 1.函数依赖A->B&#xff0c;如果通过A属性(属性组)的值…

Transformer学习【从零理解】

Transformer 一、整体框架 二、Encoder 1.输入部分: &#xff08;1&#xff09;Embedding&#xff1a;将输入的词转换为对应的词向量。 &#xff08;2&#xff09;位置编码&#xff1a;因为保证输出时&#xff0c;顺序不会打乱&#xff0c;所以要加入时序信息即位置编码。 公…

如何避免AI网红经济泡沫?警惕细分行业的AI转型而不是转行

一、AI泡沫预防针 要避免AI相关新概念催生的网红经济泡沫&#xff0c;可以从多个角度采取措施&#xff1a; 1. **理性投资**&#xff1a; - 投资者应对AI项目和网红经济中的企业进行深入研究&#xff0c;了解其真实的技术实力、商业模式的可行性和盈利能力&#xff0c;而非…