初识socket编程(实现一个简单的TCPServer)

监听套接字的创建流程

在网络编程中,listen 套接字(通常称为“监听套接字”)是服务器端用于接收客户端连接请求的特殊套接字,是 TCP 服务器建立连接过程中的核心组件。下面我们就来简单看一下监听套接字创建的过程

  • 创建流程
    服务器通过 socket() 函数创建一个基础套接字(类型为 SOCK_STREAM,表示 TCP 协议),然后通过 bind() 函数将其绑定到特定的 IP 地址和端口,最后调用 listen() 函数将其转换为 监听套接字
    示例代码框架:

    int listen_fd = socket(AF_INET, SOCK_STREAM, 0);  // 创建基础套接字
    bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr));  // 绑定地址端口
    listen(listen_fd, 5);  // 转换为监听套接字,允许5个未完成连接的排队
    
  • 核心作用
    监听套接字专门用于 接收客户端的 connect() 连接请求,但它本身 不参与实际的数据传输。当客户端发起连接时,服务器通过accept从监听套接字的队列中取出一个链接请求,并创建新的“连接套接字”处理后续的数据读写。

相关系统调用简单回顾

socket() 函数——创建套接字

函数原型

int socket(int domain, int type, int protocol);

作用:创建一个套接字描述符(类似文件描述符),用于后续的网络通信,返回值为套接字描述符(fd),失败返回 -1

参数解析:
  • domain(协议族/地址族)
    指定套接字使用的网络协议族,决定了数据传输的地址格式(如 IPv4、IPv6 等)。常见取值:

    • AF_INET:IPv4 协议族(最常用),对应的地址结构为 struct sockaddr_in
    • AF_INET6:IPv6 协议族,对应的地址结构为 struct sockaddr_in6
    • AF_UNIX/AF_LOCAL:本地进程间通信(Unix 域套接字),用于同一台主机上的进程通信。

    示例:使用 IPv4 协议族

    int sockfd = socket(AF_INET, ..., ...);
    
  • type(套接字类型)
    指定数据传输的方式(面向连接/无连接),常见取值:

    • SOCK_STREAM:流式套接字(TCP 协议),特点是 可靠、有序、面向连接,适用于需要保证数据完整性的场景(如 HTTP、FTP)。
    • SOCK_DGRAM:数据报套接字(UDP 协议),特点是 无连接、不可靠、效率高,适用于实时性要求高的场景(如视频通话、DNS)。
    • SOCK_RAW:原始套接字,允许直接操作底层协议(如 IP 协议),通常用于开发网络工具(如 ping、traceroute),需要 root 权限。

    示例:创建 TCP 套接字

    int tcp_sock = socket(AF_INET, SOCK_STREAM, 0);
    
  • protocol(协议)
    指定具体使用的协议,通常设为 0(让系统根据前两个参数自动选择默认协议):

    • domain=AF_INETtype=SOCK_STREAM 时,默认协议是 IPPROTO_TCP(TCP 协议)。
    • domain=AF_INETtype=SOCK_DGRAM 时,默认协议是 IPPROTO_UDP(UDP 协议)。

    示例:显式指定 TCP 协议(等价于设为 0)

    int tcp_sock = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP);
    

bind() 函数——为一个套接字绑定地址和端口

函数原型

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

作用:将套接字(sockfd)与特定的 IP 地址端口号 绑定,使套接字“锚定”到一个具体的网络端点,失败返回 -1

参数解析:
  • sockfd
    socket() 函数返回的套接字描述符,即要绑定的目标套接字。

  • addr(地址结构)
    指向一个通用地址结构 struct sockaddr 的指针,实际使用时需根据 socket()domain 参数转换为对应类型的地址结构:

    • IPv4 场景:使用 struct sockaddr_in(需强制转换为 struct sockaddr*),结构定义:
      struct sockaddr_in {sa_family_t    sin_family;  // 协议族,必须为 AF_INETin_port_t      sin_port;    // 端口号(网络字节序)struct in_addr sin_addr;    // IPv4 地址(网络字节序)unsigned char  sin_zero[8]; // 填充字段,通常设为 0
      };struct in_addr {uint32_t s_addr;  // IPv4 地址(32位无符号整数,网络字节序)
      };
      
    • 关键设置
      • sin_family 必须与 socket()domain 一致(如 AF_INET)。
      • sin_port 是端口号(范围 1-65535,0 为临时端口),需用 htons() 转换为 网络字节序(大端序)。
      • sin_addr.s_addr 是 IPv4 地址,常用值:
        • INADDR_ANY:绑定到本机所有网络接口的 IP 地址(服务器常用,无需指定具体 IP)。
        • 具体 IP 地址:如 inet_addr("192.168.1.100")(需转换为网络字节序)。
  • addrlen
    指定 addr 指向的地址结构的大小(字节数),通常通过 sizeof() 获取:

    socklen_t len = sizeof(struct sockaddr_in);
    
示例代码(服务器绑定 IPv4 地址)
// 创建 IPv4 + TCP 套接字
int listen_fd = socket(AF_INET, SOCK_STREAM, 0);
if (listen_fd == -1) {perror("socket failed");exit(1);
}// 配置 IPv4 地址结构
struct sockaddr_in server_addr;
memset(&server_addr, 0, sizeof(server_addr)); // 初始化清零
server_addr.sin_family = AF_INET;             // 协议族:IPv4
server_addr.sin_port = htons(8080);           // 端口号:8080(转换为网络字节序)
server_addr.sin_addr.s_addr = INADDR_ANY;     // 绑定到本机所有 IP// 绑定套接字
if (bind(listen_fd, (struct sockaddr*)&server_addr, sizeof(server_addr)) == -1) {perror("bind failed");close(listen_fd);exit(1);
}
关键注意事项
  1. 网络字节序:端口号和 IP 地址必须用 htons()/htonl() 转换为网络字节序(大端序),避免因主机字节序差异导致的错误。
  2. 端口占用:若 bind() 失败且 errno=EADDRINUSE,表示端口已被占用,需更换端口或等待占用释放。
  3. 客户端是否需要 bind():客户端通常不需要显式 bind(),系统会自动分配临时端口;服务器必须 bind() 到固定端口,否则客户端无法找到服务器。

通过 socket()bind() 的参数配置,可灵活定义套接字的通信协议、类型和绑定的网络端点,是网络编程的基础步骤。

listen()函数——将一个普通的套接字转化为监听套接字

在基于TCP协议的网络编程中,listen函数是服务器端编程的关键函数之一,用于将一个普通的套接字转化为监听套接字,从而监听客户端的连接请求。以下是对listen函数功能的详细介绍:

1. 函数原型
int listen(int sockfd, int backlog);
2. 参数说明
  • sockfd:这是由socket函数创建并通过bind函数绑定了本地IP地址和端口号的套接字描述符。它是一个整数,标识了要进行监听操作的套接字。只有在bind操作成功后,sockfd才能作为listen函数的参数,用于将其转换为监听套接字。
  • backlog:该参数指定了未完成连接队列(incomplete connection queue)和已完成连接队列(established connection queue)的总长度上限 。
    • 未完成连接队列:在TCP三次握手过程中,当客户端发送SYN报文发起连接请求后,服务器接收到该请求,会将这个连接请求暂时放入未完成连接队列中,直到完成三次握手,建立起完整的连接。
    • 已完成连接队列:完成三次握手的连接会被移动到已完成连接队列中,等待服务器调用accept函数来取出并处理。
    • 若队列已满,新的连接请求可能被服务器忽略(不同操作系统处理方式略有差异,一般客户端会收到连接超时或连接被拒绝的响应 )。在现代Linux系统中,backlog的实际含义是两个队列长度总和的上限,但具体行为还受到内核参数(如somaxconn)的影响。
3. 函数功能实现
  • 将套接字转换为监听套接字:调用listen函数后,指定的套接字(sockfd)就从一个普通套接字转变为监听套接字,处于监听状态,专门用于接收客户端的连接请求。监听套接字本身并不参与实际的数据传输,它只负责监听并处理连接请求。
  • 管理连接队列:通过backlog参数,listen函数控制着服务器可以同时处理的未完成连接和已完成连接的数量。这有助于服务器合理管理资源,避免因连接请求过多而导致系统资源耗尽。
4. 返回值
  • 成功调用listen函数时,会返回0 ,表示监听操作设置成功,套接字已成功转换为监听状态。
  • 失败时,会返回-1,并设置errno来指示错误原因。常见的错误原因包括:
    • EBADFsockfd不是一个有效的文件描述符。
    • EINVALsockfd没有绑定到一个地址,或者backlog参数的值无效(比如小于0)。
    • EMFILE:当前进程已经打开了太多的文件描述符,达到了系统限制,导致无法将套接字设置为监听状态。
5. 示例代码:创建一个监听套接字
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <unistd.h>#define PORT 8080
#define BACKLOG 5int main() {int sockfd, new_socket;struct sockaddr_in server_addr, client_addr;socklen_t client_addr_len = sizeof(client_addr);// 创建套接字sockfd = socket(AF_INET, SOCK_STREAM, 0);if (sockfd == -1) {perror("Socket creation failed");exit(EXIT_FAILURE);}// 配置服务器地址server_addr.sin_family = AF_INET;server_addr.sin_addr.s_addr = INADDR_ANY;server_addr.sin_port = htons(PORT);// 绑定套接字if (bind(sockfd, (struct sockaddr *)&server_addr, sizeof(server_addr)) < 0) {perror("Bind failed");exit(EXIT_FAILURE);}// 将套接字设置为监听状态if (listen(sockfd, BACKLOG) < 0) {perror("Listen failed");exit(EXIT_FAILURE);}...close(socket);return 0;
}

在上述代码中,通过listen函数将socket创建并bind绑定后的套接字设置为监听状态,随后通过accept函数来接受客户端的连接请求。

总的来说,listen函数是TCP服务器端实现并发连接处理的基础,它使得服务器能够高效地管理客户端的连接请求,为后续的数据交互奠定基础。

的创建

accept()函数——从监听套接字的链接队列中获取请求

在基于TCP协议的网络编程中,accept函数用于从监听套接字的已完成连接队列中取出一个客户端连接请求,并创建一个新的套接字,用于与该客户端进行通信。以下是对accept函数参数的详细解析:

1. 函数原型
int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
2. 参数说明
  • sockfd
    • 含义:监听套接字的文件描述符
    • 作用accept函数会检查 sockfd对应的监听套接字的已完成连接队列,从中取出一个已完成三次握手的客户端连接请求。如果已完成连接队列为空,accept函数默认会阻塞,直到有新的连接请求到来(也可以通过设置套接字为非阻塞模式改变这一行为)。
  • addr
    • 含义:输出型参数,accept从监听套接字的链接队列中取出一个请求之后,就把请求所属客户端的地址信息存储在一个 struct sockaddr结构体中,然后将这个结构体的指针作为输出型参数传递给进程。
    • 示例(以IPv4为例)
struct sockaddr_in client_addr;
// 作为参数传入accept函数前,不需要初始化client_addr结构体
accept(listen_fd, (struct sockaddr *)&client_addr, &client_addr_len); 
- **填充内容**:当`accept`函数成功返回时,该结构体中会被填入客户端的相关地址信息,包括客户端的IP地址和端口号。对于`struct sockaddr_in`结构体,`sin_addr`成员存储客户端的IPv4地址(网络字节序),`sin_port`成员存储客户端的端口号(网络字节序)。
  • addrlen
    • 含义:输入输出型参数,输入时accept函数通过该参数知道要填充的结构体的大小,以避免越界写入;当accept函数成功返回时,accept函数会将addr结构体中的客户端地址信息的长度写入addrlen

示例(以IPv4为例)

struct sockaddr_in client_addr;
socklen_t client_addr_len = sizeof(client_addr);
accept(listen_fd, (struct sockaddr *)&client_addr, &client_addr_len); 
3. 返回值
  • 成功时accept函数返回一个新的套接字描述符,该套接字用于与刚刚接受的客户端进行后续的数据读写操作。这个新套接字与监听套接字不同,监听套接字继续用于监听新的连接请求,而新套接字专门服务于一个特定的客户端连接。
  • 失败时accept函数返回-1,并设置errno来指示错误原因。常见的错误原因包括:
    • EBADFsockfd不是一个有效的文件描述符。
    • EAGAINEWOULDBLOCK:如果套接字被设置为非阻塞模式,且当前已完成连接队列为空,accept函数会返回这个错误。
    • ECONNABORTED:由于某些原因(如客户端异常关闭连接),已完成连接队列中的连接被中止,accept函数会返回这个错误。

基于上面的系统调用,实现一个简单的tcpServer

#include <iostream>
#include <string>
#include <cerrno>
#include <cstring>
#include <cstdlib>
#include <memory>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/wait.h>
#include <unistd.h>const static int default_backlog = 6;enum
{Usage_Err = 1,Socket_Err,Bind_Err,Listen_Err
};#define CONV(addr_ptr) ((struct sockaddr *)addr_ptr)class TcpServer
{
public:TcpServer(uint16_t port) : _port(port), _isrunning(false){}// 创建一个监听套接字void Init(){// 1. 创建socket, file fd, 本质是文件_listensock = socket(AF_INET, SOCK_STREAM, 0);if (_listensock < 0){exit(0);}int opt = 1;// 允许地址和端口复用setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));// 2. 填充本地网络信息并bindstruct sockaddr_in local;memset(&local, 0, sizeof(local));local.sin_family = AF_INET;local.sin_port = htons(_port);local.sin_addr.s_addr = htonl(INADDR_ANY);// 2.1 bindif (bind(_listensock, CONV(&local), sizeof(local)) != 0){exit(Bind_Err);}// 3. 设置socket为监听状态,tcp特有的if (listen(_listensock, default_backlog) != 0){exit(Listen_Err);}}void ProcessConnection(int sockfd, struct sockaddr_in &peer){uint16_t clientport = ntohs(peer.sin_port);std::string clientip = inet_ntoa(peer.sin_addr);std::string prefix = clientip + ":" + std::to_string(clientport);std::cout << "get a new connection, info is : " << prefix << std::endl;while (true){char inbuffer[1024];// 将客户端发来的信息从接收缓冲区中读取到inbuffer中ssize_t s = ::read(sockfd, inbuffer, sizeof(inbuffer)-1);if(s > 0){inbuffer[s] = 0;std::cout << prefix << "# " << inbuffer << std::endl;std::string echo = inbuffer;echo += "[tcp server echo message]";// 将客户端发来的数据再发回去write(sockfd, echo.c_str(), echo.size());}else{std::cout << prefix << " client quit" << std::endl;break;}}}void Start(){_isrunning = true;while (_isrunning){// 4. 获取连接struct sockaddr_in peer;socklen_t len = sizeof(peer);int sockfd = accept(_listensock, CONV(&peer), &len);if (sockfd < 0){continue;}// 走到这说明成功从监听套接字的链接队列中获取到了一个链接,我们下面就调用ProcessConnection来处理这个链接ProcessConnection(sockfd, peer);}}~TcpServer(){}private:uint16_t _port;int _listensock; // TODObool _isrunning;
};using namespace std;void Usage(std::string proc)
{std::cout << "Usage : \n\t" << proc << " local_port\n"<< std::endl;
}
// ./tcp_server 8888
int main(int argc, char *argv[])
{if (argc != 2){Usage(argv[0]);return Usage_Err;}// 获取命令行中的端口号uint16_t port = stoi(argv[1]);std::unique_ptr<TcpServer> tsvr = make_unique<TcpServer>(port);tsvr->Init();tsvr->Start();return 0;
}

注: setsockopt(_listensock, SOL_SOCKET, SO_REUSEADDR | SO_REUSEPORT, &opt, sizeof(opt));这段代码的作用主要是给监听套接字设置允许端口和地址复用,提升TCPServer的稳定性。详细解释如下:
SO_REUSEADDR(重用地址)
核心作用:允许绑定到已处于 TIME_WAIT 状态的端口。
场景:服务器程序重启时,如果之前的连接还处于 TIME_WAIT 状态(TCP 连接关闭后会保留一段时间,确保数据传输完成),默认情况下新的 bind() 会失败(提示 “地址已在使用”)。设置该选项后,即使端口处于 TIME_WAIT,也能成功绑定。
SO_REUSEPORT(重用端口,Linux 3.9+ 支持)
核心作用:允许多个套接字绑定到 同一个端口(但通常要求这些套接字属于不同进程,或有特殊权限)。
场景:常用于多进程 / 多线程服务器,多个进程可以同时监听同一个端口,内核会将新连接负载均衡到这些进程,提高并发处理能力。

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

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

相关文章

开发者如何在 Gitee 上开源一个自己的项目

文章目录一、为什么要在 Gitee 上开源&#xff1f;1. 开源的价值2. 为什么是 Gitee&#xff1f;二、前期准备&#xff1a;让项目“可开源”1. 项目代码整理2. 添加必要文件3. 确定开源许可证三、在 Gitee 上创建仓库四、推送本地代码到 Gitee五、完善项目展示&#xff08;吸引力…

卷积神经网络实现mnist手写数字集识别案例

手写数字识别是计算机视觉领域的“Hello World”&#xff0c;也是深度学习入门的经典案例。它通过训练模型识别0-9的手写数字图像&#xff08;如MNIST数据集&#xff09;&#xff0c;帮助我们快速掌握神经网络的核心流程。本文将以PyTorch框架为基础&#xff0c;带你从数据加载…

实战笔记——构建智能Agent:SpreadJS代码助手

目录 前言 解决思路 需求理解 MCP Server LangGraph 本教程目标 技术栈 第一部分&#xff1a;构建 MCP Server - 工具服务化的基础架构 第二部分&#xff1a;Tools 实现 第三部分&#xff1a;基于 LangGraph 构建智能 Agent 第四部分&#xff1a;服务器和前端搭建 前…

【Word】用 Python 轻松实现 Word 文档对比并生成可视化 HTML 报告

在日常工作和学习中&#xff0c;我们经常需要对两个版本的文档进行比对&#xff0c;比如合同修改、论文修订、报告更新等。手动逐字检查不仅耗时费力&#xff0c;还容易遗漏细节。 今天&#xff0c;我将带你使用 Python python-docx difflib 实现一个自动化 Word 文档对比工具…

从0开始搭建一个前端项目(vue + vite + typescript)

版本 node&#xff1a;v22.17.1 pnpm&#xff1a;v10.13.1 vue&#xff1a;^3.5.18 vite&#xff1a;^7.0.6 typescipt&#xff1a;~5.8.0脚手架初始化vue pnpm create vuelatest只选择&#xff1a; TypeScript, JSX 3. 用vscode打开创建的项目&#xff0c;并删除多余的代码esl…

1.ImGui-环境安装

免责声明&#xff1a;内容仅供学习参考&#xff0c;请合法利用知识&#xff0c;禁止进行违法犯罪活动&#xff01; 本次游戏没法给 内容参考于&#xff1a;微尘网络安全 IMGUI是一个被广泛应用到逆向里面的&#xff0c;它可以用来做外部的绘制&#xff0c;比如登录界面&…

基于springboot的二手车交易系统

博主介绍&#xff1a;java高级开发&#xff0c;从事互联网行业六年&#xff0c;熟悉各种主流语言&#xff0c;精通java、python、php、爬虫、web开发&#xff0c;已经做了六年的毕业设计程序开发&#xff0c;开发过上千套毕业设计程序&#xff0c;没有什么华丽的语言&#xff0…

修改win11任务栏时间字体和小图标颜色

1 打开运行提示框 在桌面按快捷键winR&#xff0c;然后如下图所示输入regedit2 查找路径 1、在路径处粘贴路径计算机\HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Themes\Personalize 2、如下图所示&#xff0c;双击打开ColorPrevalence&#xff0c;将里面的…

第13集 当您的USB设备不在已实测支持列表,如何让TOS-WLink支持您的USB设备--答案Wireshark USB抓包

问&#xff1a;当您的USB设备不在已实测支持列表&#xff0c;如何让TOS-WLink支持您的USB设备&#xff1f; 答案&#xff1a;使用Wireshark USB抓包&#xff0c;日志发给我 为什么要抓包&#xff1a; USB设备种类繁多&#xff1b;TOS-WLink是单片机&#xff0c;内存紧张&#…

[灵动微电子 MM32BIN560CN MM32SPIN0280]读懂电机MCU之比较器

作为刚接触微控制器的初学者&#xff0c;在看到MM32SPIN0280用户手册中“比较器”相关内容时&#xff0c;是不是会感到困惑&#xff1f;比如“5个通用比较器”“轮询功能”“迟滞电压”这些术语&#xff0c;好像都和电机控制有关&#xff0c;但又不知道具体怎么用。别担心&…

⸢ 贰 ⸥ ⤳ 安全架构:数字银行安全体系规划

&#x1f44d;点「赞」&#x1f4cc;收「藏」&#x1f440;关「注」&#x1f4ac;评「论」 &#x1f525;更多文章戳&#x1f449;Whoami&#xff01;-CSDN博客&#x1f680; 在金融科技深度融合的背景下&#xff0c;信息安全已从单纯的技术攻防扩展至架构、合规、流程与创新的…

布隆过滤器完全指南:从原理到实战

布隆过滤器完全指南:从原理到实战 摘要:本文深入解析布隆过滤器的核心原理、实现细节和实际应用,提供完整的Java实现代码,并探讨性能优化策略。适合想要深入理解概率数据结构的开发者阅读。 前言 在大数据时代,如何快速判断一个元素是否存在于海量数据集合中?传统的Hash…

​嵌入式Linux学习 - 网络服务器实现与客户端的通信

1.单循环服务器 2.并发服务器 1. 设置socket属性 2. 进程 ​3. 线程 3.多路IO复用模型 - 提高并发程度 1. 区别 2. IO处理模型 1. 阻塞IO模型 2. 非阻塞IO模型 3. 信号驱动IO 4. IO多路复用 3. 特点 4. 函数接口 1. select 2. poll 3. epoll 半包 1.单循环服务…

Mybatis中缓存机制的理解以及优缺点

文章目录一、MyBatis 缓存机制详解1. 一级缓存&#xff08;Local Cache&#xff09;2. 二级缓存&#xff08;Global Cache&#xff09;3. 缓存执行顺序二、MyBatis 缓存的优点三、MyBatis 缓存的缺点四、适用场景与最佳实践总结MyBatis 提供了完善的缓存机制&#xff0c;用于减…

Rust 登堂 之 类型转换(三)

Rust 是类型安全的语言&#xff0c;因此在Rust 中做类型转换不是一件简单的事&#xff0c;这一章节&#xff0c;我们将对Rust 中的类型转换进行详尽讲解。 高能预警&#xff0c;本章节有些难&#xff0c;可以考虑学了进阶后回头再看 as 转换 先来看一段代码 fn main() {let a…

【MySQL 为什么默认会给 id 建索引? MySQL 主键索引 = 聚簇索引?】

MySQL 索引 MySQL 为什么默认会给 id 建索引&#xff1f; & MySQL 主键索引 聚簇索引&#xff1f; 结论&#xff1a;在 MySQL (InnoDB) 中&#xff0c;主键索引是自动创建的聚簇索引&#xff0c;不需要删除&#xff0c;其他索引是补充优化。 1. MySQL 的id 索引是怎么来的…

[光学原理与应用-321]:皮秒深紫外激光器产品不同阶段使用的工具软件、对应的输出文件

在皮秒深紫外激光器的开发过程中&#xff0c;不同阶段使用的工具软件及其对应的输出文件如下&#xff1a;一、设计阶段工具软件&#xff1a;Zemax OpticStudio&#xff1a;用于光学系统的初步设计和仿真&#xff0c;包括光线追迹、像差分析、优化设计等。MATLAB&#xff1a;用于…

openEuler常用操作指令

openEuler常用操作指令 一、前言 1.简介 openEuler是由开放原子开源基金会孵化的全场景开源操作系统项目&#xff0c;面向数字基础设施四大核心场景&#xff08;服务器、云计算、边缘计算、嵌入式&#xff09;&#xff0c;全面支持ARM、x86、RISC-V、loongArch、PowerPC、SW…

Python爬虫实战:构建网易云音乐个性化音乐播放列表同步系统

1. 引言 1.1 研究背景 在数字音乐生态中,各大音乐平台凭借独家版权、个性化推荐等优势占据不同市场份额。根据国际唱片业协会(IFPI)2024 年报告,全球流媒体音乐用户已突破 50 亿,其中超过 60% 的用户同时使用 2 个及以上音乐平台。用户在不同平台积累的播放列表包含大量…

vscode 配置 + androidStudio配置

插件代码片段 饿了么 icon{"Print to console": {"prefix": "ii-ep-","body": ["i-ep-"],"description": "elementPlus Icon"} }Ts 初始化模版{"Print to console": {"prefix": &q…