Linux网络编程——socket 通信基础

Linux网络编程——socket 通信基础

    • 1. socket 介绍
    • 2. 字节序
      • 2.1 简介
      • 2.2 字节序举例
      • 2.3 字节序转换函数
    • 3. socket 地址
      • 3.1 通用 socket 地址
      • 3.2 专用 socket 地址
    • 4. IP地址转换(字符串ip -> 整数,主机、网络字节序的转换 )
    • 5. TCP 通信流程
    • 6. 套接字函数

1. socket 介绍

    所谓 socket套接字),就是对网络中不同主机上的应用进程之间进行 双向通信的 端点的抽象。一个套接字就是网络上进程通信的一端,提供了 应用层进程 利用网络协议交换数据的机制。从所处的地位来讲,套接字 上联 应用进程下联 网络协议栈,是 应用程序 通过 网络协议 进行通信的接口,是 应用程序 与 网络协议根 进行交互的接口。

    socket 可以看成是两个网络应用程序进行通信时,各自通信连接中的端点,这是一个 逻辑上的概念。它是网络环境中 进程间通信API,也是可以被命名寻址通信端点,使用中的每一个套接字都有其类型和一个与之相连进程。通信时其中一个网络应用程序将要传输的一段信息写入它所在主机的 socket 中,该 socket 通过与 网络接口卡NIC)相连的传输介质将这段信息送到另外一台主机socket 中,使对方能够接收到这段信息。socket 是由 IP 地址端口 结合的,提供向应用层进程传送数据包的机制

    socket 本身有“ 插座 ”的意思,在 Linux 环境下,用于表示 进程间网络通信特殊文件类型本质为 内核 借助 缓冲区 形成的 伪文件。既然是文件,那么理所当然的,我们可以使用 文件描述符 引用套接字。与管道类似的,Linux 系统将其封装成文件的目的是为了 统一接口,使得 读写套接字读写文件 的操作一致。区别是 管道 主要应用于 本地进程间通信,而 套接字 多应用于 网络进程间数据的传递

在这里插入图片描述

使用 文件描述符 fd 引用 socket
在这里插入图片描述

套接字通信 分两部分:

  • 服务器端被动 接受连接,一般不会主动发起连接
  • 客户端主动 向服务器发起连接

socket 是一套 通信的接口LinuxWindows 都有,但是有一些细微的差别

2. 字节序

2.1 简介

    现代 CPU 的累加器 一次都能 装载(至少)4 字节(这里考虑 32 位机),即一个整数。那么这 4 字节 在 内存 中排列的顺序 将影响它被累加器装载成的整数的值,这就是字节序问题。在各种计算机体系结构中,对于字节等的存储机制有所不同,因而引发了计算机通信领域中一个很重要的问题,即通信双方交流的信息单元比特字节双字 等等)应该以什么样的顺序进行传送。如果不达成一致的规则,通信双方将无法进行正确的编码/译码从而导致通信失败。

    字节序,顾名思义 字节的顺序,就是大于一个字节类型的数据在内存中的存放顺序 (一个字节的数据当然就无需谈顺序的问题了)。

    字节序 分为 小端字节序Big-Endian) 和 大端字节序Little-Endian)。

  • 小端字节序则 是指 整数的 低位字节 则存储在内存的 地址 处,而 高位字节 存储在内存的 地址 处。
  • 大端字节序 是指一个 整数的 最高位字节23 ~ 31 bit)存储在内存的 地址 处,低位字节0 ~ 7 bit)存储在 内存的 地址 处;

2.2 字节序举例

小端字节序

  • 0x 01 02 03 04 (十六进制,四字节ff = 255)

  • 内存的方向 ----->

  • 内存的低位 -----> 内存的高位
    04 03 02 01

0x 11 22 33 44 12 34 56 78八个字节
在这里插入图片描述

大端字节序

  • 0x 01 02 03 04
  • 内存的方向 ----->
  • 内存的低位 -----> 内存的高位
    01 02 03 04

0x 12 34 56 78 11 22 33 44
在这里插入图片描述

  • 通过代码检测当前主机的字节序
#include <stdio.h>int main() {union {short value;    // 2字节char bytes[sizeof(short)];  // char[2]} test;test.value = 0x0102;if((test.bytes[0] == 1) && (test.bytes[1] == 2)) {printf("大端字节序\n");} else if((test.bytes[0] == 2) && (test.bytes[1] == 1)) {printf("小端字节序\n");} else {printf("未知\n");}return 0;
}

在这里插入图片描述

2.3 字节序转换函数

当格式化的数据在两台使用不同字节序的主机之间直接传递时,接收端必然错误的解释之。

  • 解决问题的方法是:发送端 总是 把要发送的数据 转换成 大端字节序数据 后再发送,而接收端知道对方传送过来的数据总是采用大端字节序,所以接收端可以根据自身采用的字节序决定是否对接收到的数据进行转换(小端机转换,大端机不转换)。

    网络字节顺序TCP/IP规定好的 一种数据表示格式,它与具体的 CPU 类型、操作系统等无关,从而可以保证数据在不同主机之间传输时能够被正确解释,网络字节顺序 采用 大端排序 方式。


BSD Socket 提供了封装好的 转换接口,方便程序员使用。

  • 包括从 主机字节序网络字节序转换函数htonshtonl
  • 网络字节序主机字节序 的转换函数:ntohsntohl
h 	- host 主机,主机字节序
to 	- 转换成什么
n 	- network 网络字节序
s 	- short : unsigned short   无符号短整型,两个字节
l 	- long : unsigned int		 无符号长整型,四个字节
#include <arpa/inet.h>
/*网络通信时,需要将主机字节序转换成网络字节序(大端),另外一段获取到数据以后根据情况将网络字节序转换成主机字节序。
*/// 32位,转IP
uint32_t htonl(uint32_t hostlong);    	// 主机字节序 -> 网络字节序
uint32_t ntohl(uint32_t netlong); 		// 网络字节序 -> 主机字节序// 16位,转换端口
uint16_t htons(uint16_t hostshort); 	// 主机字节序 -> 网络字节序
uint16_t ntohs(uint16_t netshort);		// 网络字节序 -> 主机字节序
#include <stdio.h>
#include <arpa/inet.h>int main() {// htonl  转换IPchar buf[4] = {192, 168, 1, 100};int num = *(int *)buf;int sum = htonl(num);unsigned char *p = (char *)&sum;printf("%d %d %d %d\n", *p, *(p+1), *(p+2), *(p+3));printf("=======================\n");// htons 转换端口unsigned short a = 0x0102;printf("a : %x\n", a);unsigned short b = htons(a);printf("b : %x\n", b);printf("=======================\n");// ntohl 转换IPunsigned char buf1[4] = {1, 1, 168, 192};int num1 = *(int *)buf1;int sum1 = ntohl(num1);unsigned char *p1 = (unsigned char *)&sum1;printf("%d %d %d %d\n", *p1, *(p1+1), *(p1+2), *(p1+3));// ntohs 转换端口return 0;
}

3. socket 地址


    socket 地址 其实是一个 结构体,封装 端口号IP 等信息。 后面的 socket 相关的 api 中需要使用到这个 socket地址

  • 客户端 -> 服务器IP, Port

3.1 通用 socket 地址


    socket 网络编程接口 中表示 socket 地址 的是 结构体 sockaddr,其定义如下:

#include <bits/socket.h>struct sockaddr {sa_family_t sa_family;	// 地址族类型char sa_data[14]; 		// 14字节
};typedef unsigned short int sa_family_t;		// 2字节

    sa_family 成员是 地址族类型sa_family_t)的 变量地址族类型 通常与 协议族类型 对应。常见的 协议族protocol family,也称 domain)和对应的 地址族address family) 如下所示:
在这里插入图片描述
     PF_*AF_* 都定义在 bits/socket.h 头文件中,后者前者完全相同的值,所以二者通常混用

    sa_data 成员用于存放 socket 地址值。但是,不同的协议族的地址值具有不同的含义和长度,如下所示:
在这里插入图片描述

     由上表可知,14 字节sa_data 根本无法容纳多数协议族的地址值。因此,Linux 定义了下面这个 新的通用的 socket 地址结构体,这个结构体不仅提供了足够大的空间用于存放地址值,而且是 内存对齐 的。

#include <bits/socket.h>
struct sockaddr_storage
{sa_family_t sa_family;			// 地址族类型unsigned long int __ss_align;	// 内存对齐char __ss_padding[ 128 - sizeof(__ss_align) ];
};typedef unsigned short int sa_family_t;

3.2 专用 socket 地址

     很 多网络编程函数 诞生 早于 IPv4 协议,那时候都使用的是 struct sockaddr 结构体,为了向前兼容,现在 sockaddr 退化成了(void *)的作用,传递一个地址 给函数,至于这个函数是 sockaddr_in 还是 sockaddr_in6,由地址族确定,然后函数内部再 强制类型转化 为所需的地址类型。
在这里插入图片描述
    UNIX 本地域协议族 使用如下 专用的 socket 地址结构体

#include <sys/un.h>struct sockaddr_un
{sa_family_t sin_family;char sun_path[108];
};

    TCP/IP 协议族sockaddr_insockaddr_in6 两个专用的 socket 地址结构体,它们分别用于 IPv4IPv6

#include <netinet/in.h>struct sockaddr_in
{sa_family_t sin_family; 		/* __SOCKADDR_COMMON(sin_) 地址族类型*/in_port_t sin_port; 			/* Port number. */struct in_addr sin_addr; 		/* Internet address. *//* Pad to size of `struct sockaddr'.  填充*/unsigned char sin_zero[sizeof (struct sockaddr) - __SOCKADDR_COMMON_SIZE - sizeof (in_port_t) - sizeof (struct in_addr)];
};struct in_addr
{in_addr_t s_addr;
};struct sockaddr_in6
{sa_family_t sin6_family;in_port_t sin6_port; /* Transport layer port # */uint32_t sin6_flowinfo; /* IPv6 flow information */struct in6_addr sin6_addr; /* IPv6 address */uint32_t sin6_scope_id; /* IPv6 scope-id */
};typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef uint16_t in_port_t;
typedef uint32_t in_addr_t;#define __SOCKADDR_COMMON_SIZE (sizeof (unsigned short int))

    所有 专用 socket 地址(以及 sockaddr_storage)类型的变量 在实际使用时都需要转化通用 socket 地址 类型 sockaddr强制转化 即可),因为所有 socket 编程接口 使用的地址参数类型都是 sockaddr

4. IP地址转换(字符串ip -> 整数,主机、网络字节序的转换 )

    通常,人们习惯用 可读性好的字符串 来表示 IP 地址,比如用 点分十进制字符串 表示 IPv4 地址,以及用 十六进制字符串 表示 IPv6 地址。但编程中我们需要先把它们 转化为 整数(二进制数)方能使用。而记录日志时则相反,我们要把整数表示的 IP 地址 转化为 可读的字符串。下面 3 个函数可用于用 点分十进制字符串 表示的 IPv4 地址 和 用 网络字节序整数 表示的 IPv4 地址 之间的转换:

#include <arpa/inet.h>// 下面的这些函数比较久,使用起来比较麻烦,不推荐使用 
in_addr_t inet_addr(const char *cp); 	// 点分十进制字符串 -> 整数
int inet_aton(const char *cp, struct in_addr *inp);	// 点分十进制字符串 -> 整数, 并保存到结构体指针 inp 中
char *inet_ntoa(struct in_addr in); 	// 整数 -> 点分十进制字符串

    下面这对更新的函数也能完成前面 3 个函数同样的功能,并且它们同时适用 IPv4 地址IPv6 地址:⭐️

#include <arpa/inet.h>// p:点分十进制的IP字符串,n:表示network,网络字节序的整数
int inet_pton(int af, const char *src, void *dst);af:地址族: AF_INET AF_INET6src:需要转换的点分十进制的IP字符串dst:转换后的结果保存在这个里面// 将网络字节序的整数,转换成点分十进制的IP地址字符串
const char *inet_ntop(int af, const void *src, char *dst, socklen_t size);af:地址族: AF_INET AF_INET6src: 要转换的ip的整数的地址dst: 转换成IP地址字符串保存的地方size:第三个参数的大小(数组的大小)返回值:返回转换后的数据的地址(字符串),和 dst 是一样的
#include <stdio.h>
#include <arpa/inet.h>int main() {// 创建一个ip字符串,点分十进制的IP地址字符串char buf[] = "192.168.1.4"; // 后面默认还有一个字符串结束符unsigned int num = 0;// 将点分十进制的IP字符串转换成网络字节序的整数inet_pton(AF_INET, buf, &num);unsigned char * p = (unsigned char *)&num;printf("%d %d %d %d\n", *p, *(p+1), *(p+2), *(p+3)); // 大端排序// 将网络字节序的IP整数转换成点分十进制的IP字符串char ip[16] = "";const char * str =  inet_ntop(AF_INET, &num, ip, 16);printf("str : %s\n", str);printf("ip : %s\n", str);printf("%d\n", ip == str);return 0;
}

在这里插入图片描述

5. TCP 通信流程

TCPUDP -> 传输层的协议

  • UDP: 用户数据报协议,面向无连接,可以单播,多播,广播, 面向 数据报,不可靠
  • TCP: 传输控制协议,面向连接的,可靠的,基于 字节流仅支持单播传输
UDPTCP
是否创建连接无连接面向连接
是否可靠不可靠可靠的
连接的对象个数一对一、一对多、多对一、多对多支持一对一
传输的方式面向数据报面向字节流
首部开销8个字节最少20个字节
适用场景实时应用(视频会议,直播)可靠性高的应用(文件传输)

在这里插入图片描述

TCP 通信的流程 ⭐️⭐️⭐️

⭐️服务器端被动接受连接的角色)⭐️

  1. 创建 一个用于监听的套接字
    • 监听:监听有客户端的连接
    • 套接字 :这个套接字其实就是一个 文件描述符
  2. 将这个 监听 文件描述符本地的IP端口 绑定IP端口 就是 服务器的地址信息
    • 客户端 连接 服务器 的时候使用的就是这个 IP端口
  3. 设置 监听,监听的 fd 开始工作
  4. 阻塞等待,当有客户端发起连接,解除阻塞,接受客户端的连接,会得到一个 和客户端通信的 套接字fd
  5. 通信
    • 接收数据
    • 发送数据
  6. 通信结束断开连接

⭐️客户端⭐️

  1. 创建一个用于通信的套接字fd
  2. 连接服务器,需要指定连接的服务器IP端口
  3. 连接成功了,客户端可以直接和服务器 通信
    • 接收数据
    • 发送数据
  4. 通信结束断开连接

6. 套接字函数

#include <sys/types.h>
#include <sys/socket.h>
#include <arpa/inet.h> // 包含了这个头文件,上面两个就可以省略int socket(int domain, int type, int protocol);- 功能:创建一个套接字- 参数:- domain: 协议族AF_INET : ipv4AF_INET6 : ipv6AF_UNIX, AF_LOCAL : 本地套接字通信(进程间通信)- type: 通信过程中使用的协议类型SOCK_STREAM : 流式协议SOCK_DGRAM : 报式协议- protocol : 具体的一个协议。一般写0(默认)- SOCK_STREAM : 流式协议默认使用 TCP- SOCK_DGRAM : 报式协议默认使用 UDP- 返回值:- 成功:返回文件描述符,操作的就是内核缓冲区。- 失败:-1int bind(int sockfd, const struct sockaddr *addr, socklen_t addrlen); // socket命名- 功能:绑定,将fd 和本地的IP + 端口进行绑定- 参数:- sockfd : 通过socket函数得到的文件描述符- addr : 需要绑定的socket地址,这个地址封装了ip和端口号的信息- addrlen : 第二个参数结构体占的内存大小int listen(int sockfd, int backlog);  /proc/sys/net/core/somaxconn- 功能:监听这个socket上的连接- 参数:- sockfd : 通过socket()函数得到的文件描述符- backlog : 未连接的队列 和 已经连接的队列 和的最大值(以使用 cat /proc/sys/net/core/somaxconn 查看:4096),一般不用设置那么大,如:8/128int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);- 功能:接收客户端连接,默认是一个阻塞的函数,阻塞等待客户端连接- 参数:- sockfd : 用于监听的文件描述符- addr : 传出参数,记录了连接成功后客户端的地址信息(ip,port)- addrlen : 指定第二个参数的对应的内存大小- 返回值:- 成功 :用于通信的文件描述符- -1 : 失败int connect(int sockfd, const struct sockaddr *addr, socklen_t addrlen);- 功能: 客户端连接服务器- 参数:- sockfd : 用于通信的文件描述符- addr : 客户端要连接的服务器的地址信息- addrlen : 第二个参数的内存大小- 返回值:成功 0, 失败 -1ssize_t write(int fd, const void *buf, size_t count); // 写数据
ssize_t read(int fd, void *buf, size_t count); // 读数据

TCP 通信实现

(1)服务器端

    创建 server.c 文件

// TCP 通信的服务器端#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main() {// 1.创建socket(用于监听的套接字)int lfd = socket(AF_INET, SOCK_STREAM, 0);if(lfd == -1) {perror("socket");exit(-1);}// 2.绑定struct sockaddr_in saddr;saddr.sin_family = AF_INET;// inet_pton(AF_INET, "192.168.216.129", &saddr.sin_addr.s_addr);  // 主机字节序 -> 网络字节序saddr.sin_addr.s_addr = INADDR_ANY;  // 0.0.0.0   (任意地址)saddr.sin_port = htons(9999);   // 主机字节序要转为网络字节序int ret = bind(lfd, (struct sockaddr *)&saddr, sizeof(saddr));if(ret == -1) {perror("bind");exit(-1);}// 3.监听ret = listen(lfd, 8);if(ret == -1) {perror("listen");exit(-1);}// 4.接收客户端连接struct sockaddr_in clientaddr;  // 客户端地址信息int len = sizeof(clientaddr);int cfd = accept(lfd, (struct sockaddr *)&clientaddr, &len);  // 阻塞函数;连接成功,返回用于通信的文件描述符if(cfd == -1) {perror("accept");exit(-1);}// 输出客户端的信息char clientIP[16];inet_ntop(AF_INET, &clientaddr.sin_addr.s_addr, clientIP, sizeof(clientIP));  //IP:网络字节序 -> 主机字节序unsigned short clientPort = ntohs(clientaddr.sin_port);		//端口:网络字节序 -> 主机字节序printf("client ip is %s, port is %d\n", clientIP, clientPort);// 5.通信char recvBuf[1024] = {0};while(1) {// 获取客户端的数据int num = read(cfd, recvBuf, sizeof(recvBuf));  // 如果客户端没有发送数据,也会阻塞if(num == -1) {perror("read");exit(-1);} else if(num > 0) {printf("recv client data : %s\n", recvBuf);} else if(num == 0) {// 表示客户端断开连接printf("clinet closed...");break;}char * data = "hello,i am server";// 给客户端发送数据write(cfd, data, strlen(data));}// 关闭文件描述符close(cfd);close(lfd);return 0;
}

(2)客户端


#include <stdio.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <string.h>
#include <stdlib.h>int main() {// 1.创建套接字int fd = socket(AF_INET, SOCK_STREAM, 0);if(fd == -1) {perror("socket");exit(-1);}// 2.连接服务器端struct sockaddr_in serveraddr;serveraddr.sin_family = AF_INET;inet_pton(AF_INET, "192.168.216.129", &serveraddr.sin_addr.s_addr);serveraddr.sin_port = htons(9999);   // 要和服务器端的一致int ret = connect(fd, (struct sockaddr *)&serveraddr, sizeof(serveraddr));if(ret == -1) {perror("connect");exit(-1);}// 3. 通信char recvBuf[1024] = {0};while(1) {char * data = "hello,i am client";// 给客户端发送数据write(fd, data , strlen(data));sleep(1);int len = read(fd, recvBuf, sizeof(recvBuf));if(len == -1) {perror("read");exit(-1);} else if(len > 0) {printf("recv server data : %s\n", recvBuf);} else if(len == 0) {// 表示服务器端断开连接printf("server closed...");break;}}// 关闭连接close(fd);return 0;
}

在这里插入图片描述

注: 仅供学习参考,如有不足,欢迎指正!

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

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

相关文章

文件操作与IO(3) 文件内容的读写——数据流

目录 一、流的概念 二、字节流代码演示 1、InputStream read方法 第一个没有参数的版本&#xff1a; 第二个带有byte数组的版本&#xff1a; 第三个版本 搭配Scanner的使用 2、OutputStream write方法 第一个版本&#xff1a; 第二个写入整个数组版本&#xff1a; …

JasperStudio中TextField文本框组件渲染之后,出现行间距不一致的问题

目录 1.1、问题描述 1.2、解决方案 1.1、问题描述 最近在处理线上遇到的一个问题,是有关JasperReports报表相关的问题,问题背景大概是这样的:我们的项目中使用了JasperReports来渲染报表,其中使用到了Text Field文本框组件,但是问题是渲染出来的数据直接会出现一些间距…

2024年不能错过的12个开发者网站

作为一名程序员&#xff0c;我们总是在研究如何提高技能并简化工作流程。这也是为什么每年都会诞生各种各样的创新工具&#xff0c;无论需要将代码转换为令人惊叹的视觉效果&#xff0c;还是简化浏览器测试过程&#xff0c;这些工具旨在帮助我们提高工作效率&#xff0c;提升编…

麒麟KYLINSOS服务器操作系统SP3安装

原文链接&#xff1a;安装麒麟服务器操作系统V10 SP3 在当今的IT环境中&#xff0c;内网仓库的部署对于确保网络安全、加快本地访问速度以及保持软件包的一致性至关重要。特别是对于企业和组织而言&#xff0c;内网仓库可以极大地提升工作效率和系统稳定性。今天&#xff0c;我…

【Linux】常见指令1(ls指令、pwd指令、cd指令、touch指令、mkdir指令、rmdir指令、man指令、cp指令、mv指令、cat指令)

目录 01.ls指令与ll指令 02.pwd指令 03.cd指令 04.touch指令 05.mkdir指令 06.rmdir指令&&rm指令 07.man指令 08.cp指令 09.mv指令 10.cat指令 01.ls指令与ll指令 ls指令&#xff1a; 原型&#xff1a;list directory contents 语法&#xff1a;ls[选项][目录…

居间中介CRM系统:提升销售业绩,有效管理企业客户资源

居间中介CRM系统是一种用于提升销售业绩和有效管理企业客户资源的软件系统。它能够帮助居间中介机构跟踪和管理客户关系&#xff0c;提高销售团队的工作效率和组织能力。 鑫鹿居间中介CRM系统具备以下功能&#xff1a; 1. 客户管理 系统可以记录和维护客户的基本信息&#xf…

【go语言开发】yaml文件配置和解析

本文主要介绍使用第三方库来对yaml文件配置和解析。首先安装yaml依赖库&#xff1b;然后yaml文件中配置各项值&#xff0c;并给出demo参考&#xff1b;最后解析yaml文件&#xff0c;由于yaml文件的配置在全局中可能需要&#xff0c;可定义全局变量Config&#xff0c;便于调用 文…

信息安全系列04-安全启动介绍

本文框架 1. 基本概念1.1 基本概念回顾1.2 数字签名及验签流程 2. 安全启动实施2.1 信任根选择2.1.1 使用HSM作为信任根2.1.2 使用最底层Bootloader作为信任根 2.2 校验方法确认2.2.1 基于非对称加密算法&#xff08;数字签名&#xff09;2.2.2 基于对称加密算法 2.3 安全启动方…

Jenkins发送邮件、定时执行、持续部署

集成Allure报告只需要配置构建后操作即可。但如果是web自动化&#xff0c;或是用HTMLTestRunner生成报告&#xff0c;构建后操作要选择Publish HTML reports&#xff0c;而构建中还要添加Execute system Groovy script插件&#xff0c;内容&#xff1a; System.setProperty(&q…

浏览器工作原理与Javascript高级(异步)

总体介绍 浏览器运行是多进程的&#xff0c;包括主进程、渲染进行、网络进程和GPU进程等等 解析HTML时(渲染进程)用到两大引擎&#xff0c;一个是渲染引擎&#xff08;用于渲染页面&#xff09;、一个是JS引擎用于解析JS代码。又JS引擎运行是单线程的&#xff0c;所以渲染和 …

yum 和 rpm

rpm说明 rpm -qa &#xff1a;列出所有已安装的软件包 [roothub ~] rpm -qa geoipupdate-2.5.0-1.el7.x86_64 ncurses-base-5.9-14.20130511.el7_4.noarch libndp-1.2-9.el7.x86_64 libfastjson-0.99.4-3.el7.x86_64 。。。 rpm -qf FILENAME &#xff1a;查找提供 FILENAME…

vue h5 保存图片到手机本地相册的方法

效果图 1、插件和上传接口 import html2canvas from html2canvas import { upload } from /api/accept2、结构 <button clickgenerateImage>生成</button><!-- 中间信息展示 --><div class"sevenInfo textLeft" id"target-element"…