Linux系统编程——网络编程的学习
Linux系统编程学习相关博文
- Linux系统编程——文件编程的学习
- Linux系统编程——进程的学习
- Linux系统编程——进程间通信的学习
- Linux系统编程——线程的学习
Linux系统编程——网络编程的学习
- 一、概述
- 1. TCP/UDP
- 2. 端口号
- 3. 字节序
- 4. Sockt服务器和客户端的开发步骤
- 1. 服务器
- 2. 客户端
- 二、网络编程API
- 三、API介绍
- 1. socket函数
- 2. bind函数
- 3. listen函数
- 4. accept函数
- 5. connect函数
- 6. inet_aton函数
- 7. inet_ntoa函数
- 8. htons函数
- 四、API的使用例子
一、概述
1. TCP/UDP
- TCP面向连接( 如打电话要先拨号建立连接);UDP是无连接的,即发送数据之前不需要建立连接
- TCP提供可靠的服务。也就是说,通过TCP连接传送的数据,无差错,不丢失,不重复,且按序到达;UDP尽最大努力交付,即不保证可靠交付
- TCP面向字节流,实际上是TCP把数据看成一连串无结构的字节流;UDP是面向报文的,UDP没有拥塞控制,因此网络出现拥塞不会使源主机的发送速率降低 (对实时应用很有用,如IP电话,实时视频会议等)
- 每一条TCP连接只能是点到点的;UDP支持一对一,一对多,多对一和多对多的交互通信
- TCP首部开销20字节;UDP的首部开销小,只有8个字节
- TCP的逻辑通信信道是全双工的可靠信道,UDP则是不可靠信道
2. 端口号
一台拥有IP地址的主机可以提供许多服务,比如Web服务、FTP服务、SMTP服务等。这些服务完全可以通过1个IP地址来实现。那么,主机是怎样区分不同的网络服务呢?显然不能只靠IP地址,因为IP 地址与网络服务的关系是一对多的关系。实际上是通过“IP地址+端口号”来区分不同的服务的。端口提供了一种访问通道,服务器一般都是通过知名端口号来识别的。例如,对于每个TCP/IP实现来说,FTP服务器的TCP端口号都是21,每个Telnet服务器的TCP端口号都是23,每个TFTP (简单文件传送协议) 服务器的UDP端口号都是69。
3. 字节序
- 小端字节序 (Little endian):将低序字节存储在起始地址
- 大端字节序 (Big endian):将高序字节存储在起始地址
- 网络字节序 = 大端字节序
4. Sockt服务器和客户端的开发步骤
1. 服务器
- 创建套接字
- 为套接字添加信息 (IP地址和端口号)
- 监听网络连接
- 监听到有客户端接入,接受一个连接
- 数据交互
- 关闭套接字,断开连接
2. 客户端
- 创建套接字
- 连接服务器 (输入服务器IP地址和端口号)
- 数据交互
- 关闭套接字,断开连接
二、网络编程API
在Linux系统中,操作系统提供了一系列的API,详细看下图
创建套接字 socket()
绑定 bind()
监听 listen()
连接(服务器) accept()
连接(客户端) connect()地址转换 inet_aton() / inet_ntoa
字节序转换 htons() / htonl() / ntohs() / ntohl()
三、API介绍
1. socket函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>int socket(int domain, int type, int protocol);1. 函数功能:创建套接字2. 形参说明:
domain:指明所使用的协议族,通常为AF_INET,表示互联网协议族(TCP/IP协议族)- AF_INET IPV4因特网域- AF_INET6 IPv6因特网域 - AF_UNIX Unix域 - AF_ROUTE 路由套接字- AF_KEY 密钥套接字 - AF_UNSPEC 未指定
type:指定 socket 的类型- SOCK STREAM流式套接字提供可靠的、面向连接的通信流,它使用 TCP 协议,从而保证了数据传输的正确性和顺序性- SOCK DGRAM数据报套接字定义了一种无连接的服,数据通过相互独立的报文进行传输,是无序的,并且不保证是可靠、无差错的。它使用数据报协议 UDP- SOCK RAW允许程序使用底层协议,原始套接字允许对底层协议如 IP 或 ICMP 进行直接访问,功能强大但使用较为不便,主要用于一些协议的开发
protocal:通常赋值为0- 0 选择 type 类型对应的默认协议- IPPROTO_TCP TCP 传输协议- IPPROTO_UDP UDP 传输协议- IPPROTO_SCTP SCTP 传输协议- IPPROTO_TIPC TIPC 传输协议
3. 返回值:成功,返回套接字的文件描述符;失败,返回-1
2. bind函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);1. 函数功能:用于绑定IP地址和端口号到sockfd
2. 形参说明:
sockfd:调用socket函数返回的套接字描述符
addr:是一个指向包含有本机 IP 地址及端口号等信息的 sockaddr 类型的指针,指向要绑定给 sockfd 的协议地址结构,这个地址结构根据地址创建 socket 时的地址协议族的不同而不同(该结构体原型在下方)
addrlen:addr的大小struct sockaddr结构体原型:struct sockaddr {sa_family_t sa_family;char sa_data[14];};
可同等替换为:struct sockaddr_in {__kernel_sa_family_t sin_family; /* 协议族 */__be16 sin_port; /* 端口号 */struct in_addr sin_addr; /* IP地址结构体 *//* 填充到结构体sockaddr的大小,没有实际意义,只是为跟sockaddr结构体在内存中对齐,这样两者才能相互转换 */unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -sizeof(unsigned short int) - sizeof(struct in_addr)];};
3. 返回值:成功,返回0;失败,返回-1
3. listen函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>int listen(int sockfd, int backlog);1. 函数功能:监听设置,设置能处理的最大连接数- 设置能处理的最大连接数,listen() 并末开始接受连线,只是设置 sockect 的 listen 模式,listen 函数只用于服务端,服务路进程不知道要与谁连接,因此,它不会主动地要求与某个进程连接,只是一直监听是否有其他客户进程与之连接,然后响应该连接请求,并对它做出处理,一个服务进程可以同时处理多个客户进程的连接。主要就两个功能:将一个未连接的套接字转换为一个被动套接字(监听),规定内核为相应套接字排队的最大连接数。- 内核为任何一个给定监听套接字维护两个队列:- 未完成连接队列,每个这样的 SYN 报文段对应其中一项:已由某个客户发出并到达服务醒,而服务器正在等待完成相应的 TCP 二次握手过程。这些套接字处于 SYN REVD 状态;- 已完成连接队列,每个已完成 TCP 三次握手过程的客户端对应其中一项。这些套接字处于 ESTABLISHED 状态
2. 形参说明:
sockfd:调用socket函数返回的套接字描述符
backlog:请求队列允许的最大请求数
3. 返回值:成功,返回0;失败,返回-1
4. accept函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);1. 函数功能:用于从已完成连接队列队头返回下一个已完成的连接。如果已完成连接队列为空,那么进程被投入睡眠
2. 形参说明:
sockfd:调用socket函数返回的套接字描述符
addr:用来返回已连接的对端(客户端)的协议地址
addrlen:客户端地址长度
3. 返回值:该函数的返回值是一个新的套接字描述符,返回值是表示已连接的套接字描述符,而第一个参数是服务器监听套接字描述符。一个服务器通常仅仅创建一个监听套接字,它在该服务器的生命周期内一直存在,内核为每个由服务器进程接受的客户连接创建一个已连接套接字(表示 TCP 三次挥手已完成),当服务器完成对某个给定客户的服务时,相应的已连接套接字就会被关闭。成功,返回0;失败,返回-1
5. connect函数
#include <sys/types.h> /* See NOTES */
#include <sys/socket.h>int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);1. 函数功能:绑定之后的client端(客户端),与服务器建立连接
2. 形参说明:
sockfd:目的服务器的套接字描述符
addr:服务器端的IP地址和端口号的结构体指针
addrlen:addr大小
3. 返回值:成功,返回0;失败,返回-1
6. inet_aton函数
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>int inet_aton(const char *cp, struct in_addr *inp);char *inet_ntoa(struct in_addr in);1. 函数功能:将字符串形式的 IP 地址转为网络能识别的格式
2. 形参说明:
cp:IP地址
inp:存放IP地址的结构体指针(如下所示)struct sockaddr结构体原型:struct sockaddr {sa_family_t sa_family;char sa_data[14];};
可同等替换为:struct sockaddr_in {__kernel_sa_family_t sin_family; /* 协议族 */__be16 sin_port; /* 端口号 */struct in_addr sin_addr; /* IP地址结构体 *//* 填充到结构体sockaddr的大小,没有实际意义,只是为跟sockaddr结构体在内存中对齐,这样两者才能相互转换 */unsigned char __pad[__SOCK_SIZE__ - sizeof(short int) -sizeof(unsigned short int) - sizeof(struct in_addr)];};
7. inet_ntoa函数
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>char *inet_ntoa(struct in_addr in);1. 函数功能:将网络格式的IP地址转为字符串格式
2. 形参说明:
in:存放IP地址的结构体(结构体原型见上方)
8. htons函数
#include <arpa/inet.h>uint16_t htons(uint16_t hostshort);1. 函数功能:返回网络字节序的值(关于什么是字节序请看概述部分),用来转换端口号
2. 形参说明:
hostshort:转换的端口号。h代表host,n代表net,s代表short(两个字节),l代表long(4个字节),通过上面的4个函数可以实现主机字节序和网络字节序之间的转换。有时可以用INADDR_ANY,INADDR_ANY指定地址让操作系统自己获取
四、API的使用例子
实现功能:服务器可以接收各个客户端的信息,每隔五秒服务器向连接的客户端发送信息(类似心跳包)
1. 服务端
1 #include <stdio.h>2 #include <stdlib.h>3 #include <string.h>4 5 #include <sys/types.h> /* See NOTES */6 #include <sys/socket.h>7 #include <arpa/inet.h>8 #include <netinet/in.h>9 10 //1. int socket(int domain, int type, int protocol);11 //2. int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen);12 //3. uint16_t htons(uint16_t hostshort);13 //4. int inet_aton(const char *cp, struct in_addr *inp);14 //5. int listen(int sockfd, int backlog);15 //6. int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);16 17 int main(int argc, char **argv)18 {19 int mark = 0;20 int c_fd = 0;21 int s_fd = 0;22 int s_bind = 0;23 int c_lent = 0;24 int n_read = 0;25 int s_listen = 0;26 27 char msg[128] = {'\0'};28 char readBuf[128] = {'\0'};29 char sendBuf[128] = {'\0'};30 //char *sendBuf = "Thank you for your message";31 32 struct sockaddr_in s_addr;33 struct sockaddr_in c_addr;34 35 memset(&s_addr, 0, sizeof(struct sockaddr_in));36 memset(&c_addr, 0, sizeof(struct sockaddr_in));37 38 if(argc != 3){39 printf("Please input three params\n");40 exit(-1);41 }42 43 //1. socket44 s_fd = socket(AF_INET, SOCK_STREAM, 0);45 if(s_fd == -1){46 perror("Create socekt failed:");47 exit(-1);48 }49 50 //2. bind51 s_addr.sin_family = AF_INET;52 s_addr.sin_port = htons(atoi(argv[2]));53 inet_aton(argv[1], &s_addr.sin_addr);54 55 s_bind = bind(s_fd, (struct sockaddr *)&s_addr, sizeof(struct sockaddr_in));56 if(s_bind == -1){57 perror("Bind failed:");58 exit(-1);59 }60 61 //3. listen62 s_listen = listen(s_fd, 10);63 if(s_listen == -1){64 perror("Listen failed:");65 exit(-1);66 }67 68 printf("listen...\n");69 70 //4. accept71 while(1){72 c_lent = sizeof(struct sockaddr_in);73 c_fd = accept(s_fd, (struct sockaddr *)&c_addr, &c_lent);74 if(c_fd == -1){75 perror("Accept failed:");76 exit(-1);77 }78 79 printf("Get connect: %s\n", inet_ntoa(c_addr.sin_addr));80 81 mark++;82 83 if(fork() == 0){84 while(1){85 memset(sendBuf, '\0', sizeof(sendBuf));86 87 sprintf(sendBuf, "Welcome No.%d client", mark);88 write(c_fd, sendBuf, sizeof(sendBuf));89 sleep(5);90 91 if(fork() == 0){92 while(1){93 memset(msg, '\0', sizeof(msg));94 memset(readBuf, '\0', sizeof(readBuf));95 96 n_read = read(c_fd, readBuf, sizeof(readBuf));97 if(n_read == -1){98 printf("Read failed:");99 }else{
100 sprintf(msg, "Get No.%d message: %s", mark, readBuf);
101 printf("%s\n", msg);
102 }
103 }
104 }
105 }
106 }
107 }
108
109 return 0;
110 }
2. 客户端
1 #include <stdio.h>2 #include <stdlib.h>3 #include <string.h>4 5 #include <sys/types.h> /* See NOTES */6 #include <sys/socket.h>7 #include <arpa/inet.h>8 #include <netinet/in.h>9 10 //1. int socket(int domain, int type, int protocol);11 //2. int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);12 //3. int inet_aton(const char *cp, struct in_addr *inp);13 //4. char *inet_ntoa(struct in_addr in);14 15 int main(int argc, char **argv)16 {17 int c_fd = 0;18 int n_read = 0;19 20 char readBuf[128] = {'\0'};21 char sendBuf[128] = {'\0'};22 //char *sendBuf = "Message from client";23 24 struct sockaddr_in c_addr;25 26 memset(&c_addr, 0, sizeof(struct sockaddr_in));27 28 if(argc != 3){29 printf("Please input three params\n");30 exit(-1);31 }32 33 //1. socket34 c_fd = socket(AF_INET, SOCK_STREAM, 0);35 if(c_fd == -1){36 perror("Create socekt failed:");37 exit(-1);38 }39 40 //2. connect41 c_addr.sin_family = AF_INET;42 c_addr.sin_port = htons(atoi(argv[2]));43 inet_aton(argv[1], &c_addr.sin_addr);44 45 if(connect(c_fd, (struct sockaddr *)&c_addr, sizeof(struct sockaddr_in)) == -1){46 perror("Connect failed:");47 exit(-1);48 }49 50 while(1){51 memset(sendBuf, '\0', sizeof(sendBuf));52 53 printf("input :");54 gets(sendBuf);55 write(c_fd, sendBuf, strlen(sendBuf));56 57 if(fork() == 0){58 while(1){59 memset(readBuf, '\0', sizeof(readBuf));60 61 n_read = read(c_fd, readBuf, sizeof(readBuf));62 if(n_read == -1){63 printf("Read failed:");64 }else{65 printf("Get server message: %s\n", readBuf);66 }67 }68 }69 }70 71 return 0;72 }
相关文章:

Linux系统编程——网络编程的学习
Linux系统编程学习相关博文 Linux系统编程——文件编程的学习Linux系统编程——进程的学习Linux系统编程——进程间通信的学习Linux系统编程——线程的学习 Linux系统编程——网络编程的学习 一、概述1. TCP/UDP2. 端口号3. 字节序4. Sockt服务器和客户端的开发步骤1. 服务器2…...
Vue中的ref 和$refs的使用
ref 和$refs 作用:利用ref 和$refs可以用于获取dom元素,或组件实例 特点:查找范围→当前组件内(更精确稳定,原生的dom在vue子组件中查找最终也会扫描到父组件) 1. 获取dom 目标标签–添加ref 属性 <…...
Hive【非交互式使用、三种参数配置方式】
前言 今天开始学习 Hive,因为毕竟但凡做个项目基本就避不开用 Hive ,争取这学期结束前做个小点的项目。 第一篇博客内容还是比较少的,环境的搭建配置太琐碎没有写。 Hive 常用使用技巧 交互式使用 就是我们正常的进入 hive 命令行下的使用…...
基于Yolov8的工业小目标缺陷检测(1)
目录 1.工业油污数据集介绍 1.1 小目标定义 1.2 难点 1.3 工业缺陷检测算法介绍 1.3.1 YOLOv8...
Python文件操作和管理指南:打开、读取、写入和管理文件
文章目录 文件(File)打开文件使用 with ... as 语句打开文件读取文件内容读取大文件的方式逐行读取和读取全部行写文件操作文件定位seek()tell() 关闭文件 文件管理获取目录结构获取当前目录切换当前所在目录创建目录删除目录删除文件重命名文件 总结pyt…...

WebGL 用鼠标控制物体旋转
目录 鼠标控制物体旋转 如何实现物体旋转 示例程序(RotateObject.js) 代码详解 示例效果 鼠标控制物体旋转 有时候,WebGL程序需要让用户通过鼠标操作三维物体。这一节来分析示例程序RotateObject,该程序允许用户通过拖动&…...

Spring Boot魔法:简化Java应用的开发与部署
文章目录 什么是Spring Boot?1. 自动配置(Auto-Configuration)2. 独立运行(Standalone)3. 生产就绪(Production Ready)4. 大量的起步依赖(Starter Dependencies) Spring …...
参议院算法Java
Dota2 的世界里有两个阵营: Radiant(天辉)和 Dire(夜魇) Dota2 参议院由来自两派的参议员组成。现在参议院希望对一个 Dota2 游戏里的改变作出决定,他们以一个基于轮为过程的投票进行。在每一轮中,每一位参议员都可以行使两项权利中的一项: 禁止一名参议员的权利:参…...

前端提交规范 ESLint + Prettier + husky + lint-staged
如何统一代码风格,规范提交呢? 推荐使用前端规范全家桶 ESLint Prettier husky lint-staged。 eslint (github.com/eslint/esli…)JavaScript 代码检测工具,检测并提示错误或警告信息prettier (github.com/prettier/pr…) 代码自动化格式…...

python实现命令tree的效果
把所有的文档都传到了git上,但是内容过多找起来不方便,突发奇想如果能在readme中,递归列出所有文件同时添加上对应的地址,这样只需要搜索到对应的文件点击就能跳转过去了… 列出文件总得有个显示格式,所以就按照tree的来了… 用python实现命令tree的效果 首先,这是tree的效果…...

Deformable DETR(2020 ICLR)
Deformable DETR(2020 ICLR) detr训练epochs缩小十倍,小目标性能更好 Deformable attention 结合变形卷积的稀疏空间采样和Transformer的关系建模能力 使用多层级特征层特征,不需要使用FPN的设计(直接使用backbone多层级输出&a…...

springboot01
目录 新建Maven工程,什么都不选 pom.xml加上 新建包top.cjz.controller 新建类HelloController 新建类HelloApplication 运行浏览器访问 新建Maven工程,什么都不选 pom.xml加上 <!--springboot工程需要继承的父工程--> <parent…...

虚拟机中window/ubuntu系统如何联网?
以下内容源于网络资源的学习与整理,如有侵权请告知删除。 参考博客 (1)VMware虚拟机中Windows11无法连接网络 (2)图解vmware虚拟机win8无线上网 (3)VMware中VMnet0、VMnet1、VMnet8是什么 &…...

计算物理专题----随机游走实战
计算物理专题----随机游走实战 Problem 1 Implement the 3D random walk 拟合线 自旋的 拟合函数(没有数学意义) 参数:0.627,3.336,0.603,-3.234 自由程满足在一定范围内的均匀分布以标准自由程为单位长度,…...

《思维与智慧》简介及投稿邮箱
《思维与智慧》自1982年创刊,经国家新闻出版署批准,由河北省教育厅主管,河北行知文化传媒有限责任公司主办的益智励 志类大众文化期刊。 《思维与智慧》办刊宗旨是:“开发思维,启迪智慧,滋润心灵”&#x…...
flask+python快速搭建
app.py """APP 入口模块""" from traceback import format_excfrom api_limiter import limiter from flask import Flask, jsonify import loggingfrom controller import api_sql_blueapp Flask(__name__) limiter.init_app(app) app.regist…...

基于微信小程序的美术馆预约平台设计与实现(源码+lw+部署文档+讲解等)
前言 💗博主介绍:✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java、小程序技术领域和毕业项目实战✌💗 👇🏻…...

ruoyi-vue-pro yudao 项目商城 mall 模块启用及相关SQL脚本
目前ruoyi-vue-pro 项目虽然开源,但是商城 mall 模块被屏蔽了,查看文档却要收费 199元(知识星球),价格有点太高了吧。 分享下如何启用 mall 模块,顺便贴上sql相关脚本。 一、启用模块 修改根目录 pom.xm…...

default 和 delete 与默认构造函数 的使用
前言 使用default和delete关键字来干预编译器自动生成的函数。让我详细解释一下这些知识点: 正文 编译器生成的默认构造函数: 如果类A没有定义任何构造函数,那么编译器会自动生成一个无参的默认构造函数 A()。这个默认构造函数实际上是一个…...

【开发篇】一、热部署
文章目录 1、手工启动热部署2、自动启动热部署3、热部署范围配置4、关闭热部署功能 1、手工启动热部署 日常开发与调试,改几行代码想看效果就得手动点重启,很繁琐,接下来考虑启动热部署。首先引入springboot开发者工具: <dep…...

智慧医疗能源事业线深度画像分析(上)
引言 医疗行业作为现代社会的关键基础设施,其能源消耗与环境影响正日益受到关注。随着全球"双碳"目标的推进和可持续发展理念的深入,智慧医疗能源事业线应运而生,致力于通过创新技术与管理方案,重构医疗领域的能源使用模式。这一事业线融合了能源管理、可持续发…...

Prompt Tuning、P-Tuning、Prefix Tuning的区别
一、Prompt Tuning、P-Tuning、Prefix Tuning的区别 1. Prompt Tuning(提示调优) 核心思想:固定预训练模型参数,仅学习额外的连续提示向量(通常是嵌入层的一部分)。实现方式:在输入文本前添加可训练的连续向量(软提示),模型只更新这些提示参数。优势:参数量少(仅提…...

关于nvm与node.js
1 安装nvm 安装过程中手动修改 nvm的安装路径, 以及修改 通过nvm安装node后正在使用的node的存放目录【这句话可能难以理解,但接着往下看你就了然了】 2 修改nvm中settings.txt文件配置 nvm安装成功后,通常在该文件中会出现以下配置&…...

Opencv中的addweighted函数
一.addweighted函数作用 addweighted()是OpenCV库中用于图像处理的函数,主要功能是将两个输入图像(尺寸和类型相同)按照指定的权重进行加权叠加(图像融合),并添加一个标量值&#x…...

vue3+vite项目中使用.env文件环境变量方法
vue3vite项目中使用.env文件环境变量方法 .env文件作用命名规则常用的配置项示例使用方法注意事项在vite.config.js文件中读取环境变量方法 .env文件作用 .env 文件用于定义环境变量,这些变量可以在项目中通过 import.meta.env 进行访问。Vite 会自动加载这些环境变…...

零基础在实践中学习网络安全-皮卡丘靶场(第九期-Unsafe Fileupload模块)(yakit方式)
本期内容并不是很难,相信大家会学的很愉快,当然对于有后端基础的朋友来说,本期内容更加容易了解,当然没有基础的也别担心,本期内容会详细解释有关内容 本期用到的软件:yakit(因为经过之前好多期…...
Vue 3 + WebSocket 实战:公司通知实时推送功能详解
📢 Vue 3 WebSocket 实战:公司通知实时推送功能详解 📌 收藏 点赞 关注,项目中要用到推送功能时就不怕找不到了! 实时通知是企业系统中常见的功能,比如:管理员发布通知后,所有用户…...

海云安高敏捷信创白盒SCAP入选《中国网络安全细分领域产品名录》
近日,嘶吼安全产业研究院发布《中国网络安全细分领域产品名录》,海云安高敏捷信创白盒(SCAP)成功入选软件供应链安全领域产品名录。 在数字化转型加速的今天,网络安全已成为企业生存与发展的核心基石,为了解…...

CSS 工具对比:UnoCSS vs Tailwind CSS,谁是你的菜?
在现代前端开发中,Utility-First (功能优先) CSS 框架已经成为主流。其中,Tailwind CSS 无疑是市场的领导者和标杆。然而,一个名为 UnoCSS 的新星正以其惊人的性能和极致的灵活性迅速崛起。 这篇文章将深入探讨这两款工具的核心理念、技术差…...
用js实现常见排序算法
以下是几种常见排序算法的 JS实现,包括选择排序、冒泡排序、插入排序、快速排序和归并排序,以及每种算法的特点和复杂度分析 1. 选择排序(Selection Sort) 核心思想:每次从未排序部分选择最小元素,与未排…...