linux驱动开发day6--(epoll实现IO多路复用、信号驱动IO、设备树以及节点和属性解析相关API使用)
一、IO多路复用--epoll实现
1.核心:
红黑树、一张表以及三个接口、
2.实现过程及API
1)创建epoll句柄/创建红黑树根节点
int epfd=epoll_create(int size--无意义,>0即可)----------成功:返回根节点对应文件描述符,失败:-1
2)将要监测的文件描述符挂载到红黑树上
a.struct epoll_event event;定义事件结构体
b.struct epoll_event events[10];定义存放就绪事件描述符的数组
c.添加准备就绪事件进入epoll,如:
event.events = EPOLLIN; // 读事件
event.data.fd = fd1;
epoll_ctl(epfd, EPOLL_CTL_ADD---控制方法, fd1, &event)
3)监听事件是否发生,阻塞等待准备好的文件描述符
epoll_wait(epfd, events, 10, -1--不关心是否超时);
返回值:
>0:准备好的文件描述符的个数
=0:超时
<0:失败
4)遍历数组,做事件的处理
二、信号驱动IO
异步IO方式,linux预留了一个信号SIGIO用于进行信号驱动IO,当硬件数据准备就绪后会发起一个硬件中断,在中断的处理函数中向当前进程发送一个SIGIO信号。进程收到SIGIO信号后执行信号处理函数,在信号处理函数中将数据读走即可。
1.实现过程及API
应用程序:
1)打开设备文件
2)注册信号的信号处理函数--signal(SIGIO,信号处理函数)
3)回调驱动中的fasync方法,完成发送信号之前的准备工作
a.获取文件描述符属性
int flags=fcntl(fd,F_GETFL);
b.在文件描述符表的flags中添加FASYNC
fcntl(fd,F_SETFL,flags|FASYNC);
c.设置fd对应的驱动程序发送SIGIO信号只发送给当前进程
fcntl(fd,F_SETOWN,getpid());
4)注意:不能让主程序结束
驱动程序:
1)定义一个异步对象指针
struct fasync_struct *fp;
2)异步操作方法
int mycdev_fasync(int fd, struct file *file, int on) // 异步操作方法
{
// 完成发送信号之前的准备工作
fasync_helper(fd, file, on, &fp);
return 0;
}
需要在操作方法结构体对象中加 .fasync = mycdev_fasync,
3)向进程发送信号
参数: fp:异步对象的二级指针
sig:要发生的信号 SIGIO
band:发送信号时添加的事件标志 POLL_IN表述读数据操作
//发送信号
kill_fasync(&fp,SIGIO,POLL_IN);
三、设备树
1.概念
1)设备树(DeviceTree/DT/of)是用来保存设备信息的一种树形结构
2)设备树的源码是独立于linux内核源码存在的
3)设备树上的设备信息在内核启动后被内核解析,加载到内核空间
4)设备树上的节点(包含属性和子节点),保存设备的设备信息;设备的信息由多个属性(以链表形式存在,属性是键值对)共同描述.
2.引入设备树的原因
为了让驱动可以兼容更多硬件,不在驱动中指定设备信息,让驱动中获取设备树上的设备信息,基于这些设备信息完成硬件的控制
设备树linux官方手册:Device Tree Usage - eLinux.org
3.设备树节点结构体struct device_node和属性结构体 struct property
4.设备树节点解析API
1)根据设备树节点的名字解析指定的设备树节点信息
struct device_node *dnode;
dnode=of_find_node_by_name(NULL(默认从根节点解析),"mynode");
返回值:成功返回目标节点首地址,失败返回NULL
测试:
printk("name=%s,value=%s\n",dnode->properties->name,(char *)dnode->properties->value);
2)根据设备树节点路径解析设备树节点信息
3)根据节点的厂商信息解析指定的节点
dnode=of_find_compatible_node(NULL(默认从根节点解析),NULL(设备类型),:compatible值);
4)将大端字节序32位的数据转换成主机字节序
__u32 __be32_to_cpup(const __be32 *p)
printk("name=%s,value=%x %x\n",dnode->properties->next->next->name,
__be32_to_cpup((u32 *)dnode->properties->next->next->value),
__be32_to_cpup((u32 *)dnode->properties->next->next->value+1));
5.属性解析API
返回值:成功返回属性对象指针,失败返回NULL
struct property *pr;
int len;
pr=of_find_property(dnode,"uint",&len);
测试:
printk("name=%s value=%x %x\n",pr->name,__be32_to_cpup((u32 *)pr->value),
__be32_to_cpup((u32 *)pr->value+1));
epoll实现IO多路复用的应用程序:
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/epoll.h>
/* According to earlier standards */
#include <sys/time.h>int main(int argc, char const *argv[])
{int fd1, fd2, epfd;struct epoll_event event; // 用于操作epollstruct epoll_event events[10]; // 存放就绪事件描述符的数组char buf[128] = {0};// 创建epoll句柄epfd = epoll_create(1);if (epfd < 0){printf("epoll_create filed\n");exit(-1);}// 打开设备文件fd1 = open("/dev/input/mouse0", O_RDWR);if (fd1 < 0){printf("打开鼠标设备文件失败\n");exit(-1);}fd2 = open("/dev/mycdev0", O_RDWR);if (fd2 < 0){printf("打开鼠标设备文件失败\n");exit(-1);}// 添加准备就绪事件进入epoll;event.events = EPOLLIN; // 读事件event.data.fd = fd1;if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd1, &event) < 0){printf("epoll_ctl add filed\n");}event.events = EPOLLIN; // 读事件event.data.fd = fd2;if (epoll_ctl(epfd, EPOLL_CTL_ADD, fd2, &event) < 0){printf("epoll_ctl add filed\n");}// 监听事件是否发生while (1){// 如果成功,ret接收返回的事件个数,把就绪的事件放在events数组中int ret = epoll_wait(epfd, events, 10, -1);if (ret < 0){printf("epoll_wait filed\n");exit(-1);}int i;// 循环遍历数组,做事件的处理for (i = 0; i < ret; i++){if (events[i].events & EPOLLIN)//判断发生的事件是不是读事件{read(events[i].data.fd, buf, sizeof(buf));printf("buf:%s\n", buf);}}}close(fd1);close(fd2);return 0;
}
信号驱动IO:
proc1.c
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
#include <sys/ioctl.h>
#include <sys/select.h>
#include <sys/epoll.h>
/* According to earlier standards */
#include <sys/time.h>
int fd; // 存放就绪事件描述符的数组
char buf[128] = {0};
// 定义信号处理函数
void sigio_handler(int sig)
{read(fd, buf, sizeof(buf));printf("buf:%s\n", buf);
}
int main(int argc, char const *argv[])
{// 打开设备文件fd = open("/dev/myled0", O_RDWR);if (fd < 0){printf("打开设备文件失败\n");exit(-1);}// 注册SIGIO信号的信号处理函数signal(SIGIO, sigio_handler);// 回调驱动中的fasync方法,完成发送信号之前的准备工作int flags = fcntl(fd,F_GETFL); // 获取文件描述符属性fcntl(fd,F_SETFL,flags|FASYNC); // 在文件描述符表的flags中添加FASYNC,就可以回调fasync方法fcntl(fd,F_SETOWN,getpid());//驱动发送信号只发送给当前进程while(1){printf("aaaaa\n");sleep(1);}close(fd);return 0;
}
proc2.c
#include <stdlib.h>
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <sys/ioctl.h>int main(int argc, char const *argv[])
{char buf[128] = "hello world";int fd = open("/dev/myled0", O_RDWR);if (fd < 0){printf("打开设备文件失败\n");exit(-1);}write(fd, buf, sizeof(buf));close(fd);return 0;
}
fasync.c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/cdev.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/slab.h>
#include <linux/wait.h>
#include <linux/poll.h>
struct cdev *cdev;
char kbuf[128] = {0};
unsigned int major = 0;
unsigned int minor = 0;
dev_t devno;
module_param(major, uint, 0664); // 方便在命令行传递major的值
struct class *cls;
struct device *dev;
struct fasync_struct *fp; // 定义一个异步对象指针// 封装操作方法
int mycdev_open(struct inode *inode, struct file *file)
{printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);return 0;
}
ssize_t mycdev_read(struct file *file, char *ubuf, size_t size, loff_t *lof)
{int ret;// 判断IO方式if (file->f_flags & O_NONBLOCK) // 非阻塞{}else // 阻塞{}ret = copy_to_user(ubuf, kbuf, size);if (ret){printk("copy_to_user err\n");return -EIO;}return 0;
}
ssize_t mycdev_write(struct file *file, const char *ubuf, size_t size, loff_t *lof)
{int ret;// 从用户拷贝数据,模拟硬件数据ret = copy_from_user(kbuf, ubuf, size);if (ret){printk("copy_from_user err\n");return -EIO;}//发送信号(异步对象二级指针,要发生的信号,发送信号时添加事件的标志位)kill_fasync(&fp,SIGIO,POLL_IN);return 0;
}int mycdev_fasync(int fd, struct file *file, int on) // 异步操作方法
{// 完成发送信号之前的准备工作fasync_helper(fd, file, on, &fp);return 0;
}
int mycdev_close(struct inode *inode, struct file *file)
{printk("%s:%s:%d\n", __FILE__, __func__, __LINE__);return 0;
}
// 定义一个操作方法结构体对象并且初始化
struct file_operations fops = {.open = mycdev_open,.read = mycdev_read,.write = mycdev_write,.fasync = mycdev_fasync,.release = mycdev_close,
};
static int __init mycdev_init(void)
{int ret;// 为字符设备驱动对象申请空间cdev = cdev_alloc();if (cdev == NULL){printk("字符设备驱动对象申请空间失败\n");ret = -EFAULT;goto out1;}printk("申请对象空间成功\n");// 初始化字符设备驱动对象cdev_init(cdev, &fops);// 申请设备号if (major > 0) // 静态指定设备号{ret = register_chrdev_region(MKDEV(major, minor), 3, "myled");if (ret){printk("静态申请设备号失败\n");goto out2;}}else if (major == 0) // 动态申请设备号{ret = alloc_chrdev_region(&devno, minor, 3, "myled");if (ret){printk("动态申请设备号失败\n");goto out2;}major = MAJOR(devno); // 获取主设备号minor = MINOR(devno); // 获取次设备号}printk("申请设备号成功\n");// 注册字符设备驱动对象ret = cdev_add(cdev, MKDEV(major, minor), 3);if (ret){printk("注册字符设备驱动对象失败\n");goto out3;}printk("注册字符设备驱动对象成功\n");// 向上提交目录信息cls = class_create(THIS_MODULE, "myled");if (IS_ERR(cls)){printk("向上提交目录失败\n");ret = -PTR_ERR(cls);goto out4;}printk("向上提交目录成功\n");// 向上提交设备节点信息int i;for (i = 0; i < 3; i++){dev = device_create(cls, NULL, MKDEV(major, i), NULL, "myled%d", i);if (IS_ERR(dev)){printk("向上提交设备节点信息失败\n");ret = -PTR_ERR(dev);goto out5;}}printk("向上提交设备信息成功\n");return 0;
out5:// 释放前一次提交成功的设备信息for (--i; i >= 0; i--){device_destroy(cls, MKDEV(major, i));}class_destroy(cls); // 释放目录
out4:cdev_del(cdev);
out3:unregister_chrdev_region(MKDEV(major, minor), 3);
out2:kfree(cdev);
out1:return ret;
}
static void __exit mycdev_exit(void)
{// 释放节点信息int i;for (i = 0; i < 3; i++){device_destroy(cls, MKDEV(major, i));}// 销毁目录class_destroy(cls);// 注销驱动对象cdev_del(cdev);// 释放设备号unregister_chrdev_region(MKDEV(major, minor), 3);// 释放对象空间kfree(cdev);
}
module_init(mycdev_init);
module_exit(mycdev_exit);
MODULE_LICENSE("GPL");
相关文章:
linux驱动开发day6--(epoll实现IO多路复用、信号驱动IO、设备树以及节点和属性解析相关API使用)
一、IO多路复用--epoll实现 1.核心: 红黑树、一张表以及三个接口、 2.实现过程及API 1)创建epoll句柄/创建红黑树根节点 int epfdepoll_create(int size--无意义,>0即可)----------成功:返回根节点对应文件描述符…...
9月15日作业
Qt代码 #include "mywnd.h"//构造函数的定义 mywnd::mywnd(QWidget *parent): QWidget(parent) //显性调用父类的有参构造完成对子类从父类继承下来成员的初始化工作 {//窗口设置this->resize(QSize(500, 433));this->setWindowTitle("Widget&quo…...
关于Java多线程的那些事
多线程 多线程1. 关于多线程的理解1.1 进程和线程1.2 并行和并发1.3 线程调度 2. 创建多线程的方式创建线程有哪几种方式?2.1 通过继承Thread类来创建并启动线程的步骤如下:2.2 通过实现Runnable接口来创建并启动线程的步骤如下:2.3 通过实现…...
信息化项目验收的依据、内容和验收测评报告
随着信息系统业务覆盖率的提高和深度整合创新的逐步提高,信息系统运行阶段的复杂性和资源比例逐渐增加。一方面,信息已成为业务创新、技术应用和运营服务的综合体,而不仅仅是技术平台建设。另一方面,信息采购是技术平台建设。另一…...
解决IntelliJ IDEA执行maven打包,执行java -jar命令提示jar中没有主清单属性
问题场景 IDEA执行mvn clean package -DskipTesttrue命令或者借助工具的Maven菜单进行打包操作,然后执行java -jar app.jar命令后,提示jar中没有主清单属性 D:\WorkSpace\demo\target>java -jar demo-SNAPSHOT.jar demo-SNAPSHOT.jar中没有主清单属性…...
Python--文件和异常
目录 1、读取文件 1.1 读取文件的全部内容 1.2 相对路径和绝对路径 1.3 访问文件中的各行 1.4 使用文件中的内容 1.5 包含100万位的大型文件 1.6 圆周率中的生日 2、写入文件 2.1 写入一行 2.2 写入多行 3、异常 3.1 处理ZeroDivisionError 异常 3.2 使用try-exce…...
IDEFICS 简介: 最先进视觉语言模型的开源复现
我们很高兴发布 IDEFICS ( Image-aware Decoder Enhanced la Flamingo with Ininterleaved Cross-attention S ) 这一开放视觉语言模型。IDEFICS 基于 Flamingo,Flamingo 作为最先进的视觉语言模型,最初由 DeepMind 开发,但目前尚未公开发布…...
玩转Mysql系列 - 第20篇:异常捕获及处理详解
这是Mysql系列第20篇。 环境:mysql5.7.25,cmd命令中进行演示。 代码中被[]包含的表示可选,|符号分开的表示可选其一。 需求背景 我们在写存储过程的时候,可能会出现下列一些情况: 插入的数据违反唯一约束ÿ…...
一些工具类
1、字符串处理工具类 1.1、StrUtils package com.study.java8.util;/*** Classname:StrUtils* Description:字符串工具类* Date:2023/9/9 9:37* Author:jsz15*/import org.apache.commons.lang.text.StrBuilder; import org.apa…...
20230916后台面经整理
1.面对抢优惠券这样的高负载场景,你从架构、负载均衡等方面说一下你的设计? 答了参考Nginx进行负载均衡,然后在每台服务器怎么怎么弄(架构每一层怎么设计) 参考https://toutiao.io/posts/6z3uu2m/preview,h…...
如何通过快解析测试接口内外网?本地内网ip让外网访问连接
接口调试测试是网络技术员经常工作内容之一。如在公司内部api项目webserver测试,在公司内办公室个人电脑是正常用内网IP访问连接测试的,但在外网电脑需要远程测试时需要怎么测试呢?这里提供一种内网地址让外网访问的通用方法:快解…...
用c++实现五子棋小游戏
五子棋是一款经典小游戏,今天我们就用c实现简单的五子棋小游戏 目录 用到的算法: 思路分析 定义变量 开始写代码 完整代码 结果图: 用到的算法: 合法移动的判断:isValidMove 函数通过检查指定位置是否在棋盘范…...
Android 12.0 SystemUI下拉状态栏定制化之隐藏下拉通知栏布局功能实现(二)
1.前言 在12.0的系统定制化开发中,由于从12.0开始SystemUI下拉状态栏和11.0的变化比较大,所以可以说需要从新分析相关的SystemUI的 布局,然后做分析来实现不同的功能,今天就开始实现关于隐藏SystemUI下拉状态栏中的通知栏布局系列二,去掉下拉状态栏中 通知栏部分 白色的…...
通过finalshell快速在ubuntu上安装jdk1.8
这篇文章主要介绍一下怎么通过finalshell连接ubuntu,然后在ubuntu上安装jdk1.8,让不熟悉linux操作系统的童鞋也能快速地完成安装。 目录 一、准备一台虚拟机 二、安装finalshell远程连接工具 三、获取ubuntu虚拟机的ip地址 四、通过finalshell连接u…...
【Linux从入门到精通】多线程 | 线程互斥(互斥锁)
上篇文章我们对线程 | 线程介绍&线程控制介绍后,本篇文章将会对多线程中的线程互斥与互斥锁的概念进行详解。同时结合实际例子解释了可重入与不被重入函数、临界资源与临界区和原子性的概念。希望本篇文章会对你有所帮助。 文章目录 引入 一、重入与临界 1、1 可…...
Echarts 散点图的详细配置过程
文章目录 散点图 简介配置步骤简易示例 散点图 简介 Echarts散点图是一种常用的数据可视化图表类型,用于展示两个或多个维度的数据分布情况。散点图通过在坐标系中绘制数据点的位置来表示数据的关系。 Echarts散点图的特点如下: 二维数据展示ÿ…...
Nginx详解 五:反向代理
文章目录 1. 正向代理和反向代理1.1 正向代理概述1.1.1 什么是正向代理1.1.2 正向代理的作用1.1.3 正向代理的基本格式 1.2 反向代理概述1.2.1 什么是反向代理1.2.2 反向代理可实现的功能1.2.3 反向代理的可用模块 2. 配置反向代理2.1 反向代理配置参数2.1.1 proxy_pass2.1.2 其…...
【PDF密码】PDF文件打开之后不能打印,怎么解决?
正常的PDF文件是可以打印的,如果PDF文件打开之后发现文件不能打印,我们需要先查看一下自己的打印机是否能够正常运行,如果打印机是正常的,我们再查看一下,文件中的打印功能按钮是否是灰色的状态。 如果PDF中的大多数功…...
深入解析 qsort 函数(下),用冒泡排序模拟实现 qsort 函数
前言:对于库函数有适当了解的朋友们,对于 qsort 函数想必是有认知的,因为他可以对任意数据类型进行排序的功能属实是有点厉害的,本次分享,笔者就给大家带来 qsort 函数的全面的解读 本次知识的分享笔者分为上下俩卷文章…...
Azure + React + ASP.NET Core 项目笔记一:项目环境搭建(二)
有意义的标题 pnpm 安装umi4 脚手架搭建打包语句变更Visual Studio调试Azure 设置变更发布 pnpm 安装 参考官网,或者直接使用npm安装 npm install -g pnpmumi4 脚手架搭建 我这里用的umi4,官网已附上 这里需要把clientapp清空,之后 cd Cl…...
Vmware通过VMware tools设置共享文件夹
步骤说明: 先安装VMware tools,再设置共享文件夹即可。 写在前面: 刚安装虚拟机时,窗口可能显得太小,这是窗口分辨率没有调整导致的。 点击设置->显示->分辨率调整即可 一、安装VMware tools 1.1 点击虚拟机…...
RPA机器人流程自动化专题培训大纲 (针对大学生的版本)
一、课程简介 RPA机器人流程自动化是一种新兴的技术,它通过软件机器人模拟人类操作计算机完成重复性任务,从而实现业务流程的自动化。本课程旨在介绍RPA机器人流程自动化的基本概念、原理和应用,并通过实践案例演示如何应用RPA机器人流程自动…...
数据在内存中的存储——练习4
题目: int main() {char a[1000];int i;for(i0; i<1000; i){a[i] -1-i;}printf("%d",strlen(a));return 0; }思路分析: 已知条件: 通过循环遍历,我们得到的结果是 -1、-2、-3、-4等等。这些是数组内部的存储的元…...
Python 06 之面向对象基础
😀前言 在日常编程和软件开发中,我们通常会遇到各种各样的问题,其中很多问题都可以通过面向对象的程序设计方法来解决。面向对象编程不仅可以使代码更加组织化和系统化,而且还可以提高代码的重用性和可维护性。 . 在本教程中&…...
去除pdf/word的水印艺术字
对于pdf中的水印如果无法去除水印,则先另存为word,然后再按下面办法处理即可: 查看宏,创建:删除艺术字 添加内容: Sub 删除艺术字()Dim sh As ShapeFor Each sh In ActiveDocument.ShapesIf sh.Type msoT…...
【Linux】使用 Alist 实现阿里云盘4K播放
一、安装 Alist 官方文档 默认安装在 /opt/alist 中 curl -fsSL "https://alist.nn.ci/v3.sh" | bash -s install自定义安装路径,将安装路径作为第二个参数添加,必须是绝对路径,如果路径以 alist 结尾,则直接安装到给定…...
Gof23设计模式之状态模式
1.概述 【例】通过按钮来控制一个电梯的状态,一个电梯有开门状态,关门状态,停止状态,运行状态。每一种状态改变,都有可能要根据其他状态来更新处理。例如,如果电梯门现在处于运行时状态,就不能…...
如何免费下载RunWayML产生的视频文件
问题: 首先没有下载的按钮。 其次如果直接“视频另存为”菜单,报错。 解决方案: 1)复制视频链接。 2)新开chrome,在url中粘贴上一步的url路径。 3)当看到视频后,在视频上面右键“…...
9.14 C++作业
仿照vector手动实现自己的myVector,最主要实现二倍扩容功能 #include <iostream>using namespace std;template <typename T> class Myvector {T *data; //存储数据的数组int len; //当前数组的长度int mycapa; //容纳数据的总容量public://…...
java关于文件记录篇章之文件夹创建篇
今天,创建一个文件夹目录的时候,创建多级目录的时候发现,自己老是创建失败,但是系统显示文件夹创建成功,但是你去找文件夹的时候,又发现创建失败,这里在我成功之后封装了一个创建文件夹的创建对…...
网站改版设计要多久/百度搜索关键词技巧
留学监理服务网东北大学计算机工程(本硕连读) - Computer Engineering基本信息东 北 大 学 - Northeastern 工程学院 - 电子与计算机工程所属学校 所在院系University 系计算机工程(本硕连读) -专业名称 学历层次 本科Computer Engineering工程与技术 计算机与信息科学授予学位…...
网站可做哪些服务/快速排名优化怎么样
常见微服务的消费者本节就常见的微服务的消费者进行介绍。在Java领域比较常用的消费者框架主要有HttpClient、Ribbon、Feign 等。Apache HttpClientApache HttpClient是Apache Jakarta Common下的子项目,用来提供高效的、最新的、功能丰富的支持HTTP的客户端编程工具…...
bing网站收录/网站优化策略分析论文
简单的问答已经实现了,那么问题也跟着出现了,我不能确定问题一定是"你叫什么名字",也有可能是"你是谁","你叫啥"之类的,这就引出了人工智能中的另一项技术:自然语言处理(NLP) : 大概意思就是 让计算机明白一句话要表达的意思,NLP就相当于计算机在…...
公司网站模块制作/优化大师免费版
晚上刷到一条消息,Layui官网将在10月13日正式下线 如果你的项目中还在用Layui也不必紧张,它的下线只是限于官网,其代码依然在Github托管。 说到Layui,可能搞后端的同学不太熟悉,它是一个WebUI组件库,和Boot…...
用闲置的安卓手机做网站/爱用建站
xcopy 文件夹name %date:~0,4%%date:~5,2%%date:~8,2% /D:05-23-2017 /S /R /Y PAUSE 日期和文件夹名字修改好, 放在当前文件夹下,按D转载于:https://www.cnblogs.com/beimingbingpo/p/6892803.html...
什么人最需要建设网站/免费推广seo
以下内容均为微信内置浏览器访问的场景。本文只阐述微信分享的URL的坑,不阐述具体的代码实现和JS-SDK的具体使用。 大家有没有发现,某些网站访问的路径与最终分享的路径不同!对,没错,就是不同。 微信JS-SDK分享的URL有…...