【Linux进程通信 —— 管道】

Linux进程通信 —— 管道

  • 进程间通信介绍
    • 进程间通信的概念
    • 进程间通信的目的
    • 进程间通信的本质
    • 进程间通信的分类
  • 管道
    • 什么是管道
    • 匿名管道
        • 匿名管道的原理
        • pipe
        • 用fork来共享管道原理
        • 站在文件描述符角度-深度理解管道
        • 站在内核角度-管道本质
        • 管道读写规则
        • 管道的特点
        • 管道的四种特殊情况
        • 管道的大小
    • 命名管道
        • 命名管道的原理
        • 创建一个命名管道
        • 命名管道的打开规则
        • 用命名管道实现 serve&client 通信
        • 命名管道和匿名管道的区别

进程间通信介绍

进程间通信的概念

在 Linux 中,进程间通信(IPC,Inter-Process Communication)是指 不同进程之间交换数据和信息的一种机制。 这种通信可以是在同一台计算机上的不同进程之间,也可以是在不同计算机之间的进程之间。

进程间通信的目的

进程间通信(IPC) 的主要目的是实现不同进程之间的数据交换和协作,从而实现更复杂的任务和功能。以下是进程间通信的几个主要目的:

  1. 数据交换:进程间通信允许不同进程之间交换数据和信息。这些数据可以是简单的消息、文件、共享内存中的数据等。通过数据交换,不同进程可以共享信息,协作完成复杂的任务。

  2. 协作:进程间通信使得不同进程能够协同工作,共同完成某些任务。例如,一个进程负责生成数据,另一个进程负责处理数据,它们之间通过通信来协调工作。

  3. 资源共享:进程间通信可以实现共享资源,如共享内存、文件、设备等。多个进程可以同时访问和操作共享资源,从而提高系统的利用率和效率。

  4. 进程同步:进程间通信可以实现进程之间的同步操作,确保它们按照一定的顺序执行。例如,使用信号量来控制对共享资源的访问,或者使用消息队列来实现进程间的同步消息传递。

  5. 并发控制:进程间通信可以实现对并发访问的控制,避免竞态条件和数据不一致性。例如,通过信号量或互斥锁来控制对共享资源的访问,以确保数据的一致性和可靠性。

总的来说,进程间通信的目的是实现不同进程之间的数据交换、协作和同步,从而实现更复杂、更高效的系统功能和任务。它是操作系统中的重要概念,对于实现多任务处理、并发编程和分布式系统等方面具有重要意义。

进程间通信的本质

进程间通信的本质就是,让不同的进程看到同一份资源。

在一个正在运行的操作系统中,存在许多相互独立的进程,它们需要相互协作以确保操作系统的正常运行。为了实现这种协作,这些进程通过进程间通信来共享资源,即让不同的进程能够访问并操作同一份共享资源,从而实现数据共享和协作。
在这里插入图片描述

一个简单的例子是父子进程间的通信。假设父进程需要向子进程发送一个命令,子进程收到命令后执行相应的操作,并将结果返回给父进程。这里的通信可以通过管道、消息队列或共享内存来实现。父进程向管道写入命令,子进程从管道中读取命令并执行,然后将结果写回管道,父进程再从管道中读取结果。这样,父子进程之间就实现了简单的数据交换和通信。

进程间通信的分类

管道

  • 匿名管道pipe
  • 命名管道

System V IPC

  • System V消息队列
  • SystemV 共享内存
  • SystemV信号量

POSIXIPC

  • 消息队列
  • 共享内存
  • 信号量
  • 互斥量
  • 条件变量
  • 读写锁

管道

什么是管道

管道(Pipe) 是一种用于进程间通信的机制,允许一个进程的输出直接成为另一个进程的输入。它主要用于在父进程和子进程之间或者在同时运行的两个进程之间进行通信。管道可以分为匿名管道(Anonymous Pipe)命名管道(Named Pipe) 两种类型。
在这里插入图片描述

匿名管道

匿名管道的原理

匿名管道用于进程间通信,且仅限于本地父子进程之间的通信。

匿名管道是一种单向通信管道,只能在相关的父子进程间使用。它是通过调用pipe()系统调用创建的,具有读端写端 。匿名管道的数据流向是单向的,即数据只能从写端流入到读端。

在这里插入图片描述
注意:操作系统维护父子进程共享的文件资源时,并不会在父子进程间进行数据的写时拷贝。管道使用文件的概念,但操作系统并不会将进程通信的数据刷新到磁盘上,因为这样做既会涉及到IO操作从而降低效率,也是没有必要的。换言之,这些文件通常只存在于内存中,而不会被写入到磁盘上。

pipe

pipe函数是Unix/Linux操作系统提供的一个系统调用,用于创建一个匿名管道。它的原型如下:

#include <unistd.h>

int pipe(int pipefd[2]);

  • 参数pipefd是一个包含两个整数元素的数组,用于返回新创建的管道的文件描述符。pipefd[0]用于从管道中读取数据,pipefd[1]用于向管道中写入数据。

  • 调用 成功 时,返回值为 0 ;调用 失败 时,返回值为 -1 ,并设置全局变量errno来指示错误类型。

用fork来共享管道原理

在创建匿名管道实现父子进程间通信的过程中,需要pipe函数和fork函数搭配使用,具体步骤如下:

1、父进程调用pipe函数创建管道。
在这里插入图片描述
2、父进程创建子进程
在这里插入图片描述

3、父进程关闭写端,子进程关闭读端。

在这里插入图片描述

  1. 管道只能够进行单向通信,因此当父进程创建完子进程后,需要确认父子进程谁读谁写,然后关闭相应的读写端。
  2. 从管道写端写入的数据会被内核缓冲,直到从管道的读端被读取。
站在文件描述符角度-深度理解管道

在这里插入图片描述

站在内核角度-管道本质

在这里插入图片描述

管道读写规则

pipe2函数与pipe函数类似,同样用于创建一个匿名管道。它的原型如下:

#include <unistd.h>

int pipe2(int pipefd[2], int flags);

pipe 函数不同的是,pipe2 函数允许通过参数 flags 设置一些附加的选项,以控制管道的行为。常用的选项包括:

  • O_CLOEXEC:在父进程执行 fork 创建子进程时,子进程会自动关闭父进程中不需要的文件描述符。这可以通过设置 O_CLOEXEC 标志来实现,以确保在子进程中关闭管道的文件描述符。
  • O_NONBLOCK:设置管道的读取和写入操作为非阻塞模式。在非阻塞模式下,读取和写入操作会立即返回,不会等待直到管道中有数据可读或有空间可写。

这两个选项可以通过按位或运算组合使用。例如,要创建一个非阻塞的管道并在父进程中关闭不需要的文件描述符,可以将 flags 设置为 O_NONBLOCK | O_CLOEXEC

pipe2 函数的返回值和 pipe 函数类似,成功时返回0,失败时返回-1,并设置全局变量 errno 来指示错误类型。

管道的特点
  • 单向性:管道是单向的,只能用于单向数据流的传输。通常有两种类型的管道:单向管道和双向管道。单向管道只能实现单向数据流的传输,而双向管道则可以实现双向数据流的传输。

  • 半双工:管道是半双工的,即同一时间只能有一个方向的数据流动。在一个管道中,数据只能单向流动,要么从父进程流向子进程,要么从子进程流向父进程。

  • 适用于有亲缘关系的进程:管道通常用于具有亲缘关系的进程之间进行通信,例如父子进程之间。因为管道是通过 fork 系统调用创建的,只有具有亲缘关系的进程才能共享同一个管道。

  • 有限缓冲区:管道具有有限的缓冲区,因此在读取端没有读取数据时,写入端会被阻塞。当管道的缓冲区已满时,写入端也会被阻塞,直到缓冲区有足够的空间来容纳写入的数据。

  • 不支持随机访问:管道是顺序访问的,不支持随机访问。也就是说,只能按照数据写入的顺序依次读取数据,不能直接定位到某个位置读取数据。

  • 自动关闭:在进程终止时,管道会自动关闭。当所有指向管道的文件描述符都被关闭时,管道将被系统自动释放。

管道的四种特殊情况

管道的四种特殊情况:

  1. 写端进程不写,读端进程一直读

    • 情况描述:写端进程不向管道写入数据,但读端进程一直尝试读取数据。
    • 表现:读端进程会被挂起,直到管道中有数据可读。
    • 解释:读端进程在读取数据时,如果管道中没有数据可读,则会被挂起,直到有数据可供读取。
  2. 读端进程不读,写端进程一直写

    • 情况描述:写端进程不断向管道写入数据,但读端进程没有读取数据。
    • 表现:当管道被写满后,写端进程会被挂起,直到管道中的数据被读取后才会继续写入。
    • 解释:当写端不断写入数据,而读端没有读取时,管道会被写满。此时写端进程会被挂起,直到读端读取数据释放空间。
  3. 写端进程写完后关闭写端

    • 情况描述:写端进程将数据写入管道后关闭了写端。
    • 表现:读端进程将管道中的数据读完后,继续执行后续代码,而不会被挂起。
    • 解释:写端进程关闭写端后,读端进程读取完管道中的数据后会收到EOF,继续执行后续代码。
  4. 读端进程关闭读端

    • 情况描述:读端进程关闭了读端,但写端进程仍在向管道写入数据。
    • 表现:操作系统会将写端进程杀掉。
    • 解释:当读端进程关闭读端后,写端继续向管道写入数据,此时操作系统会向写端进程发送信号,通知其管道已关闭,然后将写端进程杀掉。

示例

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

int main() {
    int pipefd[2];
    pid_t pid;
    char buffer[10];

    // 创建管道
    if (pipe(pipefd) == -1) {
        perror("pipe");
        exit(EXIT_FAILURE);
    }

    // 创建子进程
    pid = fork();
    if (pid == -1) {
        perror("fork");
        exit(EXIT_FAILURE);
    }

    if (pid == 0) {  // 子进程
        // 关闭写端,子进程从管道中读取数据
        close(pipefd[1]);
        // 读取数据
        read(pipefd[0], buffer, sizeof(buffer));
        printf("Child Process: Data read from pipe: %s\n", buffer);
        // 关闭读端
        close(pipefd[0]);
        exit(EXIT_SUCCESS);
    } else {  // 父进程
        // 关闭读端,父进程向管道中写入数据
        close(pipefd[0]);
        // 写入数据
        write(pipefd[1], "Hello", 5);
        printf("Parent Process: Data written to pipe: Hello\n");
        // 关闭写端
        close(pipefd[1]);
        // 等待子进程结束
        wait(NULL);
        exit(EXIT_SUCCESS);
    }

    return 0;
}

在这个示例中,父进程创建了一个管道并生成了一个子进程。父进程通过管道将字符串"Hello"写入管道,而子进程则从管道中读取数据并打印到控制台上。在这个过程中,我们可以观察到以下特殊情况:

  • 写端进程不写,读端进程一直读:在这个示例中,如果父进程不写入数据,子进程会一直阻塞在读取管道的操作,直到有数据可读。
  • 读端进程不读,写端进程一直写:如果子进程不读取管道中的数据,而父进程持续写入数据,那么管道会被写满,父进程的写操作会阻塞,直到有空间可写。
  • 写端进程写完后关闭写端:在父进程写入数据后,关闭了写端。子进程读取完数据后,管道的读端会返回EOF,子进程继续执行后续代码。
  • 读端进程关闭读端:在子进程读取完数据后,关闭了读端。如果父进程继续写入数据,操作系统会向父进程发送信号,告知读端已关闭,然后父进程被杀死。
管道的大小

我们可以利用管道四种特殊情况中的第二种 读端进程不读,写端进程一直写 来验证:
让读端不读,写端进程一直写,直到写端被写满无法继续写时,则可以得到管道具体的大小。

#include <iostream>
#include <cerrno>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <string>

const int size = 1024;

//写
void SubProcessWrite(int wfd)
{
    int pipesize = 0;
    std::string message = "father, i am your son process!";
    while(true)
    {
        char c = 'A';
        write(wfd,&c,1);
        std::cout << "pipesize = " << ++pipesize << std::endl;
    }
}

//读
void FatherProcessRead(int rfd)
{
    // sleep(500);
    char inbuffer[size];    //用于储存读到的信息的缓冲区
    while(true)
    {
        // sleep(4);
        ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1);
        if(n > 0)
        {
            inbuffer[n] = 0;
            std::cout << "father get message"  << inbuffer << std::endl;
        }
    }
}

int main()
{
    //创建管道
    int pipefd[2];
    int n = pipe(pipefd);   //输出型参数

    if(n != 0)
    {
        std::cerr << "errno: " << errno << ":" << "errstring: " << strerror(errno) << std::endl;
        return 1;
    }

    //pipefd[0] ->0 -r 嘴巴 读   pipefd[1] ->1 -w 🖊 写
    std::cout << "pipefd[0]: " << pipefd[0] << " " << "pipefd[1]: "<< pipefd[1] << std::endl;

    //创建子进程
    pid_t id = fork();
    if(id == 0)
    {
        std::cout << "子进程已经关闭了读fd,保留写fd,准备开始写消息了。" << std::endl;
        //关闭不必要的fd
        //子进程
        //write
        close(pipefd[0]);

        SubProcessWrite(pipefd[1]);

        close(pipefd[1]);
        exit(0);
    }
  
    //父进程
    //read
    std::cout << "父进程已经关闭了写fd,保留读fd,准备开始读消息了。" << std::endl;
    close(pipefd[1]);
    
    // FatherProcessRead(pipefd[0]);
    // close(pipefd[0]);

    int status = 0;
    pid_t rid = waitpid(id,&status,0);
    if(rid > 0)
    {
        std::cout << "wait child process done, exit sig:" << (status&0x7f) << std::endl;
        std::cout << "wait chile process done, exit code(ign)" << ((status>>8)&0xff) << std::endl;
    }
    return 0;
}

在这里插入图片描述
可知:我当前Linux版本中管道的最大容量是65536字节。

命名管道

命名管道的原理

命名管道的原理

命名管道是一种特殊类型的文件系统对象,它允许不同进程通过文件来进行通信。其原理基于文件系统的特性,它实际上是一个由操作系统维护的特殊文件,具有磁盘上的路径名,可以在文件系统中找到。与匿名管道不同,命名管道可以通过文件系统中的路径名进行访问,从而允许不同进程在不同的时间段内进行通信。

创建一个命名管道

我们可以使用mkfifo命令创建一个命名管道。

qq@iZ0jl65jmm6w9evbwz2zuoZ:~/bt111/Linux/5_09/test$ mkfifo fifo

在这里插入图片描述
可以看到,创建出来的文件的类型是p,代表该文件是命名管道文件。
在这里插入图片描述
我们这里通过shell简单的进行两个进程的通信,这里可以看到左边的进程使用while done 来向fifo写入数据,右边进程来读取fifo的信息。

在这里插入图片描述


我们也可以使用mkfifo函数来创建命名管道

mkfifo函数
mkfifo函数是一个用于创建命名管道的Unix/Linux函数。他的原型如下:

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

int mkfifo(const char *pathname, mode_t mode);

参数:

  • pathname:指定要创建的命名管道的路径名。
  • mode:指定创建的管道的权限模式(权限位)。三位八进制数字。具体的权限可查看这篇文章Linux当中的权限问题
  1. 创建命名管道: 首先,需要使用特定的系统调用(如mkfifo函数)在文件系统中创建一个命名管道。这个系统调用将在文件系统中创建一个特殊类型的文件,其类型为FIFO(先进先出),并分配一个唯一的路径名。

  2. 进程打开管道: 创建命名管道后,进程可以通过打开文件系统中的路径名来访问该管道。进程可以像打开普通文件一样打开命名管道,并且可以使用文件描述符来进行读取和写入操作。

  3. 进程读写管道: 一旦管道被打开,进程就可以使用相应的文件描述符进行读取和写入操作。写入到管道的数据会按照先进先出的顺序被读取出来。

  4. 进程关闭管道: 当进程不再需要使用管道时,应该关闭相应的文件描述符,以释放系统资源。

总的来说,命名管道的原理是利用文件系统的特性创建一个特殊类型的文件对象,允许不同进程通过文件进行通信。这种通信方式具有持久性和可靠性,不同进程可以在不同的时间段内进行通信,而不受进程生命周期的限制。

命名管道的打开规则
  • 如果当前打开操作是为读而打开FIFO时
    • O_NONBLOCK disable:阻塞直到有相应进程为写而打开该FIFO
    • O_NONBLOCK enable:立刻返回成功
  • 如果当前打开操作是为写而打开FIFO时
    • O_NONBLOCK disable:阻塞直到有相应进程为读而打开该FIFO
    • O_NONBLOCK enable:立刻返回失败,错误码为ENXIO
用命名管道实现 serve&client 通信

我们先来写一个管道文件,如何在serve 和 client 端分别打开这个管道文件。
在这个管道文件中分别封装 读操作接口写操作接口 以供使用。

namePipe.hpp

#pragma once

#include <iostream>
#include <cstdio>
#include <cerrno>
#include <string>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

const std::string comm_path = "./myfifo";

#define DefaultFd -1
#define Creater 1
#define User 2
#define Read O_RDONLY
#define Write O_WRONLY
#define BassSize 4096

class NamePiped
{
private:
    bool OpenNamePipd(int mode)     //打开对应管道  mode决定打开方式
    {
        _fd = open(_fifo_path.c_str(),mode);
        if(_fd < 0)
        {
            return false;
        }
        return true;
    }

public:
    NamePiped(const std::string &path, int who)
        :_fifo_path(path)
        ,_id(who)
    {
        int res = mkfifo(_fifo_path.c_str(),0666);      //创建命名管道  名字是myfifo 
        if(res != 0)
        {
            //创建失败
            perror("mkfifo");
        }
        std::cout << "Creater create named pipe" << std::endl;
    }

    //创建打开接口
    //读
    bool OpenForRead()
    {
        return OpenNamePipd(Read);
    }
    //写
    bool OpenForWrite()
    {
        return OpenNamePipd(Write);
    }

    //write     输入项参数
    int WriteNamePipe(const std::string &in)
    {
        return write(_fd,in.c_str(),in.size());
    }
    //read      输出型参数
    int ReadNamePipe(std::string *out)
    {
        char buffer[BassSize];
        int n = read(_fd,buffer,sizeof(buffer));

        if(n > 0)
        {
            buffer[n] = 0;
            *out = buffer;
        }
        return n;
    }

    ~NamePiped()
    {
        if(_id == Creater)
        {
            int res = unlink(_fifo_path.c_str());
            if(res != 0)
            {
                perror("unlink");
            }
            std::cout << "creater free named pipe" << std::endl;
        }
        if(_fd != DefaultFd)    close(_fd);
    }
private:
    const std::string _fifo_path;   //命名管道的地址
    int _fd;    //fd  文件描述符
    int _id;    //id  用于判断Creater | User
};

client.cc

#include "namePipe.hpp"

//write 写
int main()
{
    NamePiped fifo(comm_path,User);

    if(fifo.OpenForWrite())
    {
        std::cout << "client open named pipe done" << std::endl;
        while(true)
        {
            std::cout << "Pleanse Enter>";
            std::string message;
            std::getline(std::cin,message);
            fifo.WriteNamePipe(message);
        }
    }
    return 0;
}

server.cc

#include "namePipe.hpp"

//read 读
int main()
{
    NamePiped fifo(comm_path,Creater);
    if(fifo.OpenForRead())
    {
        std::cout << "server open named pipe done" << std::endl;

        while(true)
        {
            std::string message;
            int n = fifo.ReadNamePipe(&message);

            if(n > 0)
            {
                std::cout << "Client say > " << message << std::endl;
            }
            else if(n == 0)
            {
                std::cout << "Client quit, Server Tool" << std::endl;
            }
            else
            {
                std::cout << "fifo.ReadNamedPipe Error" << std::endl;
                break;
            }
        }
    }
    return 0;
}

演示:

在这里插入图片描述

命名管道和匿名管道的区别
  • 匿名管道由pipe函数创建并打开。
  • 命名管道由mkfifo函数创建,由open函数打开。
  • FIFO(命名管道)与pipe(匿名管道)之间唯一的区别在于它们创建与打开的方式不同,一旦这些工作完成之后,它们具有相同的语义。

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

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

相关文章

K210开发板MicroPython开发环境搭建

一、安装CanMV IDE开发软件 1、进入如下连接 https://developer.canaan-creative.com/resource 2、点击下载 3、下一步 4、修改安装路径&#xff0c;下一步 5、接受许可下一步 6、下一步 7、安装 8、完成 9、区域①菜单栏&#xff1a;操作文件&#xff0c;使用工具等。…

HCIP【VLAN综合实验】

目录 一、实验拓扑图&#xff1a; 二、实验要求&#xff1a; 三、实验思路&#xff1a; 四、实验步骤&#xff1a; 1、在交换机SW1,SW2,SW3配置VLAN和各个接口对应类型的配置 2、在路由器上面配置DHCP服务 一、实验拓扑图&#xff1a; 二、实验要求&#xff1a; 1、PC1 …

【动态规划五】回文串问题

目录 leetcode题目 一、回文子串 二、最长回文子串 三、分割回文串 IV 四、分割回文串 II 五、最长回文子序列 六、让字符串成为回文串的最少插入次数 leetcode题目 一、回文子串 647. 回文子串 - 力扣&#xff08;LeetCode&#xff09;https://leetcode.cn/problems/…

Linux网络编程——HTTP协议的理解与运用

目录 前言 一、认识URL 二、认识HTTP样例 三、HTTP的报头内容 1.url 2. Content-Type 3.Method 方法 1.GET方法 2.POST方法 4、状态码 5.cookie和session 前言 我们知道&#xff0c;协议就是一种约定&#xff0c;客户端与服务端统一的用这种约定进行传输数据。我们…

deepin V23 RC 正式发布!

deepin 是一款基于 Linux 的开源桌面操作系统&#xff0c;就在今天&#xff0c;deepin V23 RC 来了&#xff0c;欢迎体验与反馈&#xff01; 感谢每一位 deepiner 提供想法与建议&#xff0c;让我们一起为打造美观易用、安全可靠的开源操作系统而努力&#xff01; 重要提示&a…

用docker命令行操作远程的Dockerd daemon服务

本地安装 Dockerd 服务太耗本机磁盘空间了&#xff0c;共用已有的Dockerd服务能够节省一部分空间 修改 Dockerd 服务启动文件&#xff0c;增加TCP监听方式 Dockerd 服务默认监听方式为 Unix Domain Socket &#xff0c;只允许本机连接&#xff0c;想要能够远程连接&#xff0…

一文说通用户故事点数是什么?

一文说通用户故事点数是什么&#xff1f; 第26期&#xff1a;一文说通用户故事点数是什么&#xff1f; 用户故事点数是一种采用相对估算法进行估算的一种工具&#xff0c;一般采用斐波那契数列表征用户故事里说的大小&#xff0c;采用0 1 2 3 5 8 13这样的一些数字来表征用户…

使用人人开源renren-fast快捷搭建后台管理系统

https://gitee.com/renrenio/renren-fast https://gitee.com/renrenio/renren-fast 初始化项目数据库 导入项目运行 期间遇到的坑 024-04-25 01:30:27.638 ERROR 25228 --- [ main] com.alibaba.druid.pool.DruidDataSource : init datasource error, url: jdbc:…

Postman基础功能-接口返回值获取

大家好&#xff0c;之前给大家分享关于Postman的接口关联&#xff0c;我们平时在做接口测试时&#xff0c;请求接口返回的数据都是很复杂的 JSON 数据&#xff0c;有着多层嵌套&#xff0c;这样的数据层级在 Postman 中要怎么获取呢&#xff1f; 接下来给大家展示几个获取 JSO…

计算机系列之排序算法

20、排序算法 1、直接插入排序&#xff08;这里以从小到大排序为例&#xff09; ◆要注意的是&#xff0c;前提条件是前i-1个元素是有序的&#xff0c;第i个元素依次从第i-1个元素往前比较&#xff0c;直到找到一个比第i个元素值小的元素&#xff0c;而后插入&#xff0c;插入…

免费SSL证书:适合你的网站吗?

随着互联网的发展&#xff0c;安全性已经成为了网站运营不可或缺的一部分。而SSL证书作为保障网站数据传输安全的重要手段&#xff0c;被越来越多的网站所采纳。然而&#xff0c;对于很多小型网站或者个人博客来说&#xff0c;付费购买SSL证书可能会带来一定的经济压力。因此&a…

Linux---编辑器vim的认识与简单配置

前言 我们在自己的电脑上所用的编译软件&#xff0c;就拿vs2022来说&#xff0c;我们可以在上面写C/C语言、python、甚至java也可以在上面进行编译&#xff0c;这种既可以用来编辑、运行编译&#xff0c;又可以支持很多种语言的编译器是一种集成式开发环境&#xff0c;集众多于…

UIKit之图片浏览器

功能需求 实现一个图片浏览器&#xff0c;点击左右按钮可以切换背景图&#xff0c;且更新背景图对应的索引页和图片描述内容。 分析&#xff1a; 实现一个UIView的子类即可&#xff0c;该子类包含多个按钮。 实现步骤&#xff1a; 使用OC语言&#xff0c;故创建cocoa Touch类…

解决kali Linux安装后如何将语言修改为中文

开启虚拟机 用root用户进入终端 进入终端执行dpkg-reconfigure locales命令 选择en_US.UTF-8 UTF-8选项&#xff0c;按空格键将其取消。 选择zh_CN.UTF-8 UTP-8&#xff0c;按空格选择&#xff0c;按tab键选择ok。 选择zh_CN.UTF-8字符编码&#xff0c;按tab键选择ok&#xff0…

【JAVA】嵌入式软件工程师-2025校招必备-详细整理

一、Java 基础 1.JDK 和 JRE 有什么区别&#xff1f; jdk&#xff1a;java development kit jre&#xff1a;java runtime Environment jdk是面向开发人员的&#xff0c;是开发工具包&#xff0c;包括开发人员需要用到的一些类。 jre是java运行时环境&#xff0c;包括java虚拟机…

IDEA的妙用

IDEA 安装破解 复制JetbrainsIdesCrack-4.2.jar到安装目录下 修改安装目录下的bin目录的idea64.exe.vmoptions&#xff1a; 最后一行添加&#xff1a;-javaagent:E:\develop\JetBrains\IntelliJ IDEA 2018.3.5\bin\JetbrainsIdesCrack-4.2.jar(注意&#xff1a;使用自己的路…

(2)双指针练习:复写零

复写零 题目链接&#xff1a;1089. 复写零 - 力扣&#xff08;LeetCode&#xff09; 给你一个长度固定的整数数组 arr &#xff0c;请你将该数组中出现的每个零都复写一遍&#xff0c;并将其余的元素向右平移。 注意&#xff1a;请不要在超过该数组长度的位置写入元素。请对输入…

WebSocket or SSE?即时通讯的应用策略【送源码】

最近在研究H5推送&#xff0c;发现除了我们常用的WebSocket以外&#xff0c;其实还有一种协议也能实现H5推送&#xff0c;那就是SSE协议。 而且&#xff0c;当前主流的大模型平台&#xff0c;比如ChatGPT、通义千问、文心一言&#xff0c;对话时采用的就是SSE。 什么是SSE协议…

基于HTML5和CSS3搭建一个Web网页(一)

倘若代码中有任何问题或疑问&#xff0c;欢迎留言交流~ 网页描述 创建一个包含导航栏、主内容区域和页脚的响应式网页。 需求: 导航栏: 在页面顶部创建一个导航栏&#xff0c;包含首页、关于我们、服务和联系我们等链接。 设置导航栏样式&#xff0c;包括字体、颜色和背景颜…

识物扫一扫识别植物怎么做?6个软件教你轻松识别植物

识物扫一扫识别植物怎么做&#xff1f;6个软件教你轻松识别植物 识别植物可以通过专门的植物识别应用来实现。以下是六款可以帮助您轻松识别植物的软件&#xff1a; 1.一键识别王&#xff1a;这款软件有着强大的植物识别服务&#xff0c;用户可以通过拍照或上传图片来识别植物…