Linux - 动静态库(下篇)

news2024/11/9 4:53:13

前言

在上篇博客当中,对静态库是什么,怎么使用,简单实现自己的静态库,这些做了描述,具体请看上篇博客:
 

本篇博客将会对 动态库是什么,怎么使用,简单实现自己的动态库,这些做描述。

动态库

动态库的创建

 动态库的创建其实说和 静态库是非常类似的,都是先想要把 源程序文件生成 ".o" 为后缀的文件,也就是编译成功过的文件,然后再 进行链接。

只是在链接过程当中链接方式不同。

在静态库当中,我们使用的编译源文件的方式是直接使用 "-c" 选项的方式生产源程序的 ".o" 编译文件。

gcc -c $^   // -c 选项不在后续 $@ 确认生成的目标文件名,默认生成的 与源程序名相同的 .o 文件

但是在动态库当中,我们在上述生成 ".o" 文件使用 "-c" 选项 之外,还需要带上 "-fPIC" (其中 PIC 要大小)选项,这个  fPIC 代表的意思是 产生位置无关码

gcc -fPIC -c xxx.c

而gcc 是默认 优先链接 动态库的,也就是,当 同一个动态库和静态库同时存在的时候,gcc 会优先链接动态库吗,除非是 当前只有 静态库,没有动态库,那么 gcc 没有办法只能使用静态库;又或者是 用户强制要求要用某一个静态库。

所以,静态库在安装的过程当中,需要用户手动去安装,但是对于 动态库,gcc 就可以自己帮助我们实现一个 ".so" 文件。

利用gcc命令,使用 "-shared" 选项,生成一个动态库:
 

gcc -shared -o libxxx.so *.o

上述就是利用在当前目录当中的所有后缀为 ".o" 的文件,打包为一个 xxx 动态库(动态库必须是 "lib" 开头 ,".o" 结尾的文件名)

上述的 "-shared" 选项就是生成一个 分享动态库。


静态库只是该源程序提供代码,本质上也就是提供一些二进制数据,所以,静态库的作用其实就是把源程序需要的代码拷贝到源程序当中,拷贝完之后,源程序当中是如何进行执行的,就和 这个静态库没关系了;静态库是不用被加载到内存当中执行的

 而动态库不一样,当源程序当中执行到 动态库当中的代码的时候,是直接跳转到 动态库当中去执行,所以,注定了 动态库当中的代码是需要被加载到内存当中供 cpu 执行的

从 两 ".a" 和 ".so" 文件的 默认权限你也可以看出,动态库的文件权限当中是带 "x" 可执行的,而 静态库不是。

动态库:

 静态库:所以,上述动态库我们直接执行就会报错:
 

 并不是 这个程序不能执行,而是这个动态库不能单独执行,他需要源程序来调用其中的代码来执行。

而,动态库和静态库不是不能执行,只是不能单独执行,他们都是未来 链接出的 可执行程序当中代码的一部分。


现在我们给出几个源程序文件:

// mylog.c
#include "mylog.h"

void Log(const char*info)
{
    printf("Warning: %s\n", info);
}

//mylog.h
#pragma once

#include <stdio.h>


void Log(const char*);

//mymath.c
#include "mymath.h"

int myerrno = 0;

int add(int x, int y)
{
    return x + y;
}
int sub(int x, int y)
{
    return x - y;
}
int mul(int x, int y)
{
    return x * y;
}
int div(int x, int y)
{
    if(y == 0){
        myerrno = 1;
        return -1;
    }
    return x / y;
}

//mymath.h
#pragma once

#include <stdio.h>

extern int myerrno;

int add(int x, int y);
int sub(int x, int y);
int mul(int x, int y);
int div(int x, int y);

//myprint.c
#include "myprint.h"


void Print()
{
    printf("hello new world!\n");
    printf("hello new world!\n");
    printf("hello new world!\n");
    printf("hello new world!\n");
}

//myprint.h

#pragma once

#include <stdio.h>


void Print();

所以,有了上述动态库创建,和上篇博客当中 对于 静态库的 创建,现在我们在makefile 当中写上对应 生成的目标文件,就可以一键生成的 对应的 动态库 和 静态库了:
 

dy-lib=libmymethod.so   #目标动态库文件的生成
static-lib=libmymath.a  # 目标静态库文件的生成

.PHONY:all              #同时生成多个目标文件
all: $(dy-lib) $(static-lib)   all 这个文件依赖于 dy-lib 和 static-lib

$(static-lib):mymath.o   # 生成静态库(把 .o 文件打包生成 libmymath.a文件)
	ar -rc $@ $^
mymath.o:mymath.c        # 由源程序 生成 对应 .o 文件
	gcc -c $^           

$(dy-lib):mylog.o myprint.o  # 生成动态库(把 .o 文件打包生成 libmymethod.so文件)
	gcc -shared -o $@ $^
mylog.o:mylog.c              # 由源程序 生成 对应 .o 文件
	gcc -fPIC -c $^    
myprint.o:myprint.c          # 由源程序 生成 对应 .o 文件
	gcc -fPIC -c $^

.PHONY:clean
clean:
	rm -rf *.o *.a *.so mylib

.PHONY:output            # 用于发布的出来 的 生成的目标文件
output:
	mkdir -p mylib/include
	mkdir -p mylib/lib
	cp *.h mylib/include
	cp *.a mylib/lib
	cp *.so mylib/lib

先 make 把对应的 源程序文件 编译生成 ".o" 文件:

下述就是上述生成之后的结果:

   把上述这些  ".o" 文件 打包到 各自的 动静态库 当中:

此时就发布完成了:
 

 上述的这个 lib 文件夹当中的内容,就是我们上述由 各个源程序文件生成的 各自的 .o 文件的动态库,和 静态库的 。

此时,直接使用这种 "#include<mylog.h>" ,然后直接编译的话,他就会提示找不到这个头文件,所以,我们还需要把 这个存储 动静态库 和 各自头文件的 mylib 文件夹,拷贝到 系统默认路径 当中,这种才能使用 这种 "#include<mylog.h>" 方式来引用头文件,和使用其中的代码。

 上述拷贝到 默认 路径当中,其实就是库的安装,这种方式使用最多的,当然,你也可以使用 gcc 命令当中的 "-i"  "-L" "-l" 三个选项来实现。

上述我们就创建了一个 动态库 和 一个静态库。


如何让可执行程序找到动态库(LD_LIBRARY_PATH 环境变量

现在我们来用上述的 动态库,来实现一个 可执行程序,然后编译运行:

在上述,生成的 动态库 和 静态库的基础之上,我们来使用上述的库,来写一个程序,看看能不能编译成功:
 

//main.c

#include "mylog.h"
#include "myprint.h"

int main()
{

    Print();
    Log("hello log function");
    return 0;
}

上述引用的两个头文件都是动态库当中头文件,也就是说上述源程序当中只引入了 我们上述实现的 动态库。

但是我们发现,虽然我们上述的 动静态库,没有在 系统默认路径下安装,但是我们还用了gcc 当中的三个指令来引入 头文件 和 库;虽然成功生成了 可执行文件,但是,当我们运行这个可执行程序的时候,却报错:找不到我们上述实现的  动态库:

 但是,当我们 file 命令 这个 a.out 可执行文件之时,却告知我们 这个可执行文件是有 动态库链接的

但是在 ldd 命令当中,显示这个 动态链接是 not found 的 :

 但是我们上述在 gcc 命令当中不是已经对 对应的 头文件 和 库名称,和库所在路径做了声明吗?已经告诉编译器了,头文件是什么,什么库,在哪里了。

是的,你已经告诉了编译器了,编译器已经知道了,但是别忘了,动态库是在加载到 内存当中,进行调度执行的,他不像静态库一样单独拷贝一份给对应的源程序当中,拷贝完就不管了;

动态库要加载到内存当中进行调度执行。

所以,上述只是 编译器知道了这个动态库要被执行,也只是编译器知道这个 库是什么,在哪里;但是,系统并不知道。

所以,系统要知道,也就是 加载器 要知道。

 所以,首先你要明白的是,动态库是一个单独的文件,他和 可执行文件是两个文件,我没在执行可执行文件之时,是带上了 路径的,就算没有带上路径,要想成功运行,必须在 PATH 环境变量当中存储的路径下,有这个可执行文件,才能够不带上路径执行。

所以,我们要想执行一个 动态库,就要让 系统找到这个动态库

 第一个方法就是把 这个 动态库拷贝到 C程序默认的系统路径当中,这样,系统就可以在 这个默认路径当中找到这个 动态库,从而执行。

如上所示,拷贝到 lib64 这个目录当中,系统 就可以找到这个 动态库,从而执行。


除了上述 拷贝 的方式,还可以 ln 命令 在上述 lib64 当中创建软链接,链接到 libmymethod.so 这个 动态库 所在的存储路径当中。

 软链接建立结果:

此时他直接就指向了 动态库的存储位置。

如上所示,我们都没有重新对 a.out 重新编译,但是上述使用 ldd 命令却已经可以看到链接 到 libmymethod.so 动态库的链接的结果了。 也可以运行了。


除了上述的 在 默认目录当中进行 拷贝,软链接 的操作,和 系统找命令的可执行程序的 PATH 环境变量一样,上述的 在C当中和 默认 查找 库的也有一个环境变量 --- LD_LIBRARY_PATH

说得更详细一点,LD_LIBRARY_PATH  环境变量当中存储的就是 系统用来搜索动态库的路径 

 

这个环境变量,可能你的系统当中会没有,但是如果你配置过 VIM 的话,可能会有。

如果没有也没关系,可以自己配置一下:

在冒号后面跟上 动态库所在的路径就可以了 ,不需要加上 动态库文件名。 

 因为,需要链接哪一个库,在 gcc 当中 选项当中已经给出了。

此时,我们再 ldd 命令查看 链接情况,发现也是链接成功了的。 


但是,上述我们是修改了 环境变量,但是我们修改的是 bash 的环境变量,因为 当前我们所有的运行的命令,都是bash 的为我们生成的子命令,export 命令是一个内建命令,因为它要修改 bash 的环境变量,所以,export 是不需要创建子进程,而是由 bash 自己的来执行。

但是,修改的终究是 bash 的环境变量,bash 当中的环境变量是在 程序启动之时,由对应的 环境变量生成脚本,一层一层 加载(添加)出来的。

所以,也就是说,如果我们不修改 每一次开机,系统脚本对 bahs 当中的环境变量的添加进行修改的话,那么,当我们关机之后,再次启动,脚本生成的还是之前的 那些环境变量。

所以,要想根本解决上述 由 LD_LIBRARY_PATH 环境变量当中存储的 路径的话,需要修改 生成的 bash 环境变量的脚本。


除了上述方式,还有一个访问可以实现:
在系统当中,有一个目录当中存储的是 后缀为 ".conf" 的文件,这些文件我们称之为 配置文件,在这些文件当中存储的都是路径信息

 这个目录是 :  /etc/ld.so.conf.d

 我们可以随便打开一个看看:

 所以,如果像系统找到 对应 动态库的话,就可以在   /etc/ld.so.conf.d 这个 路径下,创建一个 后缀为 ".conf"  的文件(文件名随便取),在这个文件当中,直接像上述一样,存放 对应 动态库的存储位置(注意此时 不需要带上 库名称)

 然后,很重要的一步,需要使用 ldconfig 命令 ,重新加载一下 上述路径下的配置文件

 上述这种 在 /etc/ld.so.conf.d 路径当中进行修改的操作,只要我们不修改我们对于其中的配置文件,那么这个修改就是永久有效的;

其实只有 上述的第三种使用 LD_LIBPARY_PATH 环境变量这个操作,如果不修改 bash 启动的脚本的话,关机之后就无效了


同样的,如果我们在上述程序当中加上 我们在上篇博客当中实现的静态库的话,只要我们gcc 编译链接成功了,把静态库当中的代码已经拷贝到 可执行程序当中用于链接了,而且此时我们运行程序是可以正常运行的,结果也是正确的。

那么,在之后,不管这个静态库被移动到什么其他位置了,不在之前链接的位置了,或者更加夸张一点,直接把静态库删除;

我们上述生成的可制成程序一样说可以跑的,因为,静态库链接,就是把静态库当中的代码直接拷贝到 对应源程序的当中,成为 源程序的 一部分。

而不像 动态库链接,链接成功之后,动态库的位置是不能移动的,因为编译链接成功之后,不仅仅是编译器在编译之时需要找到这个动态库位置,因为 动态库是要被加载到 内存当中进行执行的。

连接成功后,执行可执行程序,当 可执行程序执行到 需要动态库当中的代码之时, 就需要直接跳转到 动态库 当中执行,此时动态库需要被加载到内存当中,配合 原可执行程序 进行 执行。

所以,动态库在被加载之前,还需要被 系统知道在什么位置存储的,也就是 加载器需要知道 此动态库在什么位置存储的。

至此才能保证 动态库可以被加载到 内存当中进行运行。

如下所示:

在上述代码当中:"mymath.h" 就是上一篇博客当中实现的 静态库。 

上图就在之前 gcc 链接动态库的基础之上,加上 "-lmymath.h" 链接上静态库。 

删除静态库,并且在此执行程序:

 由上图你会发现,当我们删除掉静态库,没有重新编译链接 形成可执行文件,但是,在之前形成的 可执行文件 还是可以运行。没有出错。


当然,不管是静态库 还是 动态库,都是可以被多个 源程序所引用的。但是,由静态库 链接生成 的 多个 可执行文件,如果把此静态库删除掉,这些个 可执行程序还是可以跑的;

但是如果是 由 动态库 链接形成的多个可执行文件,如果是把此动态库删除掉了,那个由此动态库链接的所有 可执行程序就都不能跑了。

如上图所示:我们把上述 main 和 mytest 两个可执行文件 需要的 libmymethod.so 动态库删除了,发现就不能运行了。 

所以,动态库再被系统加载之后,会被所有的 进程所共享!!!

使用了动态链接的方式 形成 的 可执行文件,都是需要系统加载到内存,运行这个 动态库的。

而且,这个动态库被加载到内存,只需要加载一次,因为一次也已经够所有 进程所使用了。这样的话,在系统当中, 就减少了重复的代码。

所以,一个进程,在磁盘(外存)上有自己独立的一份代码,而在 内存当中又有 和其他 进程 有相同的 代码,共享这些相同的代码

所以,我们把 动态库 又叫做 共享库

那么,一个系统当中肯定会加载很多个 动态库到内存当中,操作系统如何管理这些海量的 库呢?

我们说,只要是加载到内存当中,被操作系统所管理的,都离不开 这 六字真言:先描述,再组织 

 先把 管理一个库,所需要的所有属性,都用一个 struct 结构体描述起来(这叫做 先描述对象),然后用某种数据结构,把上述生成的 很多个 struct 结构体都链接起来(这叫做 再组织)。


动态库是如何做到被所有进程共享的?(重谈进程地址空间)

 

如上所示,在每一个进程的 地址空间当中,每一个地址都是一个 虚拟地址,通过 页表映射到 物理内存地址。

 在磁盘当中,每一个进程 独立的 可执行程序,从磁盘当中被加载到 内存当中,通过 页表来映射到 进程地址空间的 代码区当中。

而,如果在 进程 独立的 可执行程序 当中发现,比如是 printf()函数并没有在 可执行程序当中实现,但是,在编译链接之时,又有一个动态库当中有 printf()函数的实现,那么在运行到 printf()函数之时,就会 跳转到 共享区 当中去寻找 printf()实现的虚拟地址,通过页表映射到物理内存当中。

在执行完之后,就会跳回 代码区当中继续执行 这个进程的 代码。

至此,动态库当中的代码就被进程所使用,同样的,上述只是一个进程,同样可以是多个进程。

那么,动态库加载到内存当中的 代码数据,就可以被 多个进程所使用了。

所以,在页表当中建立映射之后,从此往后,进程在执行任何的代码,不管是自己 可执行程序当中的代码,还是 共享区当中的 动态库的共享代码,都是可以在 进程地址空间当中进行 执行的。

 此时,对于动态库 和 自己可知程序当中的代码,就都可以看做是一份代码了,只不过在执行到 动态库当中的代码之时,需要跳转到 动态库当中代码进行执行,只是可能两份代码跳转的比较远罢了。


所以,在操作系统是如何 跳转到共享区当中,找到对应的 共享代码区执行的。其实就是上述所说的,先描述在组织。

操作系统只需要管理好 先描述在组织 的数据结构,就可以管理好其中的 对象,而管理好一个一个的 存储 库 属性的 对象,就可以管理好 一个一个库在内存当中的 使用情况:比如 , 当前是否正在被使用?大小占多少?进程要使用的库,有没有被加载到内存当中?起始地址是什么?·········

所以,在上述 操作之后,OS 对于 库的 加载情况就非常清楚了。


共享库当中的 errno 变量存储的问题

 共享库当中的代码是共享的,那么其中的数据是不是共享的呢?

如果我们在一个 C 程序当中,对 errno 这个 C库当中的全局变量进行了修改,其他C 程序会不会受到影响?

答案是不会的,因为 共享区是在 栈 和 堆区当中的,这也就意味着,当 某一个进程对 errno 变量做了修改,就会发生写时拷贝,谁写谁就自己拷贝一份 自己存储 自己修改的数据。

这个写时拷贝的 出来的空间 在共享区当中存储,同时,在 C 管理 要输出到文件当中的数据,也有一个 struct_file 结构体来管理,在这个结构体当中就有 C 帮我们提供的 语言级别的缓冲区(用户级缓冲区)。

 


总结 

静态库 只需要在 编译时期 ,可以找到这个 静态库在哪,把这个静态库当中的数据,拷贝到 对应源程序当中。

但是动态链接不一样,如果要是用到 动态库的话,动态库就需要再 内存当中被加载,从而执行。而 静态库是不需要 加载到内存当中执行的,它完成了 拷贝的工作就可以 下岗了,在当前可执行程序的链接就不需要他了。

在上述说明的 四种 可执行程序链接 动态库 的方式的当中,其实用得最多就是 拷贝 这种方式。因为这种方式最直接了当,你会发现,这种方式其实是最方便的。

第三方库链接操作例子:【Linux拓展】ncurses库的安装和使用 {ncurses库的安装方法,ncurses库的使用手册,基于终端的贪吃蛇游戏}_芥末虾的博客-CSDN博客

上述的 ncurses库 是一个 基于 终端的 图形界面的库,有兴趣的可是尝试链接这个第三方库。

上述的 ncurses 库是在Linux 下的,如果是在windows 当中其实还有 easyx 库,在windows 当中这些库会做的更酷炫一些。还有 QT。

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

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

相关文章

什么是yum?

&#x1f388;个人主页:&#x1f388; :✨✨✨初阶牛✨✨✨ &#x1f43b;推荐专栏1: &#x1f354;&#x1f35f;&#x1f32f;C语言初阶 &#x1f43b;推荐专栏2: &#x1f354;&#x1f35f;&#x1f32f;C语言进阶 &#x1f511;个人信条: &#x1f335;知行合一 &#x1f…

浅谈安科瑞多功能仪表和网关在中国香港某项目的应用

摘要&#xff1a;本文介绍了安科瑞多功能电能表在中国香港某项目的应用。APM系列交流多功能仪表是一款专门为电力系统、工矿企业、公用事业和智能建筑用于电力监控而设计的智能电表。 Abstract&#xff1a;This article introduces the application of the IoT power meter in…

AI超级个体:ChatGPT与AIGC实战指南

目录 前言 一、ChatGPT在日常工作中的应用场景 1. 客户服务与支持 2. 内部沟通与协作 3. 创新与问题解决 二、巧用ChatGPT提升工作效率 1. 自动化工作流程 2. 信息整合与共享 3. 提高决策效率 三、巧用ChatGPT创造价值 1. 优化产品和服务 2. 提高员工满意度和留任率…

vue3中的Fragment、Teleport、Suspense新组件

Fragment组件 在Vue2中: 组件必须有一个根标签 在Vue3中: 组件可以没有根标签, 内部会将多个标签包含在一个Fragment虚拟元素中 好处: 减少标签层级, 减小内存占用 <template><div style"font-size: 14px;"><p> 组件可以没有根标签</p&g…

EtherCAT从站XML文件组成元素详解(2):状态机

0 工具准备 1.EtherCAT从站XML文件(本文使用DM3E-556) 2.ETG.2000 S (R) V1.0.71 前言 EtherCAT从站的设备描述文件ESI(EtherCAT Slave Information)是联系主站和从站的桥梁,主站可以通过xml格式的从站设备描述文件识别从站的特征信息、获取对象字典信息、进行组态等。因此…

vue3(二)-基础入门之列表循环、数组变动检测、filter模糊查询、事件修饰符

一、列表循环 of 和 in 都是一样的效果 html代码&#xff1a; <div id"app"><ul><li v-for"item of datalist">{{ item }}</li></ul><ul><li v-for"item in dataobj">{{ item }}</li></u…

k8s安装步骤

环境&#xff1a; 操作系统&#xff1a;win10 虚拟机&#xff1a;VMware linux发行版&#xff1a;CentOS7.9 CentOS镜像&#xff1a;CentOS-7-x86_64-DVD-2009 master和node节点通信的ip(master)&#xff1a; 192.168.29.164 0.检查配置 本次搭建的集群共三个节点&#xff0c;…

【开发实践】使用POI实现导出带有复杂表头的的excel文件

一、需求分析 公司业务部门需要&#xff0c;根据一些数据&#xff0c;加上表头&#xff0c;导出需要的excel表格。效果如下&#xff1a; 二、代码实现 【依赖准备】 <!-- POI --><dependency><groupId>org.apache.poi</groupId><artifactId>po…

Echarts地图registerMap使用的GeoJson数据获取

https://datav.aliyun.com/portal/school/atlas/area_selector 可以选择省&#xff0c;市&#xff0c;区。 也可以直接在地图上点击对应区域。 我的应用场景 我这里用到这个还是一个特别老的大屏项目&#xff0c;用的jq写的。显示中国地图边界区域 我们在上面的这个地区选择…

C++学习之路(九)C++ 用Qt5实现一个工具箱(增加一个JSON数据格式化功能)- 示例代码拆分讲解

上篇文章&#xff0c;我们用 Qt5 实现了在小工具箱中添加了《粘贴板记录管理》功能&#xff0c;用着还不错哈。为了继续丰富我们的工具箱&#xff0c;今天我们就再增加一个平时经常用到的功能吧&#xff0c;就是「 JSON数据格式化 」功能。下面我们就来看看如何来规划开发一个这…

【MATLAB源码-第91期】基于matlab的4QAM和4FSK在瑞利(rayleigh)信道下误码率对比仿真。

操作环境&#xff1a; MATLAB 2022a 1、算法描述 正交幅度调制&#xff08;QAM&#xff0c;Quadrature Amplitude Modulation&#xff09;是一种在两个正交载波上进行幅度调制的调制方式。这两个载波通常是相位差为90度&#xff08;π/2&#xff09;的正弦波&#xff0c;因此…

解决在SwingBench压测时出现一些问题

解决在SwingBench压测时出现一些问题 压测时断层 1.问题来由&#xff1a;在进行swingbench压测的时候会出现断断续续的情况 2.导致原因&#xff1a; 我们通过查看日志文件&#xff0c;看看是什么情况 tail -100 /u01/app/oracle/diag/rdbms/orcl/orcl/trace/alert_orcl.l…

锂电行业废水及母液除铊解决方案,除铊树脂技术

锂电池原材料和生产设备的制造、电池回收和处理等&#xff0c;产业的发展会带来铊排放问题。除了锂电池生产过 程中存在的铊污染外&#xff0c;企业的生活污水或者初期雨水也含有铊&#xff0c;因为铊是一种广泛存在于自然环境中的 元素&#xff0c;存在于饮用水、土壤和食物中…

【三维重建】摄像机标定(张正友相机标定法)

摄像机标定的目的是为了求解摄像机的内、外参数 求解投影矩阵M 通过建立特殊的场景&#xff0c;我们能过得到多对世界坐标和对应图像坐标 根据摄像机几何可知 &#xff1a; &#xff0c;M是一个3*4的矩阵&#xff0c;令 通过一对点可以得到两个方程组&#xff0c;M中一共有11个…

Linux安全之auditd审计工具使用说明

一、auditd工具简介 audited是Linux审核系统的用户空间组件。它负责将审核记录写入磁盘。查看日志是通过ausearch或aureport实用程序完成的。审核系统或加载规则的配置是使用auditctl实用程序完成的。在启动过程中&#xff0c;/etc/audit/audit.rules中的规则由auditctl读取并加…

全新爱蜗影视优码双端影视源码v9.1/影视视频APP源码+支持代理/在线支付+支持对应苹果CMS

源码简介&#xff1a; 这个是最新爱蜗影视优码双端影视源码&#xff0c;作为实用方便的影视视频APP源码&#xff0c;它不仅支持代理/在线支付&#xff0c;而且也支持对应苹果CMS。 爱蜗影视优码双端影视支持对应苹果CMS支持代理在线支付 带图文教程&#xff0c;全新美化多功能…

深入理解OS--数值编码

信息的表示和处理 寻址和字节顺序 位于0x100处&#xff0c;int类型值0x01234567在大端和小端下的存储。 字符串的存储不受字节序影响。 移位 1.对左移&#xff0c;右边统一补0 2.对右移&#xff0c;分为算术右移&#xff0c;逻辑右移 算术右移下&#xff0c;左边补原最高有效…

jsoup登录日志平台后调企业微信机器人自动发送错误日志告警

一、需求&#xff1a;错误日志Top10告警发送 二、需求分解 jsoup实现登录&#xff0c;获取到cookie和token等用户鉴权信息获取接口相应的key值调用日志平台错误日志Top榜接口&#xff0c;查询到结果集调用企业微信机器人发送消息接口加上定时任务&#xff0c;可以实现定时发送…