【Linux】进程创建/终止/等待/替换

news/2024/5/17 5:25:44/文章来源:https://blog.csdn.net/gfdxx/article/details/128046685


目录

一、子进程的创建

1、fork函数的概念

2、如何理解fork拥有两个返回值

3、fork调用失败的场景

二、进程的终止

1、main函数返回值

1.1main函数的返回值的意义

1.2将错误码转化为错误信息

1.3查看进程的退出码

2、进程退出的情况

1、进程的正常退出与异常退出

2、库函数exit

3、系统调用_exit

三、进程等待

1、进程等待的必要性

2、进程等待的方法

2.1系统调用wait

2.2系统调用waitpid

3、status值的意义

4、图解父进程等待子进程的方式

5、阻塞/非阻塞式等待

四、进程程序替换

1、进程程序替换的概念

2、进程程序替换的原理

3、exec*()系列函数的返回值

4、main函数在磁盘中被加载到内存的原理

五、写一个shell


一、子进程的创建

1、fork函数的概念

在linux中fork函数是非常重要的函数,它从已存在进程中创建一个新进程。新进程为子进程(子进程的PID是0),而原进程为父进程。

#include <unistd.h>
pid_t fork(void);

返回值:fork创建子进程成功后,会给父进程返回子进程的PID,给子进程返回0,失败则返回-1。

为什么要让父进程拿到子进程的PID?因为子进程变为僵尸状态后,需要父进程读取子进程的退出信息并回收资源。

操作系统将会给创建成功的子进程:

1、给子进程分配新的内存块和内核数据结构(PCB、进程地址空间、页表等,并构建对应的映射关系);

2、将父进程的部分数据结构内容拷贝至父进程;

3、把子进程添加到系统进程列表中;

4、fork返回,调度器开始调度。

2、如何理解fork拥有两个返回值

在fork函数return之前,就已经有了父子两个进程,给父进程返回子进程的PID,给子进程返回0,失败则返回-1。

利用这个特性,我们可以用变量接收返回值,根据fork返回值不同让父子进程执行不同的代码。

#include <stdio.h>
#include <unistd.h>
int main()
{pid_t id=fork();if(id==0){printf("子进程:pid=%d,ppid=%d | grobal_val=%d,&grobal_val=%p\n",getpid(),getppid(),grobal_val,&grobal_val);}else if(id>0){printf("父进程:pid=%d,ppid=%d | grobal_val=%d,&grobal_val=%p\n",getpid(),getppid(),grobal_val,&grobal_val);sleep(1);}else {printf("fork error\n");return 1;}return 0;
}

pid_t id=fork()这句代码父子进程谁先返回不确定。谁先返回,谁就在虚拟内存中写入id的值,后返回的进程由于进程的独立性将会发生写时拷贝。所以,我们可以看到父子进程的id变量的虚拟地址是一样的,但是内容却不一样。

3、fork调用失败的场景

系统中的进程数达到了最大限制。

二、进程的终止

1、main函数返回值

1.1main函数的返回值的意义

int main()
{return 0;
}

return 后面的值代表进程退出的时候,对应的退出码。标定进程执行的结果是否正确。

如果不关心一个进程的退出码,可以直接return 0;如果要关心进程的退出码,则要返回特定的数字以表明进程不同的错误。

1.2将错误码转化为错误信息

函数原型:

#include <string.h>
char* strerror(int errnum);

遍历打印错误码: 

#include <stdio.h>
#include <unistd.h>
#include <string.h>
int main()
{for(int i=0;i<150;++i){printf("num[%d]:%s\n",i,strerror(i));}return 0;
}

可以看到不同的错误码对应的出错误信息。

1.3查看进程的退出码

echo $?

$?会记录最近一个进程在命令行中执行完毕时的退出码,即main函数的返回值。

2、进程退出的情况

1、进程的正常退出与异常退出

正常终止(父进程获取退出码):

1、程序执行完毕,main函数返回0

2、程序执行完毕,但是返回值不为0,例如调用exit()和_exit()或return !0。

异常终止(父进程获取退出信号):

ctrl+c或除0错误等导致程序被信号终止。

2、库函数exit

函数原型:

#include <stdlib.h>
void exit(int status);

库函数exit在进程退出后,会主动刷新缓冲区。

3、系统调用_exit

函数原型:

#include <unistd.h>
void _exit(int status);

系统调用_exit在进程退出后,并不会主动刷新缓冲区。

三、进程等待

1、进程等待的必要性

子进程退出后会进入僵尸状态,父进程通过进程等待的方式,获取子进程的退出信息,回收子进程资源,让子进程结束僵尸状态。当然,在子进程没有退出时,父进程只能阻塞等待子进程变成僵尸状态。

2、进程等待的方法

头文件:

#include <sys/types.h>
#include <sys/wait.h>

2.1系统调用wait

函数原型:

pid_t wait(int* status);

返回值:等待成功被等待进程的pid,失败返回-1。

参数:status输出型参数,获取子进程退出码和退出状态,不关心则可以设置成为NULL。

通过wait让父进程获取子进程的PID。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t id=fork();if(id==0){//子进程int cnt=3;while(cnt--){printf("子进程:%d  父进程:%d  %d\n",getpid(),getppid(),cnt);sleep(1);}exit(0);//子进程退出}sleep(5);pid_t ret =wait(NULL);if(id>0) {//父进程printf("等待成功:%d\n",ret);}return 0;
}

2.2系统调用waitpid

函数原型:

pid_t waitpid(pid_t pid,int* status,int options);

返回值:当正常返回的时候waitpid返回收集到的子进程的进程PID;如果设置了选项WNOHANG,而调用waitpid发现没有已退出的子进程可收集,则返回0;如果调用中出错,则返回-1,这时errno会被设置成相应的值以指示错误所在;

参数:

PID:

PID=-1,等待任一个子进程。与wait等效。PID>0,等待进程为PID的子进程。

status:

WIFEXITED(status): 若为正常终止子进程返回的状态,则为真。(查看进程是否是正常退出)

WEXITSTATUS(status): 若WIFEXITED非零,提取子进程退出码。(查看进程的退出码)

//WIFEXITED和WEXITSTATUS是两个宏
if(WIFEXITED(status))//判断子进程是否正常退出(判断退出信号)
{printf("exit code:%d\n",WEXITSTATUS(status));//判断子进程运行结果是否正常(判断退出码)
}
else{//something to do 
}

options:

WNOHANG: 若PID指定的子进程没有结束,则waitpid()函数返回0,不予以等待。若正常结束,则返回该子进程的PID。

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t id=fork();if(id==0){//子进程int cnt=3;while(cnt--){printf("子进程:%d  父进程:%d  %d\n",getpid(),getppid(),cnt);sleep(1);}exit(10);//子进程退出}int status=0;pid_t ret =waitpid(id,&status,0);if(id>0) {//父进程printf("等待成功:%d,ret=%d\n",ret,status);}sleep(5);return 0;
}

通过修改子进程的退出码,可以打印出不同的status值,因为status用于存储子进程的退出码和退出信号。

3、status值的意义

wait和waitpid,都有一个status参数,该参数是一个输出型参数,由操作系统填充。

如果传递NULL,表示不关心子进程的退出状态信息。

否则,操作系统会根据该参数,将子进程的退出信息反馈给父进程。

status不能简单的当作整型,可以当作位图来看待。(只研究status低16比特位):

在进程退出时,终止信号是评判一个进程是否正常退出;退出状态是评判一个进程运行的结果是否正确。

1、使用kill -l来查看进程终止的信号。

2、根据退出码确定进程的退出状态。

以下代码手动调用waitpid模拟父进程等待子进程的过程:

#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{pid_t id=fork();if(id==0){//子进程int cnt=3;while(cnt--){printf("子进程:%d  父进程:%d  %d\n",getpid(),getppid(),cnt);sleep(1);}exit(10);//子进程退出}int status=0;pid_t ret =waitpid(id,&status,0);if(id>0) {//父进程printf("等待成功,返回值子进程的PID:%d,终止信号:%d,子进程退出码:%d\n",ret,(status&0X7F),((status>>8)&0XFF));}sleep(5);return 0;
}

4、图解父进程等待子进程的方式

1、调用wait/waitpid后,父进程只能阻塞等待子进程变成僵尸状态。

2、子进程退出后变成僵尸状态,task_struct中的代码和数据会被释放掉,并把自己的退出信号、退出码写入到自己的task_struct中;

3、wait/waitpid是系统调用,操作系统有资格也有能力去读取子进程的task_struct。

4、父进程通过进程等待的方式,获取子进程的退出信息,回收子进程资源,让子进程结束僵尸状态。

5、阻塞/非阻塞式等待

阻塞式等待:当父进程调用wait/waitpid(第三个参数为0)等待子进程,如果子进程暂未退出,父进程会被阻塞,暂停运行,如果父进程刚好没事干,可以选择使用阻塞等待。

非阻塞式等待:当父进程调用waitpid(第三个参数为WNOHANG)等待子进程,如果父进程检测到子进程未退出,父进程并不会原地等待,而是继续执行自己的代码。如果使用while循环,便能达到轮询的效果。

非阻塞式等待不会占用父进程的精力,父进程可以在轮询的过程中做其他事情:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>#define NUM 5
typedef void (*func_t)(); //函数指针
func_t handlerTask[NUM];//函数指针数组//任务
void task1()
{printf("handler task1\n");
}
void task2()
{printf("handler task2\n");
}
void task3()
{printf("handler task3\n");
}void loadTask()
{memset(handlerTask, 0, sizeof(handlerTask));//将函数指针数组初始化为0handlerTask[0] = task1;//函数指针数组handlerTask[0]存放task1的地址handlerTask[1] = task2;handlerTask[2] = task3;
}
int main()
{pid_t id = fork();assert(id != -1);if(id == 0){//childint cnt = 10;while(cnt){printf("child running, pid: %d, ppid: %d, cnt: %d\n", getpid(), getppid(), cnt--);sleep(1);}exit(10);}loadTask();//加载任务// parentint status = 0;while(1)//父进程对子进程状态轮询{pid_t ret = waitpid(id, &status, WNOHANG);//第三个参数为0表示阻塞式等待,为WNOHANG表示非阻塞式等待if(ret == 0)//等于0代表没有被等待的进程暂未退出{printf("wait done, but child is running...., parent running other things\n");for(int i = 0; handlerTask[i] != NULL; i++)//遍历到NULL,即0停止{handlerTask[i](); //采用回调的方式,执行我们想让父进程在空闲的时候做的事情}}else if(ret > 0)//waitpid调用成功,并且子进程退出,返回值为被等待子进程的pid{printf("wait success, exit code: %d, sig: %d\n", (status>>8)&0xFF, status & 0x7F);break;}else//等于-1表示等待失败,waitpid中的第一个参数值传错会导致调用失败{printf("waitpid call failed\n");break;}sleep(1);}return 0;
}

四、进程程序替换

1、进程程序替换的概念

将磁盘中指定的程序加载到内存中,让指定的进程进行执行。不论是何种后端语言写的程序,exec*函数都可以调用。

替换函数:

#include <unistd.h>`
//execve的封装
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ...,char *const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[],char *const envp[]);
//系统调用
int execve(const char *filename, char *const argv[],char *const envp[]);

l(list) : 表示参数采用列表 ;

p(path) : 带p,不用传入地址,传入可执行程序的名字即可,它会自动去环境变量PATH中寻找该可执行程序的地址;

v(vector) :执行参数放入数组中,统一传递;

e(env) : 可以传入自己写的环境变量。

1、通过execl函数调用ls命令:

#include <stdio.h>
#include <unistd.h>
int main()
{printf("process is running·····\n");execl("/usr/bin/ls"/*要执行的程序*/,"ls","--color=auto","-a","-l",NULL/*如何执行*/);//一定要用NULL结尾printf("process is down·····\n");//这句话并不会被打印,因为后续代码和数据已经被execl函数替换了return 0;
}

如果调用execl函数,参数传错导致函数调用失败,后续代码将不会被覆盖。

2、通过execle函数调用外部程序并使用自定义环境变量:

#include <stdio.h>
#include <unistd.h>
int main()
{printf("process is running··\n");//使用自定义的环境变量//char* const _env[]={(char*)"MYENV=12345",NULL};//execle("./mybin","mybin",NULL,_env);//使用系统的环境变量//extern char** environ;//execle("./mybin","mybin",NULL,environ);//系统环境变量不传,进程也能获取//使用putenv将自己的环境变量导入到environ指向的环境变量表中extern char** environ;char* const _env[]={(char*)"MYENV=12345",NULL};putenv((char*)"MYENV=654321");execle("./mybin","mybin",NULL,environ);printf("process is running··\n");return 0;
}

2、进程程序替换的原理

程序替换的本质:用磁盘指定位置上的程序的代码和数据,覆盖进程自身的代码和数据,达到让进程执行指定程序的目的。

3、exec*()系列函数的返回值

exec()函数仅在发生错误时返回。返回值为-1,设置errno以指示错误。

可以看到exec*系列函数调用成功后并没有返回值,因为exec*一旦被调用成功,后续代码将被覆盖,根本没机会用到返回值。

4、main函数在磁盘中被加载到内存的原理

五、写一个shell

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <assert.h>
#include <string.h>
#define NUM 100
#define OPT_NUM 20
char LineCommend[NUM];
char* myargv[OPT_NUM];//指针数组,用于记录切割出来的字符串
int  lastCode = 0;
int  lastSig = 0;
int main()
{   while(1){printf("用户名@主机名 当前路径:");fflush(stdout);//获取输入内容char* s=fgets(LineCommend,sizeof(LineCommend),stdin);assert(s!=NULL);//清除最后一个\nLineCommend[strlen(LineCommend)-1]=0;//字符串切割myargv[0]=strtok(s," ");int i=1;if(myargv[0]!=NULL&&strcmp(myargv[0],"ls")==0)//判断myargv[0]是否切割正常,穷举指令,为指令添加选项{myargv[i++]=(char*)"--color=auto";}while(myargv[i++]=strtok(NULL," "));//如果切割完毕,strtok会返回NULL,刚好myargv[end]需要等于NULLif(myargv[0]!=NULL&&strcmp(myargv[0],"cd")==0)//解决cd命令返回的是子进程的上级目录问题{if(myargv[1]!=NULL){chdir(myargv[1]);//使用chdir()改变父进程的当前路径continue;//如果改变路径,就continue跳出本轮循环,后续子进程可以不用创建}}if(myargv[0] != NULL && myargv[1] != NULL && strcmp(myargv[0], "echo") == 0){if(strcmp(myargv[1], "$?") == 0){printf("%d, %d\n", lastCode, lastSig);}else{printf("%s\n", myargv[1]);}continue;}//测试是否成功
#ifdef DEBUG for(i=0;myargv[i];++i){printf("myargv[%d]:%s\n",i,myargv[i]);}
#endif//执行命令pid_t id=fork();//创建子进程,让子进程去执行被切割出来的指令assert(id!=-1);if(id==0){execvp(myargv[0],myargv);exit(1);}int status = 0;pid_t ret = waitpid(id, &status, 0);assert(ret > 0);(void)ret;lastCode = ((status>>8) & 0xFF);lastSig = (status & 0x7F);}return 0;
}

运行该程序,可以实现shell的效果,如果33行-40行逻辑如果不写,使用cd ..指令回到上级目录时,发现我们在原地TP。

原因:因为这个"模拟shell"程序会使用fork()创建子进程执行指令,当使用cd ..时,回到的是子进程的上级目录,不会改变父进程的当前工作目录。可以额外判断一下cd命令,如果是cd命令,使用chdir改变父进程的工作目录,continue跳出本轮循环。(无需创建后续子进程)

像这种不需要子进程来执行,而是让shell来执行的命令叫做内建/内置命令这也解释了为什么echo能打印本地环境变量,因为echo也是一个内建命令,由shell执行,shell当然能打印本地环境变量。

查看进程当前目录:

可以使用chdir()更改进程的当前目录。

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

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

相关文章

Nodejs中包的介绍及npm安装依赖包的多种方法

文章目录1 包的介绍1.1 什么是包1.2 包的来源1.3 为什么需要包1.4 从哪里下载包1.5 如何下载包2 npm2.1 npm安装依赖包2.2 装包后多了哪些文件2.3 安装指定版本的包1 包的介绍 1.1 什么是包 Nodejs中的第三方模块又叫做包 就像电脑和计算机指的是相同的东西&#xff0c;第三…

推特自动发帖,全天占据核心流量

利用热门趋势和Hashtags标签 Twitter有一个热门趋势&#xff0c;跟微博热搜是差不多的&#xff0c;卖家可以多关注一下热门趋势&#xff0c;看看有没有和产品相关的内容。在帖子中加入趋势性和热门的标签&#xff0c;是一种非常好的营销方式。 这一方面能够增加推文的热度&am…

android源码-ContentProvider实现原理分析

前言&#xff1a; 最初的目的是想研究下ContentProvider产生ANR原因的&#xff0c;但是如果要讲ANR的原因&#xff0c;那么必须要了解ContentProvider的完整实现原理&#xff0c;所以本篇就先讲一下ContentProvider的实现原理&#xff0c;下一篇再去讲ANR的原因。 本篇主要会讲…

Baklib知识库|为什么知识共享工具对减少内部知识缺口至关重要

你的企业是否存在知识缺口&#xff1f; 知识缺口——没有对关键知识进行研究和记录&#xff0c;以有效地传播信息&#xff0c;并教育企业内外的用户——可能是寻求生产率最大化并最终实现利润增长的公司的一个关键缺陷。知识&#xff08;或数据、关键信息等&#xff09;是你的…

网络通信基本原理

通讯的必要条件 主机之间需要有传输介质。光纤、蓝牙、wify主机上必须有网卡设备。把二进制信息转为高低电压的过程就是数据的调制过程。把电信号转为二进制信息的过程为解调制。主机之间需要协商网络速率。 网路的通讯方式 日常生活中&#xff0c;我们通讯的方式不可能只有…

现代密码学导论-14-基于伪随机发生器的EAV安全

目录 3.3.3 基于伪随机发生器的EAV安全 用伪随机发生器进行加密的图示 CONSTRUCTION 3.17 一种基于伪随机发生器的私钥加密方案 THEOREM 3.16 基于伪随机发生器的私钥加密方案的EAV安全 THEOREM 3.16 的证明 最后来理一下 3.3.3 基于伪随机发生器的EAV安全 用伪随机发生…

sql server如何卸载干净?来看这里

一、如何卸载干净 1.关闭服务 快捷键&#xff1a;windows R&#xff0c;在命令行输入&#xff1a; services.msc&#xff0c;把有关SQL都关闭 &#xff0c;下图所示&#xff1a; 2.到控制面板&#xff0c;卸载 sql server 3.删除磁盘里的文件 我的在c盘里&#xff0c;看各位…

Linux Command htpasswd 创建密码文件

文章目录Linux Command htpasswd 创建密码文件1. 简介2. 安装3. 语法4. 选项5. 示例6. 其他Linux Command htpasswd 创建密码文件 1. 简介 htpasswd是Apache的Web服务器内置的工具,用于创建和更新储存用户名和用户基本认证的密码文件。 2. 安装 centos 7、 redhat&#xff…

详解设计模式:简单工厂模式

简单工厂模式&#xff08;Smiple Factory Pattern&#xff09;&#xff1a;定义一个工厂类&#xff0c;他可以根据参数的不同返回不同类的实例&#xff0c;被创建的实例通常都具有共同的父类&#xff0c;简单工厂模式也被称为静态工厂模式。 &#xff5e; 本篇内容包括&#xf…

SpringBoot整合Mybatis方式2:使用注解方式整合Mybatis

SpringBoot整合Mybatis简介SpringBoot整合Mybatis方式2&#xff1a;使用注解方式整合Mybatis1.先用idea创建一个添加mybatis需要的相关依赖的工程。2.准备数据库和表3.创建表映射类4.创建mapper代理接口5.创建Service层和Service的实现层6.创建控制层&#xff08;也就是web层&a…

五种方法帮你解决电脑内存占用大的问题

有用户反映自己的电脑什么都没开&#xff0c;但是运行内存显示占用90%以上&#xff0c;这是什么情况&#xff1f;运行内存占用大&#xff0c;直接影响了用户的使用体验&#xff0c;下面小编就给大家分享五个解决电脑内存占用大的办法吧。 方法一&#xff1a; 1、右键【我的电脑…

Spring Boot Admin2 自定义异常监控

其他相关文章&#xff1a; Spring Boot Admin 参考指南SpringBoot Admin服务离线、不显示健康信息的问题Spring Boot Admin2 EnableAdminServer的加载Spring Boot Admin2 AdminServerAutoConfiguration详解Spring Boot Admin2 实例状态监控详解Spring Boot Admin2 自定义JVM监控…

GEO振弦式钢筋计的组装

&#xff08;1&#xff09;按钢筋直径选配相应的钢筋计&#xff0c;如果规格不符合&#xff0c;应选择尽量接近于结构钢筋直径 的钢筋计&#xff0c;例如&#xff1a;钢筋直径为 35mm&#xff0c;可使用 NZR-36 或 NZR-32 的钢筋计&#xff0c;此时仪器的最小 读数应进行修…

MapReduce

4.1 MapReduce概述 2003年和2004年&#xff0c;Google公司在国际会议上分别发表了两篇关于Google分布式文件系统和MapReduce的论文&#xff0c;公布了Google的GFS和MapReduce的基本原理和主要设计思想。 4.1.1 MapReduce定义 MapReduce是一个分布式运算程序的编程框架&#…

【无百度智能云与实体经济“双向奔赴”: 一场1+1>2的双赢 标题】

实体经济&#xff0c;已经成为检验科技企业潜力的试金石。 在最近的财报季中&#xff0c;各家大厂的财报里“实体经济”都是关键字眼&#xff0c;已经成为各家心照不宣的共同目的地。 当然&#xff0c;条条大路通罗马。每一家的战略思路和打法都不一样。11月22日&#xff0c;…

DOTA-PEG-麦芽糖 maltose-DOTA 麦芽糖-四氮杂环十二烷四乙酸

DOTA-PEG-麦芽糖 maltose-DOTA 麦芽糖-四氮杂环十二烷四乙酸 PEG接枝修饰麦芽糖&#xff0c;麦芽糖-聚乙二醇-四氮杂环十二烷四乙酸&#xff0c;DOTA-PEG-麦芽糖 中文名称&#xff1a;麦芽糖-四氮杂环十二烷四乙酸 英文名称&#xff1a;maltose-DOTA 别称&#xff1a;DOTA修…

HCIA 访问控制列表ACL

一、前言 ACL又称访问控制列表&#xff0c;其实这个东西在很多地方都有用&#xff0c;可能名字不太一样但原理和功能都差不太多&#xff0c;比如服务器、防火墙&#xff0c;都有类似的东西&#xff0c;功能其实也就是“过滤”掉不想收到的数据包。为什么不想收到一些数据包呢&…

The Seven Tools of Causal Inference with Reflections on Machine Learning 文章解读

目录 THE THREE LAYER CAUSAL HIERARCHY. 4 THE SEVEN TOOLS OF CAUSAL INFERENCE (OR WHAT YOU CAN DO WITH A CAUSAL MODEL THAT YOU COULD NOT DO WITHOUT?) 7 Tool 1: Encoding Causal Assumptions – Transparency and Testability. 10 Tool 2: Do-calculus and the …

深入浅出基于HLS流媒体协议视频加密的解决方案

一套简单的基于HLS流媒体协议&#xff0c;使用video.js NodeJS FFmpeg等相关技术实现的m3u8tsaes128视频加密及播放的解决方案示例。 项目简介 起初是为了将工作中已有的基于Flash的视频播放器替换为不依赖Flash的HTML5视频播放器&#xff0c;主要使用了现有的video.js开源播…

【学习笔记25】JavaScript字符串的基本认识

JavaScript字符串的基本认识一、严格模式二、字符串1、字面量2、构造函数3、包装类型三、字符集&#xff08;了解&#xff09;1、ASCII&#xff1a;128个2、GBK国标码&#xff1a;前128位ASCII&#xff0c;从129开始为汉字3、unicode(万国码)四、字符串的length与下标一、严格模…