【Linux系统编程二十】:(进程通信2)--命名管道/共享内存

news2024/9/23 5:35:33

【Linux系统编程二十】:命名管道/共享内存

  • 一.命名管道
    • 1.创建管道
    • 2.打开管道
    • 3.进行通信(server/client)
  • 二.共享内存
    • 1.实现原理
    • 2.申请内存
    • 3.挂接
    • 4.通信
    • 5.去关联
    • 6.释放共享内存
    • 7.特性:

一.命名管道

上一篇介绍的一个管道是没有名字的
因为你打开那个文件的时候,并没有告诉我们文件名。直接以读的方式方式打开复制子进程,各自拿一个读写单就可以通信了。
好,这有问题吗?没有问题
正是因为它没有名字,那么所以他必须得让我们对应的父子进程继承这才可以看到同一份资源。它采用的是让父子继承的方案看到的。
它只能是具有血缘关系的进程之间进行进程间通信。
那如果没有血缘关系的进程之间想要通信该怎么办呢?这就可以使用我们的命名管道!
如果两个不同的进程。打开同一个文件的时候。在内核中。操作系统会打开几个文件呢?
在这里插入图片描述

不同的进程打开同一个文件时,在操作系统层面上它还是这种结构。
所以呢两个进程可能有不同的读写文件对象,但是它文件缓冲区还是同一个。还记得进程间通信有个前提吗?这个前提就是应该让我们的不同进程看到。同一份我们对应的资源。
所以这个概念呢,其实和我们匿名管道的原理是一模一样的。

两个不同的进程打开同一个文件时。就要想到这样的问题:

问题1:你怎么知道你们两个打开的是同一个文件?
我们两个只要看到同一个文件名,那么此时这两个进程就可以打开同一个文件了还有个隐含条件叫做同路径下的我们对应的文件。路径加文件名,我们就能保证打开的是同一个文件。

问题2:我们为什么要打开同一个文件啊?
进程通信必须保证在同一个文件里通信啊,不然怎么通信呢,进程间通信的前提是先让不同的进程看到同一份资源。打开同一个文件不就是看到同一份资源了吗。

这种管道文件它是通过使用路径加文件名的方案,让不同的进程看到同一份文件资源,进而实现不同进程之间通信的。所以它有文件,有路径名,有文件名,所以这种通信我们叫做命名管道。

命名管道唯一和我们匿名管道区别的地方是在哪里呢?
就是我们这里所谓的匿名通信的呢,就是可以用来进行我们不相关进程之间进行进行通信的那它是怎么做到不相关进程之间通信的啊,前提条件是得先让不同进程看到同一份资源。它怎么看到的?
是通过文件名叫路径的方式,让我们看到同一份资源的。

1.创建管道

你要用管道文件进行通信,前提条件你是不是得先把对应要通信的资源,你先准备好,资源你都准备不好,那你玩什么呢?对不对?资源都没有准备好,你就不要玩了。

首先得创建一个管道文件创建,怎么创建呢?

mkfifo(pathname,mode)

第一个参数我们要创建这个管道文件
第二个参数呢就是我们打开这个管道文件对应的一个那么权限。

unlink(path)

unlink是用来删除管道文件的。

我们现在已经能创建一个管道了。下面要做的当然就是不同进程直接通信了。
通信之前你们两个进程决定一下一个从管道里读,一个从管道里写。命名管道也是管道,只支持单向通信的。未来我们俩要通信的话,是不是正常的进行我们管道的读写就可以了。

2.打开管道

现在我们剩下的就是打开管道文件,剩下读写。
那么这打开管道文件怎么打开呢?,linux下一切皆文件,它在设计的时候已经把接口设计成了通用接口。
同学们所以你要打开这个管道文件,怎么打开呢按照文件操作的一系列即可open.打开呀然后呢read write close,这样去读写就可以了,就这么简单。

后我们通信时,客户端和服务双方他们都直接那么叫做通过打开文件的方式那么来进行通信就可以了。

3.进行通信(server/client)

服务端server:



#include "comm.hpp"
// 服务端创建信道,客户端只需要发送信息即可
// 使用命名管道,就像使用文件一样要

using namespace std;

// 管理管道文件
int main()
{
    int n = mkfifo(FIFO_FILE, MODE);
    if (n == -1) // 创建失败
    {
       
        perror("mkfifo");
        exit(FIFO_CREATE_ERR); // 创建失败就退出
    }

    // 使用管道文件--如何使用呢?就跟文件操作一样使用,打开,写入关闭
    int fd = open(FIFO_FILE, O_RDONLY);
    if (fd < 0)
    {
        perror("open");
        exit(FIFO_OPEN_ERR);
    }
    while (1)
    {
        // 读取到一个数组里
        char buffer[1024] = {0};

        int x = read(fd, buffer, sizeof(buffer));
        if (x > 0) // 说明读取成功
        {
            buffer[x] = 0;
            //读取完后,就将客户端发送
            cout << "client say@ " << buffer << endl;
        }
        else if (x == 0) // 说明读取结束,读到末尾
        {
            break;
        }
        else
            break;
    }
    close(fd);

    // 最后不用了,就删除管道文件
    int m = unlink(FIFO_FILE);
    if (m == -1)
    {
        perror("unlink");
        exit(FIFO_DELETE_ERR);
    }

    return 0;
}

客户端client:

#include "comm.hpp"
using namespace std;
//客户端这里只需要写入信息

int main()
{
    int fd=open(FIFO_FILE,O_WRONLY);
    if(fd<0)
    {
        perror("open");
        exit(FIFO_OPEN_ERR);
    }
    //打开成功后就可以进行通信
    cout<<"client opne file done"<<endl;
    string line;
    while(1)
    {
        cout<<"Please Enter@: "<<endl;
        getline(cin,line);
        write(fd,line.c_str(),line.size());
    }
    close(fd);

    return 0;
}


共同访问区:

#pragma once

#include <iostream>
#include <cerrno>
#include <sys/types.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <cstring>
#include <cstdlib>
#define FIFO_FILE "./myfifo" // 在当前目录下创建命名管道myfifo
#define MODE 0664

enum
{
    FIFO_CREATE_ERR = 1,
    FIFO_OPEN_ERR,
    FIFO_DELETE_ERR
};
//可以这样封装创建管道和销毁管道,定义一个Init对象即可。
class Init
{
    Init()
    {
        int n = mkfifo(FIFO_FILE, MODE);
        if (n == -1) // 创建失败
        {

            perror("mkfifo");
            exit(FIFO_CREATE_ERR); // 创建失败就退出
        }
    }
    ~Init()
    {
        // 最后不用了,就删除管道文件
        int m = unlink(FIFO_FILE);
        if (m == -1)
        {
            perror("unlink");
            exit(FIFO_DELETE_ERR);
        }
    }
};

以上就是基于文件部分的通信,包括匿名管道和命名管道。

二.共享内存

1.实现原理

在介绍之前再强调一遍:通信的前提:让不同的进程先看到同一份资源。
sister v的共享内存呢,它是一个那么单独设计的内核模块,用来进行进程间通信的。

让进程看到同一份资源,什么资源不是资源,谁提供资源都行。
上面讲的那些都叫做基于文件的。
现在讲的这个共享内存,它是专门由操作系统去给我们提供的一种通信方案。
假设我们有a b两个进程呢,那么不管是哪两个进程呢,那么我们以a进程为例,它自己有自己的代码区有自己的数据区,那么就可以有自己的页表,然后经过页表呢将对地址空间当中的内容进行映射到物理地址当中。(当然前提肯定是你这个进程的代码数据各种全全部都有了)。
然后映射过来,此时我们的进程就能看到,就能访问了。

本质原理:
①这里第一步就先在这个物理内存先创建出来一块空间。
②第二步再把这个共享空空间再经过页表啊映射到我们对应的一个进程的共享区当中。
在这里插入图片描述

那么映射到我们的共享区当中呢,然后给应用层返回一个对应的连续的内存空间的起始地址啊,所以这里呢我们就有一个起始的虚拟地址。

那么首先这一点在操作系统层面上,他想做是完全可以做到的那么这时我们就完成了一个叫做共享内存的创建。

这个共享内存是建立在我们进程地址空间的共享区的,那么进程这个时候就能直接访问了。既然操作系统能把你对应的申请的这块内存给你这个进程a去进行建立映射。要通信其他进程,其他进程你也可以来进行映射啊,经过页表映射到你自己的地址空间里面。
然后呢建立好之后,给这应用层也返回虚拟地址啊,所以从此往后呢,我们两个进程就可以通过各自的页表访问同一块物理内存了。

那么此时这就叫做先让不同的进程看到同样的一份资源。这个原理就叫做共享内存。

这要做的第一步无非就是要先申请内存啊,二步就是要把申请好的这块内存分别挂接到我们两个进程它的地址空间。然后返回我们对应的首地址啊。返回了我们对应的地址之后呢,就有了一段我们对应的起始对应的内存了。
所以我们未来访问时可以经过列表访问这个内存,一个进程向我们的共享内存里进行写,如果你愿意,你也可以写啊,共享内存是双向通信的,但一般我们都是一个写一个读。

【问题1】系统调用
进城需要通信。操作系统来帮我们来做这个建立通信的信道。
也就是说,我们的进程呢,说我要通信,那么操作系统说行,那我帮你执行。
进程自己不能在系统里直接访问内核数据结构,它也不能直接搞它的页表,然后建立映射。需要操作系统来搞。如果进程自己做了这个东西,那么这个内存就属于你自己的。我们想要的是一个共享的空间。
所以我们的需求是要让操作系统去执行,它解决的是我们为什么要要建立共享内存,它解决的是我们如何建立的问题。
所以我们必须得由操作系统来给我们的进程操作,这样就一定要存在一个东西叫做系统调用。
所以共享内存这玩意儿呢,它将来的建立,挂接,最终是由操作系统必须得给我提供一批系统调用。然后我进程呢调你的系统调用,我什么时候调是由我进程说了算。但一旦我调系统调用了,就跟我的进程没关系,是由操作系统帮我去做的,创建出来的共享内存,那么其中就不属于我这个进程私有的。

【问题2】共享内存在内核方面的理解
所以这个共享内存是要由操作系统管理的,当很多进程都要进行通信,操作系统就要创建多个共享内存,操作系统就需要对这些共享内存进行管理:
在操作系统当中,你对应的要管理这个共享内存。要管理这个管理层,必须得把管理层先得描述起来。比如说啊我们就得有一个struct结构体描述你申请的共享内存多大呀,谁申请的呀?那么当前有多少个进程和我是关联的呀,那么我们当前进程呃共享内存啊我们使用了多少了等它的诸多属性。我们是不是一定要有内核结构体来描述共享内存。再把所有的这些被共享内存的结构体,用链表了、数组了,或者你各种数据结构管理起来。
所以在操作系统层面上管理共享内存的行为就变成了对某种数据结构的增删查改。

【问题3】删除共享内存
共享内存申好了,那么如何删除共享内存呢???
那么你肯定是要先去关联啊,不难理解,就是你曾经不是把共享内存挂接到你地址空间了吗?那要释放共享内存,那么首先得做的是让当前进程和共享内存去掉关联,也就是把页表曾经映射的那些条目直接去掉。那么去关联之后,然后才能进行释放共享内存。
可是最最尴尬的是,我们的共享内存,它怎么知道有几个进程跟他是关联的呢?
好,共享内存,它怎么知道有几个进行关联呢?所以它在内部是存在着引用计数,计算有多少个进程和它关联起来的。那么删除共享内存时,去关联后,并不能直接删除共享内存,还需要看引用计数是否为0,只有当最后一个进程去关联的时候,减到零的时候才可以真正的删除共享内存。

2.申请内存

首先得能够在系统里创建一个共享内存。在linux系统里创建一个共享内存,我们所要采用的接口叫shmget(),它的作用呢是申请一个系统的共享内存啊
在这里插入图片描述
shmget系统调用三个参数分别代表的意义:

1.第一个参数:key
不管我将来是想创建共享内存,还是想获取共享内存。我们两个j进程必须拿到同一个key。
因为只有拿到了同一个king,我们此时才能证明我们两个访问的是同一个共享内存。因为系统里可同时会存在非常多的共享内存哦,那有很多的话,你怎么保证你的就是通信双方的进程看到的是同一个共享内存呢?
所以key是让我们的不同进程进行唯一性标识。我们呢才能保证我们在共享内存里判断一个共享内存存在。
因为我要创建过共享内存,我也得有个key。这样在系统里去创建共享内存的时候,你存在还是不存在,我就可以拿着我的k和系统里进行对比就可以了。那么正是因为我们有了key,所以啊第一个进程可以通过我们对应的k创建共享内存。
那么第二个进程他们只要拿着同一个key值获得已经创建的共享内存。
所以两个进程只要拿着同一个key就可以看到同一个叫做我们的共享内存了。
第一个进程通过king创建。第二个他只要拿着同样的key,他就可以和第一个进程看到同共享内存了。
因为我们这个king是唯一的,而且呢我们两个呢那么就可以通过key来标定为一个共享内存了。在这里插入图片描述
通过ftok(pathname,processid)接口就可以创建一个key值。
【问题1】为什么要自己创建key,而不是让操作系统创建呢?
如果操作系统给你形成了一个key,你怎么把这个key交给另一个和你通信的进程呢?
2.第二个参数
共享内存的大小
3.第三个参数
设置共享内存创建时,设置一些配置和权限
IPC_CREAT:创建共享内存,如果不存在,就创建。如果存在就返回存在的共享内存。
IPC_CREAT|IPC_EXCL.创建共享,如果不存在就创建,如果存在就报错。
4.返回值
shmid返回的是用来标识唯一的共享内存标识符。

key是给操作系统用的,让操作系统去拿着它去申请共享内存,这里所谓的shmid最后是不是就是它的返回值啊?它的返回值叫做共享内存标识符,它只在你的进程内。
一个是给我们对应的操作系统内标定唯一性的。
一个是让我们在自己的编码当中用这个共享内存,用它来进行控制共享内存的。所以只有创建共享内存是用这个key从此往后再也不用这个key了。创建完后,再对这个共享内存操作都是通过shmdi来找到共享内存操作的。

3.挂接

共享内存申请完了,第二步骤就是我们要把这个外部内存和我的进程挂接起来。那么怎么挂接呢?首先肯定不能你自己挂,肯定是由操作性的给你提供接口的,所以我们叫做shmat。
在这里插入图片描述
返回值这里的代表的含义就是你想让你当前对应的共享内存挂接到地址空间的什么位置。共享内存最终挂接的时候是在我们对应的共享区,但具体你想让它挂载到我们共享区的什么位置?void*就是我们最终得到的那个虚拟的起始地址。第三个参数我们也是默认啊,默认的话就按照我们的这个呃叫做我们共享内存它默认的权限来就行了。所以这里设为零就可以。
第二个参数设为nullptr,它给我们得到的是一块连续堆空间的起始地址,说白了就是一块虚拟地址。

4.通信

将共享内存挂接到不同进程的地址空间后,这些进程之间就可以进行通信了。
公共资源comm.hpp。它们之间的通信非常简单直接,就是各种朝自己的内存里输出和输入即可。
在这里插入图片描述

#include <iostream>

using namespace std;
#include <string>
#include <cstdlib>
#include <cstring>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/types.h>
#include <sys/stat.h>
//key值的获取根据系统调用ftok(pathname,pro_id)

const string pathname="/home/tao";
const int pro_id=0x6666;
int size= 4096;

//这是用户自己决定的
//Log log;
key_t GetKey()
{
    key_t k=ftok(pathname.c_str(),pro_id);
    if(k<0) 
    {
       // log(Fatal,"ftok error:%s",strerror(errno));//致命错误
        exit(1);
    }
    //普通日志:成功
    //log(Info,"ftok success key is %d",k);
    return k;
}

int GetMEm(int flag=IPC_CREAT|IPC_EXCL|0666)//默认是创建,权限是0666
{
    int key=GetKey();
    int shmid=shmget(key,size,flag);//确保每次创建的都是新的共享内存
    if(shmid<0)
    {
     //   log(Fatal,"Creat shmget error:%s",strerror(errno));
        exit(2);
    }
    //log(Info,"Creat shmget success shmid is%d",shmid);
    return shmid;
}

//进程a是创建共享内存的,而进程b是要获取共享内存的,我们可以通过传不同的参数
//调用同一个GetMem来完成,因为获取共享内存,我们就不加上IPC_EXCL就可以实现
//如果共享内存以及存在,就直接返回该共享内存。

int CreatSM()
{
    return GetMEm();//默认是创建
}
int GetSM()
{
    return GetMEm(IPC_CREAT);//只传入IPC_CREAT就是获取原先存在的共享内存
}




进程a.cc

#include "comm.hpp"

//两个进程想通过共享内存通信
//1.首先进程a根据key值让操作系统申请共享内存
//通过唯一的key值来让操作系统申请一块唯一的共享内存,所以第一步首先需要约定以一个key值
//本质就是操作系统会拿这个key值去构造一个结构体对象,该结构体对象是用来管理共享内存的
//2.进程a的地址空间需要挂接到共享内存
//3.不用了就去挂接
//4.共享内存是没有同步互斥的,我们可以利用管道的方式,来进行同步,在进行通信之前,创建
//命名管道,并将命名管道打开。开始接受信息,一旦接受到一个信息才可以进行输出,如果没有收到就在等待

int main()
{
   //1.创建共享内存
   int shmid=CreatSM();
   
   //2.挂接到共享内存--挂接到地址空间的哪里呢?挂接到shmadder地方
   char*shmaddr=(char*)shmat(shmid,nullptr,0);
   //3.通信
  while(true)
  {
     cout<<"client say@ "<<shmaddr<<endl;
    
  }

   //4.不用了去关联
   shmdt(shmaddr);

   //5.最后如果没有进程要用共享内存了,共享内存是不会自动销毁的,需要用户手动销毁
   shmctl(shmid,IPC_RMID,nullptr);
}

进程b.cc

#include "comm.hpp"

//进程b如果想和进行a通过共享内存通信,需要用自己的key来和共享内存进行匹配
//因为共享内存被进程a创建完了,进程b只需匹配
//进程b匹配完也需要将自己的地址空间挂接到共享内存上。

//实现同步---在通信之前创建命名管道,并打开命名管道,往命名管道里输入固定信息,才能往
//共享内存里输入。
int main()
{
   //1.获取到共享内存
   int shmid=GetSM();

   //2.挂接到地址空间
   char* shmaddr=(char*)shmat(shmid,nullptr,0);
   
   //3.开始通信
  while(true)
  {
    cout<<"Please enter:";
     cin>>shmaddr;
  }

   //4.不用了,去关联
   shmdt(shmaddr);
}

5.去关联

如果想去掉你自己对某一个共享内存的关联,那么你只需要使用shmdt
在这里插入图片描述
那么这个参数什么意思呢?这个参数就是曾经这个shmat所得到的虚拟地址的起始地址。

6.释放共享内存

自己不主动的把共享内存给我关掉,操作系统我也不给你关。你不主动关闭,那么共享内存会一直存在。所以共享内存一旦创建,还要放在那儿。
你用户你想进行几个进程通信,你就随时挂机,你想用你就创建啊,你想用你就挂机,不用你把它直接去关联,除非你主动关闭好,否则固态内存一直存在。生命周期随内核啊
在这里插入图片描述
shamctl可以用来删除共享内存,也可以用来获取共享内存的属性。它这个接口第一个参数啊,就是我们共享内存的id,也就是共享内存标识符。第二个参数用来删除共享内存的。第三个参数就是用来获取共享内存的各种属性的,各种属性都存在struct shmid_ds这个结构体对象里。

7.特性:

共享内存这块,我们发现它没有同步机制,需要我们自己设置一些同步加锁来保护它,它是通信方式中最快的一种,因为通信的过程中,拷贝次数很少。
在这里插入图片描述

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

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

相关文章

前端工程、静态代码、Html页面 打包成nginx 的 docker镜像

1. 创建一个 mynginx的目录 2. 将前端代码文件夹&#xff08;比如叫 front &#xff09;复制到 mynginx 目录下 3. 在mynginx 目录下创建一个名为Dockerfile 的文件&#xff08;文件名不要改&#xff09;&#xff0c;文件内容如下&#xff1a; # 使用官方的 Nginx 镜像作为基…

【C++】泛型编程 ⑭ ( 类模板示例 - 数组类模板 | 容器思想 | 自定义类可拷贝 - 深拷贝与浅拷贝 | 自定义类可打印 - 左移运算符重载 )

文章目录 一、容器思想1、自定义类可拷贝 - 深拷贝与浅拷贝2、自定义类可拷贝 - 代码示例3、自定义类可打印 - 左移运算符重载 二、代码示例1、Array.h 头文件2、Array.cpp 代码文件3、Test.cpp 主函数代码文件4、执行结果 一、容器思想 1、自定义类可拷贝 - 深拷贝与浅拷贝 上…

企业软件定制开发的优势|app小程序网站搭建

企业软件定制开发的优势|app小程序网站搭建 企业软件定制开发是一种根据企业特定需求开发定制化软件的服务。相比于购买现成的软件产品&#xff0c;企业软件定制开发具有许多优势。 1.企业软件定制开发可以满足企业独特需求。每个企业都有自己独特的业务流程和需求&#xff0c;…

为什么vue中数组和对象的props默认值要写成函数形式?

多个组件数据不相互干涉 假如在一个地方引用了同一个组件&#xff0c;并给他们都绑定了单独的值。如果只声明为一个对象或数组&#xff0c;可能会导致在某一个实例中修改数据&#xff0c;影响到其他实例中的数据&#xff0c;因为数组和对象是引用类型的数据。为了在多次引用组件…

揭秘:如何精准定位性能瓶颈,优化系统性能?

你好&#xff0c;我是静姐&#xff0c;目前在一家准一线互联网大厂做测试开发工程师。对于一般公司普通测试工程师来说&#xff0c;可能性能测试做的并不是很复杂&#xff0c;可能只是编写下脚本&#xff0c;做个压测&#xff0c;然后输出报告结果&#xff0c;瓶颈分析和调优的…

ubuntu安装cuda驱动报错及解决,屡试不爽

机器重启输入nvidia-smi提示如下错误,字面意思就是驱动和库不匹配 Failed to initialize NVML: Driver/library version mismatch 查看一下nvidia相关库 sudo dpkg --list | grep nvidia-* 将所有已安装库卸载 sudo apt purge nvidia-* 重新安装驱动 sudo ./NVIDIA-Linux-…

Java实现王者荣耀小游戏

主要功能 键盘W,A,S,D键&#xff1a;控制玩家上下左右移动。按钮一&#xff1a;控制英雄发射一个矩形攻击红方小兵。按钮控制英雄发射魅惑技能&#xff0c;伤害小兵并让小兵停止移动。技能三&#xff1a;攻击多个敌人并让小兵停止移动。普攻&#xff1a;对小兵造成基础伤害。小…

redis的集群,主从复制,哨兵

redis的高可用 在Redis中&#xff0c;实现高可用的技术主要包括持久化、主从复制、哨兵和集群&#xff0c;下面分别说明它们的作用&#xff0c;以及解决了什么样的问题。 持久化&#xff1a; 持久化是最简单的高可用方法&#xff08;有时甚至不被归为高可用的手段&#xff09;…

京东大数据分析:2023年10月手机行业销量同比增长249%

今年双11&#xff0c;手机仍是竞争极为激烈的产品品类&#xff0c;各大手机厂商均在双11之前做足了准备&#xff0c;10月下旬&#xff0c;各电商平台双十一预售正式开启。而在双11大促节的参与下&#xff0c;10月份手机市场的整体销售也呈现增长趋势。 根据鲸参谋平台的数据显示…

mybatis 基本操作 删除 插入 更新 查询

根据主键删除数据 插入数据 -- 插入 insert into emp(username, name, gender, image, job, entrydate, dept_id, create_time, update_time) values (tom,塔姆,tom,1 , 1.png ,now(),1,now(),now() Options(keyProperty "id",useGeneratedKeys true) Insert(&quo…

[译]JavaScript中Base64编码字符串的细节

本文作者为 360 奇舞团前端开发工程师 本文为翻译 原文标题&#xff1a;The nuances of base64 encoding strings in JavaScript 原文作者&#xff1a;Matt Joseph 原文链接&#xff1a;https://web.dev/articles/base64-encoding Base64编码和解码是一种常见的将二进制内容转…

将对象转成URL参数

背景 有的时候前端跳转到其他平台的页面需要携带额外的参数&#xff0c;需要将对象转成用 & 连接的字符串拼接在路径后面。 实现方法

SquareCTF-2023 Web Writeups

官方wp&#xff1a;CTFtime.org / Square CTF 2023 tasks and writeups sandbox Description&#xff1a; I “made” “a” “python” “sandbox” “”“” nc 184.72.87.9 8008 先nc连上看看&#xff0c;只允许一个单词&#xff0c;空格之后的直接无效了。 flag就在当…

河北专升本(微机原理)

目录 第一章&#xff1a;计算机基础与数制转化 1. 进制运算基础 2. 常用编码形式 3. 计算机系统的组成及其工作原理 4. 微机系统主要技术指标 第二章&#xff1a;8086微处理器及其系统 1. 8086微处理器&#xff08;CPU&#xff09; 2. 8086的存储器及I/O组织 3. 8086系…

TikTok历史探秘:短视频中的时间之旅

在数字时代的浪潮中&#xff0c;TikTok崭露头角&#xff0c;成为社交媒体领域的一颗耀眼新星。这款短视频应用以其独特的创意、时尚和娱乐性质&#xff0c;吸引了全球数以亿计的用户。 然而&#xff0c;TikTok并非一夜之间的奇迹&#xff0c;它背后蕴藏着丰富而有趣的历史故事…

Course1-Week2-多输入变量的回归问题

Course1-Week2-多输入变量的回归问题 文章目录 Course1-Week2-多输入变量的回归问题1. 向量化和多元线性回归1.1 多维特征1.2 向量化1.3 用于多元线性回归的梯度下降法 2. 使梯度下降法更快收敛的技巧2.1 特征缩放2.2 判断梯度下降是否收敛2.3 如何设置学习率 3. 特征工程3.1 选…

管理时间的四象限法则

在管理工作和生活中&#xff0c;我们经常面临着各种各样的任务和事务。为了更好地处理这些任务&#xff0c;可以借鉴“重要紧急”、“重要不紧急”、“不重要紧急”以及“不重要不紧急”这四个象限的概念。 重要紧急&#xff1a;这类任务需要立刻行动&#xff0c;因为它们对目…

PPT思维导图怎么做?这2个思维导图工具墙裂推荐!

在日常学习和工作中&#xff0c;我们常常会面临需要处理大量信息的情况&#xff0c;这时候&#xff0c;一种叫做思维导图的工具可能会成为你的救星。 不同于传统的线性记录方式&#xff0c;思维导图以其独特的视觉表现力和结构化的信息处理方式&#xff0c;使得人们能够更加有…

实时截留抖音询价的用户:10个合规方法,让你的业务迅速增长!

先来看实操成果&#xff0c;↑↑需要的同学可看我名字↖↖↖↖↖&#xff0c;或评论888无偿分享 一、引言 随着抖音的普及度越来越高&#xff0c;越来越多的商家开始关注抖音询价用户。这些潜在客户对于企业的发展至关重要&#xff0c;如何实时截留这些用户成为商家关注的重点…

Python 基础【四】--数据类型-字符串【2023.11.23】

1 .定义 字符串是 Python 的一种数据类型&#xff0c;它可以通过单引号 ‘、双引号 "、三引号 ‘’’ 或 “”"来定义。 aabcd bacsdcd c"""accsfv""" print(a) print(b) print(c)2 .基本操作 访问单个字符 注意&#xff1a;从0开始…