Linux系统编程——进程控制

news2024/12/23 9:37:16

目录

一,进程创建

1.1 fork回顾

1.2 写时拷贝

1.3 fork用处

1.4 fork调用失败原因

二,进程退出

2.1 进程退出场景

2.2 mainCRTStartup调用

2.3 进程退出码

2.3.1 main函数返回值

2.3.2 strerror 

​编辑 2.3.3 命令的退出码

2.4 进程正常退出

2.5 进程异常退出

三,进程等待

3.1 为什么要有进程等待?

3.2 wait函数和waitpid函数的使用

 3.3 wait函数和waitpid的status参数

3.4 多进程阻塞等待

3.5 非阻塞等待

四,进程程序替换

4.1 什么是进程程序替换?为什么要有这个?

4.2 execl函数

4.3 其它exec函数

​编辑 

4.4 execve

五:一些问题解答

5.1 wait和waitpid如何拿到进程的退出信息的呢?为什么不直接用全局变量拿到退出码?

5.2 僵尸进程中曾经new和malloc的空间会释放码?


一,进程创建

1.1 fork回顾

fork的详细介绍请见上篇博客第四节——这里只做简单回顾Linux系统编程 —— 进程概念,环境变量,虚拟地址空间总结(收藏向)-CSDN博客

fork()函数可以创建子进程,由于fork也是个函数,所以也有返回值,会对父进程返回子进程的pid,对子进程返回0。具体用法如下:

#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
int main()
{
    printf("我是主进程");
    pid_t id =fork();
    if(id<0)
    {
        printf("创建子进程失败\n");
        return 1;
    }
    else if(id==0)
    {
        printf("我是子进程:pid:%d,ppid:%d\n",getpid(),getppid());
    }
    else 
    {
        printf("我是父进程:pid:%d,ppid:%d\n",getpid(),getppid());      
    }
    return 0;
}

问题:fork创建子进程时,OS做了什么?

解答:创建子进程,系统里就多了一个进程,所以先创建进程的PCB,地址空间和页表,将程序在磁盘上的地址加载到内存中,然后把程序的代码和数据从磁盘加载到物理内存中,然后把PCB放到CPU的运行队列里等待被调度,当该进程开始调度时,就通过虚拟地址的映射找到在物理内存的相关代码,然后就按照顺序语句从上往下在进程内部执行代码

进程 = 内核数据结构 + 进程代码和数据 (前者OS维护,后者一般从磁盘上来,就是C/C++程序加载之后的结果)

1.2 写时拷贝

进程具有独立性,所以创建子进程时分配给它的内核结构是它独有的,理论上进程也要有自己的代码和数据。可是一般而言,子进程只是随着父进程的加载而创建出来的,也就是说子进程没有属于自己的代码和数据,它只能用父进程的代码和数据,但这又和进程的独立性相违背,为啥?

①代码:对进程来说,代码都是已经加载完成的,只能读取不能写入,所以代码数据父子共享没问题

②数据:由于数据可能会被修改,所以必须分离,但是不能直接拷贝分离,因为可能会拷贝子进程根本用不到的数据空间,而且子进程也可能只读数据不做修改,这种情况再进行拷贝就会造成时间和空间的浪费。所以采用写时拷贝的技术,写时拷贝请参照上篇博客第十节:

Linux系统编程 —— 进程概念,环境变量,虚拟地址空间总结(收藏向)-CSDN博客

 如下代码:

#include<stdio.h>
int main()
{
  const char *str1 = "aaa";
  const char *str2 = "aaa";
  printf("%p\n%p\n",str1,str2); 
  return 0;
}

 

 第一个指针创建常量字符,第二个指针也指向这个常量字符时,就不再额外开空间了,所以连编译器编译时都知道节省空间来优化,OS没有理由不优化,所以创建子进程的时候OS采用了写时拷贝的技术,就是字面意思:“你要数据的候再给你拷贝”。

写时拷贝使得父子进程得以彻底分离,保证了进程的独立性,同时也是高效使用内存的一种表现

1.3 fork用处

①一个进程希望复制自己,使执行父进程代码的同时子进程执行不同的代码,例如父进程等待客户端请求数据,子进程处理请求数据

②一个进程要执行一个不同程序,例如子进程创建后来后,调用exec函数,这个后面的进程程序替换再讲

1.4 fork调用失败原因

系统进程过多,或者是用户的进程超过了限制

如下代码,由于下列代码执行后进程满了fork报错,系统可能会出问题,为了各位主机的安全考虑 就不贴代码啦

可以看见创建将近800个进程后再创建就失败了 

二,进程退出

2.1 进程退出场景

进程退出只能有三种情况:

①代码运行完成,结果正确

②代码运行完成,结果不对

③代码未运行完成,异常退出(OS通过发送信号的方式强制终止进程)

2.2 mainCRTStartup调用

为什么main函数也有返回值呢?有人会说main函数也是函数所以有返回值,那么既然main是函数,那么是谁调用的main函数呢?下面是VS2022编译器的main函数层层调用的示例图:(下面代码需要在release调试环境下用逐语句依次执行才可显示)

所以其实main函数最开始是被一个叫做mainCRTStartup的函数调用的,而这个函数又是通过加载器被操作系统所调用的,也就是说main函数是间接被操作系统调用的。

main函数只是用户级别的代码的入口,而程序的入口还是操作系统 

2.3 进程退出码

2.3.1 main函数返回值

通过2.2,我们知道main函数既然是被操作系统调用的,那么main函数作为被调用者就应该给调用者返回退出信息,而这个退出信息就是以退出码的形式作为main函数的返回值返回,一般以返回0代表代码执行完毕,非0代表代码执行过程中出现错误,但是我们前面写的程序都是小程序,代码数不多,所以一般以0返回。

main函数的返回值就是进程的退出码,我们可以用" echo $? "来查看最后一次进程退出时的退出码

问题:为什么0代表执行成功,非0代表错误?

解答: 代码执行完毕只有两种情况,成功和错误,成功就是我们想要的只有一种情况,但是错误往往是有很多的,这个大家都懂,所以我们就用这些非0的数字代表代码执行错误的原因

2.3.2 strerror 

 但是退出码是方便计算机返回设定的,而我们并不清楚像“ 1234 ”这样的退出码到底是什么意思,所以我们需要一个能将错误码转化成字符串显示给用户的方案 —— ”strerror“,打印退出码代表的字符信息,如下代码:

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

 2.3.3 命令的退出码

Linux中的各种命令也是C语言写的程序,所以也会有退出码,我们使用命令后照样也可以通过“ echo $? ”查看退出码

2.4 进程正常退出

return

main函数使用的return退出就是我们最常用的方法来退出进程

exit

exit函数也是常用的退出进程的方法,但是exit在进程退出前多做了一系列工作:

1,执行用户定义的atexit或on_exit定义的清理函数

2,关闭所有打开的流,所有缓存数据均被写入

3,调用_exit系统调用终止进程

#include<stdio.h>
#include<stdlib.h>

void Hello()
{
  printf("hello world"); //未加\n,不会刷新缓冲区
  exit(1);
}

int main()
{
  Hello();
  return 0;
}

 

_exit

_exit是系统调用的进程退出函数,但是我们一般不用_exit来退出进程,因为_exit推出前不会有exit中的推出前工作,它只是退出,比如上面的代码如果把exit换成_exit就不会刷新缓冲区了,hello world不会打印

而这三个函数的关系呢,就是下面这个图了:

_exit就是进程直接退出,什么也不干,exit就是在推出前做了些善后工作再退出,然后return,在C语言语法实现中就是调用的exit() 

2.5 进程异常退出

前面说过:所有的进程异常终止都是操作系统干的,具体有下面两种方式:

①操作系统向进程发送特定信号导致进程异常退出,例如kill -9和Ctrl+C

②代码错误导致进程运行时异常退出,例如野指针,越界访问和除0等(其实代码的逻辑错误异常退出本质也是操作系统检测到了代码的非法操作然后向进程发送信号的方式终止进程的)

三,进程等待

3.1 为什么要有进程等待?

①子进程退出父进程如果不读取子进程的退出信息,子进程就会变成僵尸进程,造成内存泄漏

②子进程被创建出来是要它去做事的,它的事做得咋样父进程需要关心,也就是进程退出的三个结果

③然后就是子进程把结果返回父进程了,那么父进程咋收到子进程的退出信息呢?那么包括回收子进程申请的相关内存资源,还有父进程获得子进程退出结果都需要进程等待来完成

3.2 wait函数和waitpid函数的使用

如下图,是man文档的2号文档对wait接口的说明:

wait函数

如下代码:

#include<stdio.h>
#include<unistd.h>
#include<string.h>
#include<stdlib.h>
#include<sys/types.h>
#include<sys/wait.h>
int main()
{
    pid_t id = fork();
    if(id<0)
    {
        perror("fork");
        exit(1);//标识进程运行完毕结果不对
    }
    else if(id==0)
    {
        //子进程
        int cnt =3;
        while(cnt)
        {
            printf("cnt:%d,我是子进程,pid:%d,ppid:%d\n",cnt,getpid(),getppid());
            sleep(1);
            cnt--;
        }
        exit(0);
    }
    else
    {
        //父进程
        printf("我是父进程,pid:%d,ppid:%d\n",getpid(),getppid());
	    sleep(6); //子进程跑3秒,父进程等6秒,所以可以看到子进程有3秒的Z状态,然后wait后Z状态就没了
        pid_t ret = wait(NULL); //阻塞式等待,简单来说就是卡在这,等子进程完成
        //pid_t ret = waitpid(id, NULL, 0); //waitpid的效果和wait一样,wiatpid的第一个参数表示要等待的进程的pid,第三个参数为0表示阻塞等待
        if(ret > 0)
        {
          printf("等待子进程成功,ret:%d\n", ret);
        }
    }
}

可以看到完美地消除了Z进程,有效解决了僵尸进程问题 

 3.3 wait函数和waitpid的status参数

那么只解决了僵尸进程问题还不够,因为父进程还需要知道子进程的退出码滴,当然也是通过wait和waitpid函数来获取,这时候就要着重讲讲它们的参数了

该参数是一个输出型参数,是一个指针,为NULL时不会获取到子进程退出码,不为NULL时会传入子进程的退出码。参数这里是一个指针,那么指针指向的是一个16比特位的整型。但是status不能当作一个简单的整型来对待,一共16个比特位,而操作系统对这16个比特位做了划分,如下图:

 在status一个int的16个比特位中高8位代表进程的退出状态也就是退出码,后7位表示终止信号

进程最终的结果有两种,一种是正常退出,一种是被信号所杀,而两种退出方式对status做的修改也不同

先看下正常退出对status做的修改:

 信号所杀做的修改:

 所以获取退出码和退出信号时,我们需要做一些额外的处理,如下:

int status;
wait(&status);
waitpid(pid, &status, 0);

exitCode = (status >> 8) & 0xFF;  //退出码
exitSignal = status & 0x7F;       //退出信号

//当然上面的方式用起来肯定是比较别扭的而且不美观,所以系统中提供了两个宏来方便用户操作
IfexitSignal = WIFEXITED(status); //返回值为bool类型,用于查看进程是否正常退出,检查是否收到终止信号
exitCode = WEXITSTATUS(status);   //获取进程退出码

 注意,凡是在C语言中,像这种全大写的特殊字符,很大可能就是宏定义

3.4 多进程阻塞等待

上面的都是父进程创建一个进程,然后单独等待一个子进程退出的场景,但是我们也可以同时创建多个子进程,然后让父进程依次等待子进程退出,如下代码:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
int main()
{
	pid_t ids[10]; //该数组存储子进程的pid
    int i = 0;
	for (i = 0; i < 10; i++)
    {
	   pid_t id = fork(); //创建10个子进程
	   if (id == 0)
       {
		  printf("child[%d] created success pid:%d, \n",i, getpid());
		  sleep(3);
		  exit(i); //使每个子进程的退出码设置为创建它的编号
	   }
	   ids[i] = id; //这一条是父进程执行
	}
	for (i = 0; i < 10; i++)
    {
	  	int status = 0;
	    pid_t ret = waitpid(ids[i], &status, 0);
	    if (ret >= 0)
        {
		    printf("wiat child success..PID:%d\n", ids[i]);
		    if (WIFEXITED(status))
            {
			   //正常退出
			   printf("exit code: %d\n", WEXITSTATUS(status));
		    }
		    else
            {
			   //信号终止
	  	       printf("signal kill: %d\n", status & 0x7F);
		    }
		}
	}
	return 0;
}

 

3.5 非阻塞等待

前面说到过waitpid的第三个参数option,为0时就是代表阻塞等待,那么不为0的时候呢?

阻塞等待:一般都是在内核中阻塞,等待被唤醒 --> 伴随被唤醒 --> scanf,cin --> 必定封装系统调用

非阻塞等待:我们父进程通过调用waitpid来进行等待,如果子进程没有退出,那么waitpid立即返回,waipid的伪代码实现如下:

waitpid(chile_id, status, flag);
if(status == 进程已经退出) {
    return child_pid;
}
else if(status == 进程还没退出) {
    if(flag == 0) {} //如果falg为0,那么就阻塞
    else if(flag == WNOHANG) return 0; //直接返回,不阻塞进程
    return 0;
}
else {
    //出错了或者其他原因
    return -1;
}

 当waitpid第三个参数为WNOHANG时,就代表父进程非阻塞等待,这个其实也是宏定义,我们可以查看,如下图:

所以我们可以用这个特性, 让子进程忙的时候父进程做点其它的事,但是又能让父进程获取到子进程的退出信息,如下代码:

#include<iostream>
#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>
#include<sys/types.h>
#include<vector>

typedef void (*handler_t) (); //函数指针类型
std::vector<handler_t> handlers ; //函数指针数组

void fun_one() { printf("这是一个临时任务1\n"); }
void fun_two() { printf("这是一个临时任务2\n"); }
//设置对应的方法回调
//以后想让父进程闲的时候执行其他方法时,只要向Load里面注册,就可以让父进程执行对应的方法
void Load()
{
  handlers.push_back(fun_one);
  handlers.push_back(fun_two);
}

int main()
{
  pid_t id = fork();
  if(id==0)
  {
    //子进程
    int cnt = 5;
    while(cnt)
    {
      printf("我是子进程:%d\n",cnt--);
      sleep(1);
    }
    exit(11);
  }
  else
  {
    int quit=0;
    while(!quit)
    {
      int status =0;
      pid_t res = waitpid(-1,&status,WNOHANG); //非阻塞等待
      if(res>0)
      {
        //等待成功,子进程退出
        printf("等待子进程退出成功,退出码:%d\n",WEXITSTATUS(status));
        quit = 1;
      }
      else if(res==0)
      {
        //等待成功,但子进程未退出
        printf("子进程还在运行还未退出,父进程再等一等或者做其他事情\n");
        if(handlers.empty()) Load();
        for(auto e : handlers)
        {
          //执行处理其他任务
          e();
        }
      }
      else{
        //等待失败
        printf("wait失败");
        quit=1;
      }
      sleep(1);
    }
  }
}

四,进程程序替换

4.1 什么是进程程序替换?为什么要有这个?

fork之后,父子进程各自执行代码的一部分,代码数据只读,父子进程共享;变量数据写时拷贝

那么如果子进程我不执行原来的代码了,我想执行一份全新的程序呢?就是父子代码不共享了,子进程执行自己的代码。

程序替换是通过特定的接口,加载磁盘上一个全新的程序(代码和数据),加载到子进程的地址空间中

问题:进程替换的时候有没有创建新的子进程?

解答:没有创建进程。进程为内核数据结构+代码和数据,把一个全新的程序加载到内存里,仅仅是重新建立了映射关系,进程的内核数据结构,状态和优先级等未发生变化,所以没有创建进程

4.2 execl函数

先看man execl的查询:

可以看到exec系列函数一共有6个,我们先搞第一个execl。 

第一个参数是一个const char指针代表其它可执行程序的绝对路程或相对路径,第二个参数是一个可变参数列表,表示你要替换的可执行程序后面要带的参数,以NULL结尾。比如我要执行ls函数,如下代码:

#include<stdio.h>
#include<unistd.h>
#include<stdlib.h>
#include<sys/wait.h>

int main(int argc,char* argv[],char *env[])
{
  pid_t id = fork();
  if(id == 0)
  {
    //子进程
    printf("子进程开始运行,pid:%d\n",getpid());
    sleep(3);
    execl("/usr/bin/ls","ls","--color=auto", "-a","-l",NULL);
    exit(1);
  }
  else{
    //父进程
    printf("父进程开始运行,pid:%d\n",getpid());
    int status =0;
    pid_t id =waitpid(-1,&status,0);//阻塞等待
    if(id>0)
    {
      printf("wait success,exit code:%d\n",WEXITSTATUS(status));
    }
  }
}

 

刚开始父子进程都开始运行,但是3秒后才有ls -a -l的内容,虽然不知道父子进程谁先开始的,但是肯定是父进程最后结束

问题:为什么要创建子进程让子进程来执行execl函数?

解答:如果不创建子进程,那么替换的就是父进程,我们需要让父进程聚焦在读取数据解析数据,指派子进程执行代码的功能(类似工头和工人的关系),所以我们要保证子进程在使用execl替换的时候不影响父进程。

(加载程序之前,父子进程代码共享,数据写时拷贝,当子进程替换的时候,其实也是一种写入,这时候代码也要写时拷贝,并且要将父子进程的代码分离) 

4.3 其它exec函数

 

 ①execlp

这个函数就是在execl后面加了个“ p ” ,p通常表示环境变量,这个函数作用和execl一样,只是第一个参数可以不传路径了,我们输入可执行程序名,它会直接在环境变量当中找,比如我要替换ls:

execlp("ls","ls", "--color=auto", "-l", NULL);

②execle

这个函数和execlp相比多了一点东西又少了一点东西。第一个参数和execl仍然是路径,第二个参数为可变模板列表,第三个参数是新增的,该参数可以由用户自己定义,并在替换后被另一个进程使用

char* myenvp = {"MYVAL=2024", NULL};
execle("./mytest", "mytest", NULL, myenvp);

 ③execv

execl最后的l我们可以看成list的意思,那么execv的v我们就可以看成vector。

第一个参数是路径,后面跟的是一个指针数组了,数组里面每一个指针都指向一个字符串,每个字符串都是替换过后的进程的参数,比如我要用execv将当前进程替换成ls,如下代码:

char* myargv[] = {"ls", "--color=auto", "-l"};
execv("/usr/bin/ls", myargv);

④execvp

和execlp一样,第一个参数不再是路径,可以直接传可执行程序名,然后系统回去环境变量当中找,比如我要执行ls,如下代码

char* myargv[] = {"ls", "--color=auto", "-l"};
execv("ls", myargv);

⑤execvpe

算是这几个函数里最难用的一个吧,也是把自定义环境变量传给替换后的进程。如下代码:

char* myargv[] = {"mytest", NULL}
char* myenvp[] = {"MYVAL=2024", NULL};
execvpe("./mytest", myargv, myenvp);

4.4 execve

上面的要想在man文档查,发现只能“man 3 execl”,而不能“man 2 execl”,这说明上面的6个函数都不是系统调用,那么真正实现替换的是哪个系统调用呢?

man 2 execve后可以看到下面内容:

可以发现execve的参数和execvpe一模一样,上面的6个函数都是都是对系统调用的execve做了封装,因为这么做是为了满足上层用户的不同调用场景

五,一些问题解答

5.1 wait和waitpid如何拿到进程的退出信息的呢?为什么不直接用全局变量拿到退出码?

僵尸进程是已经死掉的进程,代码和数据可以释放,但是进程的内核数据结构也就是PCB不能释放,而PCB内部也保留了进程退出时的各种对出结果,所以wait和waitpid本质就是去读取子进程的task_struct里的退出信息来获取子进程的退出信息。

不能用全局变量是因为进程具有独立性,而且对全局变量做修改时会发生写时拷贝

5.2 僵尸进程中曾经new和malloc的空间会释放码?

前面讲过,僵尸进程是已经死掉的进程,代码和数据会被释放,进程new和malloc申请的空间也会随着进程死掉被释放,因为new和malloc是用户申请的空间,僵尸进程的PCB是操作系统开辟的空间,前者属于用户层的,后者是属于内核层的,所以僵尸进程的内存泄漏是属于操作系统内部的内存泄漏,与用户层无关。

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

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

相关文章

P8803 [蓝桥杯 2022 国 B] 费用报销

P8803 [蓝桥杯 2022 国 B] 费用报销 分析 最值问题——DP 题意分析&#xff1a;从N张票据中选&#xff0c;且总价值不超过M的票据的最大价值&#xff08;背包问题&#xff09; K天限制 一、处理K天限制&#xff1a; 1.对于输入的是月 日的格式&#xff0c;很常用的方式是…

python代码实现xmind思维导图转换为excel功能

目录 转换前xmind示例 运行代码转换后excel示例 python代码 转换前xmind示例 运行代码转换后excel示例 如果想要合并单元格内容&#xff0c;在后面一列参考输入 B2&C2&D2&E2 python代码 from xmindparser import xmind_to_dict import pandas as pd from openp…

微服务项目实战-黑马头条(十三):持续集成

文章目录 项目部署_持续集成1 今日内容介绍1.1 什么是持续集成1.2 持续集成的好处1.3 今日内容 2 软件开发模式2.1 软件开发生命周期2.2 软件开发瀑布模型2.3 软件的敏捷开发 3 Jenkins安装配置3.1 Jenkins介绍3.2 Jenkins环境搭建3.2.1 Jenkins安装配置3.2.2 Jenkins插件安装3…

微信投票源码系统至尊版 吸粉变现功能二合一

源码简介 微信投票系统在营销和社交互动中发挥着多方面的作用&#xff0c;它能够提升用户的参与度和品牌曝光度&#xff0c;还是一种有效的数据收集、营销推广和民主决策工具。 分享一款微信投票源码系统至尊版&#xff0c;集吸粉变现功能二合一&#xff0c;全网独家支持礼物…

TEA: Temporal Excitation and Aggregation for Action Recognition 论文阅读

TEA: Temporal Excitation and Aggregation for Action Recognition 论文阅读 Abstract1. Introduction2. Related Works3. Our Method3.1. Motion Excitation (ME) Module3.1.1 Discussion with SENet 3.2. MultipleTemporal Aggregation(MTA) Module3.3. Integration with Re…

【RAG 论文】Contriever:对比学习来无监督训练文本嵌入模型

论文&#xff1a;Unsupervised Dense Information Retrieval with Contrastive Learning ⭐⭐⭐⭐⭐ Facebook Research, arXiv:2112.09118 Code&#xff1a;github.com/facebookresearch/contriever 一、论文速读 本文使用对比学习的方法来对文本检索模型做无监督学习训练&am…

HTTP基础概念和HTTP缓存技术

什么是HTTP HTTP是超文本传输协议&#xff0c;主要分为三个部分&#xff1a;超文本、传输、协议。 超文本是指&#xff1a;文字、图片、视频的混合体。传输是指&#xff1a;点与点之间的信息通信。协议是指&#xff1a;通信时的行为规范或约定 HTTP常见字段 字段名 解释 例…

Mac IDEA 自动补全mybatis sql语句

导航 Mac IDEA 自动补全mybatis sql语句一、点击IDEA 右侧Database选项二、选择添加对应数据库三、输入数据库信息和方案四、输入数据库信息和方案五、成功 Mac IDEA 自动补全mybatis sql语句 背景&#xff1a; 想在Mapper中&#xff0c;能够实现自动检索数据库表和对应的字段…

暗区突围加速器哪个好 暗区突围国际服加速器 暗区突围PC加速器

《暗区突围》自曝光以来&#xff0c;便以其紧张刺激的战术竞技风格和细腻真实的战场环境&#xff0c;在游戏界掀起了新一轮的热议狂潮。这款游戏将玩家置身于一片神秘而危机四伏的区域&#xff0c;任务简单却极具挑战——深入敌后&#xff0c;搜刮资源&#xff0c;然后在重重围…

LINUX 入门 8

LINUX 入门 8 day10 20240507 耗时&#xff1a;90min 有点到倦怠期了 课程链接地址 第8章 TCP服务器 1 TCP服务器的介绍 开始讲服务器端&#xff0c;之前是客户端DNShttps请求 基础&#xff1a;网络编程并发服务器&#xff1a;多客户端 一请求&#xff0c;一线程 veryold…

物联网SCI期刊,潜力新刊,审稿速度快,收稿范围广泛!

一、期刊名称 Internet of Things 二、期刊简介概况 期刊类型&#xff1a;SCI 学科领域&#xff1a;物联网 影响因子&#xff1a;5.9 中科院分区&#xff1a;3区 出版方式&#xff1a;订阅模式/开放出版 版面费&#xff1a;选择开放出版需支付$2310 三、期刊征稿范围 I…

终端安全管理防护软件排行榜2024(四大终端监控软件推荐)

你的企业存在这些问题吗&#xff1f; 数字化转型的深入和远程办公模式的普及&#xff0c;企业对终端安全管理的需求日益凸显。 确保终端设备的安全性不仅关乎数据保护、业务连续性&#xff0c;更直接影响企业的声誉与合规性。 2024年终端安全防护软件排行榜&#xff0c;有谁荣…

【驱动】SPI

1、简介 SPI(Serial Peripheral interface)串行外设接口。 特点: 高速:最大几十M,比如,AD9361的SPI总线速度可以达到40MHz以上全双工:主机在MOSI线上发送一位数据,从机读取它,而从机在MISO线上发送一位数据,主机读取它一主多从:主机产生时钟信号,通过片选引脚选择…

2024第十六届“中国电机工程学会杯”数学建模A题B题思路分析

文章目录 1 赛题思路2 比赛日期和时间3 竞赛信息4 建模常见问题类型4.1 分类问题4.2 优化问题4.3 预测问题4.4 评价问题 5 建模资料 1 赛题思路 (赛题出来以后第一时间在CSDN分享) https://blog.csdn.net/dc_sinor?typeblog 2 比赛日期和时间 报名截止时间&#xff1a;2024…

vue3+TS或JS, 实现粒子特效 @tsparticles/vue3

在跟着B站视频BV11s4y1a71T学习时&#xff0c;使用到了粒子效果&#xff0c;但是以下这种情况只适用于项目是基于typescript的写法&#xff0c;否则无法实现。 粒子效果 VUE3TStsparticles/vue31、安装2、main.ts 引入3、App.vue4、效果 VUE3JS非最新版1、安装低版本的vue3-pa…

流量分析(一)

数据库类流量分析 MySQL流量 常规操作&#xff0c;查找flag ctfhub{} 注意要选择字符集 Redis流量 查找ctfhub结果没找到 尝试把其变成十六进制继续进行查找 看到了前半段flag 接着往下看 找到了后半段的flag MongoDB流量 还是一样查找ctfhub 字符串没找到 转成十六进制也没…

Leetcode—295. 数据流的中位数【困难】

2024每日刷题&#xff08;132&#xff09; Leetcode—295. 数据流的中位数 实现代码 class MedianFinder { public:MedianFinder() {}void addNum(int num) {if(maxHeap.empty() || num < maxHeap.top()) {maxHeap.push(num);} else {minHeap.push(num);}if(maxHeap.size(…

算法学习笔记(2)-前缀和

##前缀和 指的是某序列的前n项和&#xff0c;在数学上我们可以理解称为数列的前n项和。前缀和是一种预处理&#xff0c;用于降低查询的时间复杂度。 ##一维前缀和 有一个一维数组x和该数组的前缀和数组y&#xff0c;则x和y具有以下关系&#xff1a; #python代码示例 #关系&am…

WorkPlus im(即时通讯)集成平台助力政企数字化转型升级

随着互联网技术的不断发展&#xff0c;企业内部通讯软件已经成为企业日常运营中不可或缺的一部分。企业IM&#xff08;即时通讯&#xff09;和移动门户作为企业内部通讯软件的关键组成部分&#xff0c;为企业提供更加高效、便捷的通讯方式&#xff0c;提高了企业的运营效率。 针…

ETLCloud中如何执行Java Bean脚本

ETLCloud中如何执行Java Bean脚本 在ETLCloud这一强大的数据集成和转换平台中&#xff0c;执行Java Bean脚本的能力为其增添了更多的灵活性和扩展性。Java Bean脚本不仅仅是一段简单的代码&#xff0c;而是一种强大的工具&#xff0c;可以帮助用户定制和优化数据处理的每一个环…