我与Linux的爱恋:进程间通信 匿名管道


外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

🔥个人主页guoguoqiang. 🔥专栏Linux的学习

Alt

文章目录

  • 匿名管道
  • pipe

匿名管道

匿名管道(Anonymous Pipes)是Unix和类Unix操作系统中的一种通信机制,用于在两个进程之间传递数据。匿名管道通常用于命令行工具之间的数据传递;

匿名管道的工作原理是创建一个临时文件,该文件被称为管道文件,它仅存在于内存中,不持久化到磁盘。当一个进程创建一个匿名管道时,它会打开一个写入端和一个读取端。写入端通常由|运算符创建,而读取端则通过<运算符

匿名管道的一个关键特性是它是单向的,即只能从写入端到读取端传递数据。此外,一旦管道中的数据被读取,管道就会被关闭,不能再次使用。

1.概念与原理
匿名管道是进程间通信(IPC)的一种方式,主要用于具有父子关系的进程之间传递数据。它是在内核中开辟的一块缓冲区,作为进程间单向数据传输的通道。当一个进程(通常是父进程)调用pipe()系统调用时,内核会创建这个匿名管道,并返回两个文件描述符,一个用于写入数据(管道的写端),一个用于读取数据(管道的读端)。
例如,在 C 语言的 Linux 系统编程中,代码可能如下:
在上述代码中,首先通过pipe()函数创建匿名管道,得到pipefd数组,其中pipefd[0]是读端文件描述符,pipefd[1]是写端文件描述符。然后通过fork()函数创建子进程。子进程关闭写端,从读端读取数据;父进程关闭读端,向写端写入数据。
2.特点
半双工通信:数据只能在一个方向上流动。这意味着在某一时刻,要么是数据从写端流向读端,要么是通过创建另外的管道来实现相反方向的通信。
基于父子进程关系:匿名管道依赖于父子进程之间的继承关系。子进程通过继承父进程的文件描述符来访问管道,所以它主要用于有亲缘关系的进程之间的通信。
临时性:匿名管道是没有名字的,并且在最后一个使用它的进程结束后自动消失。它没有在文件系统中留下持久的标记,这与命名管道不同。
3.性能和效率方面
匿名管道的效率相对较高,因为它是在内核缓冲区中直接进行数据传递。不过,由于它是半双工的,在需要双向通信的复杂场景下,可能需要创建多个管道,这可能会增加系统开销。
它的读写操作通常是阻塞式的。当管道为空时,读操作会阻塞,直到有数据写入管道;当管道满时,写操作会阻塞,直到有空间可写。这种阻塞特性有助于简单地协调父子进程之间的数据传输,但在某些对实时性要求较高的场景下可能需要特殊处理,如使用非阻塞 I/O 或多路复用技术。
4.应用场景
在 Unix/Linux 系统的命令行操作中,匿名管道应用广泛。例如,ls -l | grep "txt"命令组合。ls -l命令会列出文件的详细信息,它的输出通过匿名管道传递给grep "txt"命令,grep命令则在这些输出中筛选出包含"txt"的行。这种命令组合方式可以方便地对一个命令的输出进行过滤、排序等操作,是匿名管道在实际系统操作中的典型应用。

pipe

在C语言中,pipe函数是用于创建一个匿名管道(也称为管道)的标准库函数。它允许进程之间通过管道进行通信。pipe函数的声明如下:
在这里插入图片描述
这个函数的作用是在调用进程和其子进程之间创建一个匿名管道。pipefd是一个整数数组,包含两个整数元素,分别用于读取和写入管道。

pipefd[0]:这是管道的读取端,可以通过它从管道中读取数据。

pipefd[1]:这是管道的写入端,可以通过它将数据写入管道。

pipe函数的返回值:

如果成功,pipe函数返回0。

如果失败,pipe函数返回-1,并设置errno以指示错误。

成功调用pipe函数后,返回的两个文件描述符pipefd[0]和pipefd[1]可以用于后续的读取和写入操作。
这个函数的作用是在调用进程和其子进程之间创建一个匿名管道。pipefd是一个整数数组,包含两个整数元素,分别用于读取和写入管道。

pipefd[0]:这是管道的读取端,可以通过它从管道中读取数据。

pipefd[1]:这是管道的写入端,可以通过它将数据写入管道。

pipe函数的返回值:

如果成功,pipe函数返回0。

如果失败,pipe函数返回-1,并设置errno以指示错误。

成功调用pipe函数后,返回的两个文件描述符pipefd[0]和pipefd[1]可以用于后续的读取和写入操作。

  1. pipe系统调用的基本概念
    • 在Unix和类Unix系统(如Linux)中,pipe是一个重要的系统调用,用于创建匿名管道。它的主要功能是在内核中开辟一块缓冲区,这个缓冲区用于在进程之间单向传输数据。
    • 当调用pipe系统调用时,它会返回一个包含两个整数的数组(在C语言等编程语言中),这两个整数代表两个文件描述符。例如,在C语言代码中:
   #include <unistd.h>
   int pipefd[2];
   int result = pipe(pipefd);
  • 这里pipefd[0]是管道的读端文件描述符,用于从管道中读取数据;pipefd[1]是管道的写端文件描述符,用于向管道中写入数据。
  1. pipe函数的参数和返回值
    • 参数pipe函数通常只接受一个参数,即一个用于存储两个文件描述符的数组的地址。在C语言中,这个数组的类型是int类型的数组,大小为2。例如上面代码中的pipefd数组。
    • 返回值:如果pipe调用成功,它会返回0,并将两个有效的文件描述符存储在传入的数组中;如果调用失败,它会返回 - 1,并且会设置errno来指示错误原因。常见的错误原因可能包括系统资源不足(如内存不足)或文件描述符数量达到系统限制等。
  2. pipe在进程通信中的应用示例
    • 以下是一个简单的示例,展示了父子进程之间如何通过pipe创建的匿名管道进行通信:
   #include <stdio.h>
   #include <stdlib.h>
   #include <unistd.h>
   int main() {
       int pipefd[2];
       pid_t pid;
       char buffer[100];
       // 创建管道
       if (pipe(pipefd) == -1) {
           perror("pipe");
           return 1;
       }
       // 创建子进程
       pid = fork();
       if (pid == -1) {
           perror("fork");
           return 1;
       } else if (pid == 0) {
           // 子进程
           close(pipefd[1]);// 关闭写端
           int n = read(pipefd[0], buffer, sizeof(buffer));
           if (n > 0) {
               buffer[n] = '\0';
               printf("子进程读取到: %s", buffer);
           }
           close(pipefd[0]);
       } else {
           // 父进程
           close(pipefd[0]);// 关闭读端
           char message[] = "这是父进程发送的数据";
           write(pipefd[1], message, sizeof(message));
           close(pipefd[1]);
       }
       return 0;
   }
  • 在这个示例中,首先通过pipe创建了匿名管道,然后使用fork创建子进程。子进程关闭写端,从读端读取数据;父进程关闭读端,向写端写入数据,从而实现父子进程之间的单向通信。
  1. pipe与其他进程通信机制的比较
    • 与命名管道(FIFO)相比,pipe创建的匿名管道没有名字,只能用于有亲缘关系(父子进程)之间的通信,而命名管道可以用于无亲缘关系的进程之间的通信。
    • 与消息队列相比,pipe是基于文件描述符的简单数据传输机制,主要用于单向的字节流传输;消息队列则是更灵活的消息传递机制,它可以按消息类型接收和发送,并且可以在多个进程之间实现全双工通信(通过合适的设计)。
    • 与共享内存相比,pipe通过缓冲区进行数据传输,进程间的数据交换相对间接;共享内存则是让多个进程直接共享一块内存区域,数据交换更加直接,但需要更好的同步机制来避免数据冲突。
      在这里插入图片描述
      当需要进行通信时,需要通过pipefd[1]文件描述符,将数据拷贝到管道文件中;再通过pipefd[0]文件描述符,将管道文件中的数据拷贝到用户空间中。因而,管道通信时,需要产生两次拷贝。

我们简单测试一下返回的文件描述符

#include <iostream>
#include <string>
#include <cerrno>  // errno.h
#include <cstring> // string.h
#include <unistd.h>
 
int main(){
    //1.创建管道
    int pipefd[2];
    int n = pipe(pipefd);//输出型参数,rfd,wfd
 
    if(n != 0){
        std::cerr << "errno: " << errno << ": "<< "errstring : " << strerror(errno) << std::endl;
        return 1;
    }
 
    std::cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << std::endl;
 
    return 0;
}

在这里插入图片描述
接下来让子进程写入数据,父进程读数据,在此期间会关闭不需要的fd

#include <iostream>
#include <string>
#include <cerrno>  // errno.h
#include <cstring> // string.h
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>
 
const int size = 1024;
std::string getOtherMessage()
{
    static int cnt = 0;
    std::string messageid = std::to_string(cnt); // stoi -> string -> int
    cnt++;
    pid_t self_id = getpid();
    std::string stringpid = std::to_string(self_id);
 
    std::string message = "messageid: ";
    message += messageid;
    message += " my pid is : ";
    message += stringpid;
 
    return message;
}
 
// 子进程进行写入
void SubProcessWrite(int wfd)
{
    int pipesize = 0;
    std::string message = "father, I am your son prcess!";
    char c = 'A';
    while (true)
    {
        std::string info = message + getOtherMessage(); // 这条消息,就是我们子进程发给父进程的消息
        write(wfd, info.c_str(), info.size()); // 写入管道的时候,没有写入\0, 有没有必要?没有必要
        
        std::cerr << info << std::endl;
        
         sleep(2); // 子进程写慢一点
        // write(wfd, &c, 1);
        // std::cout << "pipesize: " << ++pipesize << " write charator is : "<< c++ << std::endl;
        // // if(c == 'G') break;
 
        // sleep(1);
    }
 
    std::cout << "child quit ..." << std::endl;
}
 
// 父进程进行读取
void FatherProcessRead(int rfd)
{
    char inbuffer[size]; // c99 , gnu g99
    while (true)
    {
        //sleep(2);
        std::cout << "-------------------------------------------" << std::endl;
        // sleep(500);
        ssize_t n = read(rfd, inbuffer, sizeof(inbuffer) - 1); // sizeof(inbuffer)->strlen(inbuffer);
        if (n > 0)
        {
            inbuffer[n] = 0; // == '\0'
            std::cout  << inbuffer << std::endl;
        }
        else if (n == 0)
        {
            // 如果read的返回值是0,表示写端直接关闭了,我们读到了文件的结尾
            std::cout << "client quit, father get return val: " << n << " father quit too!" << std::endl;
            break;
        }
        else if(n < 0)
        {
            std::cerr << "read error" << std::endl;
            break;
        }
 
        // sleep(1);
         break;
    }
}
 
int main(){
    //1.创建管道
    int pipefd[2];
    int n = pipe(pipefd);//输出型参数,rfd,wfd
 
    if(n != 0){
        std::cerr << "errno: " << errno << ": "<< "errstring : " << strerror(errno) << std::endl;
        return 1;
    }
 
    std::cout << "pipefd[0]: " << pipefd[0] << ", pipefd[1]: " << pipefd[1] << std::endl;
 
    // 2. 创建子进程
    pid_t id = fork();
    if (id == 0)
    {
        std::cout << "子进程关闭不需要的fd了, 准备发消息了" << std::endl;
        sleep(1);
        // 子进程 --- write
        // 3. 关闭不需要的fd
        close(pipefd[0]);
 
        SubProcessWrite(pipefd[1]);
        close(pipefd[1]);
        exit(0);
    }
 
    // 3. 父进程读入数据
    // 关闭不需要的fd
    close(pipefd[1]);
    FatherProcessRead(pipefd[0]);
    std::cout << "5s, father close rfd" << std::endl;
    sleep(5);
    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 child process done, exit code(ign): " << ((status>>8)&0xFF) << std::endl;
    }
    
    return 0;
}

在这里插入图片描述

管道的四种情况 和5种特征
特征一 匿名管道:只能用来进行具有血缘关系的进程之间,进行通信,常用于父子进程之间通信
情况一 如果管道内部是空的 并且 write fd没有关闭,读取条件不具备,读进程会被阻塞,解决方案,等待读取条件具备,也就是写入数据
特征二 管道内部,自带进程之间同步的机制–>多执行流执行代码的时候,具有明显的顺序性

我们让子进程写慢一点,父进程持续去读

void  SubProcessWrite(int wfd){
    int pipesize=0;
    std::string message="father , I am your son process!";
    char a='A';
    while(true){
        std::string info =message+getOtherMessage();//子进程发给父进程的信息
        write(wfd,info.c_str(),info.size());//写入管道的时候,没有写入\0,有没有必要?没有必要
        std::cerr<<info<<std::endl;
        sleep(5);
    }
    std::cout<<"child quit..."<<std::endl;
}
//父进程进行读取
void  FatherProcessRead(int rfd){
    char inbuffer[size];//c99 
    while(true){
        //sleep(2)
        std::cout<<"-----------------------------"<<std::endl;
        ssize_t n=read(rfd,inbuffer,sizeof(inbuffer)-1);//sizeof(inbuffer)->==strlen(inbuffer)
        if(n>0){
            inbuffer[n]=0;//=='\0'
            std::cout<<inbuffer<<std::endl;
        }
        else if(n==0){
            //如果read的返回值为0,表示写端关闭了,我们读到了文件的结尾
            std::cout<<"client quit,father get return val:"<<n<<"father quit too!"<<std::endl;
            break;
        }
        else if(n<0){
            std::cerr<<"read error"<<std::endl;
            break;
        }
        //break;
    }
}

在这里插入图片描述
现象就是,每隔5秒打印一次,也就是父进程读取要和子进程一致,子进程慢了,父进程就要读慢点,子进程写一条,父进程读一条;在5s期间,父进程就在等待子进程写数据,也就是读进程被阻塞(这里的这个情况就好像之前我们的scanf)
情况二 管道被写满,并且父进程不读且不关闭;管道被写满会被阻塞,如果要恢复正常就需要父进程读取数据(让写条件具备)
我们接下来让子进程疯狂写,父进程不读

void  SubProcessWrite(int wfd){
    int pipesize=0;
    std::string message="father , I am your son process!";
    char a='A';
    while(true){
        write(wfd,&a,1);
        std::cout<<"pipesize:"<<++pipesize<<"write charator is:"<<a++<<std::endl;
    }
    std::cout<<"child quit..."<<std::endl;
}

现象就是子进程写到第65536就一直卡在哪里,我们父进程一直在,但是不读,这里我们也就可以计算出管道文件的大小65536/1024 = 64kb,也就是ubuntu下22.04管道大小是64kb
在这里插入图片描述
情况三 管道一直在读但是写端关闭了,读端read返回值会读到0,表示读到了文件结尾
我们让子进程写一条语句就关闭,父进程一直读

void  SubProcessWrite(int wfd){
    int pipesize=0;
    std::string message="father , I am your son process!";
    char a='A';
    while(true){
        write(wfd,&a,1);
        std::cout<<"pipesize:"<<++pipesize<<"write charator is:"<<a++<<std::endl;
        break;
    }
    std::cout<<"child quit..."<<std::endl;
}
void  FatherProcessRead(int rfd){
    char inbuffer[size];//c99 
    //sleep(500);
    while(true){
        //sleep(2)
        std::cout<<"-----------------------------"<<std::endl;
        ssize_t n=read(rfd,inbuffer,sizeof(inbuffer)-1);//sizeof(inbuffer)->==strlen(inbuffer)
        if(n>0){
            inbuffer[n]=0;//=='\0'
            std::cout<<inbuffer<<std::endl;
        }
        else if(n==0){
            //如果read的返回值为0,表示写端关闭了,我们读到了文件的结尾
            std::cout<<"client quit,father get return val:"<<n<<"father quit too!"<<std::endl;
            //break;
        }
        else if(n<0){
            std::cerr<<"read error"<<std::endl;
            break;
        }
        //break;
    }
}

在这里插入图片描述
情况四 读端直接关闭 写端一直写–>写端进程会被操作系统直接使用13号信号关掉,相当于进程出现了异常

void  SubProcessWrite(int wfd){
    int pipesize=0;
    std::string message="father , I am your son process!";
    while(true){
        char a='A';
        write(wfd,&a,1);
        std::cout<<"pipesize:"<<++pipesize<<"write charator is:"<<a++<<std::endl;
        //break;
        // std::string info =message+getOtherMessage();//子进程发给父进程的信息
        // write(wfd,info.c_str(),info.size());//写入管道的时候,没有写入\0,有没有必要?没有必要
        // std::cerr<<info<<std::endl;
        //sleep(5);
    }
    std::cout<<"child quit..."<<std::endl;
}
//父进程进行读取
void  FatherProcessRead(int rfd){
    char inbuffer[size];//c99 
    //sleep(500);
    while(true){
        //sleep(2)
        std::cout<<"-----------------------------"<<std::endl;
        ssize_t n=read(rfd,inbuffer,sizeof(inbuffer)-1);//sizeof(inbuffer)->==strlen(inbuffer)
        if(n>0){
            inbuffer[n]=0;//=='\0'
            std::cout<<inbuffer<<std::endl;
        }
        else if(n==0){
            //如果read的返回值为0,表示写端关闭了,我们读到了文件的结尾
            std::cout<<"client quit,father get return val:"<<n<<"father quit too!"<<std::endl;
            break;
        }
        else if(n<0){
            std::cerr<<"read error"<<std::endl;
            break;
        }
        break;
    }
}

在这里插入图片描述
特征三 管道文件的生命周期是随进程的
特征四 管道文件在通信的时候,是面向字节流的,写入的次数和读取的次数不是一一匹配的

  1. 面向字节流的含义
    • 当管道作为进程间通信的方式时,它是面向字节流的。这意味着数据在管道中是以字节序列的形式存在和传输的,没有固定的消息格式或记录边界。就像文件读写操作中的字节流一样,数据是连续的字节序列。
    • 例如,一个进程可以将任意长度的字节数据写入管道,无论是一个字符、一个字符串,还是一个包含复杂数据结构的二进制数据块。接收进程看到的只是一串连续的字节,它需要根据预先约定的协议或数据格式来解析这些字节,以理解发送方传递的信息。
  2. 写入次数和读取次数不匹配的原因
    • 数据量不一致
      • 写入进程可能一次写入大量的数据,而读取进程可以根据自己的处理能力和需求,分多次读取这些数据。例如,写入进程将一个1000字节的文件内容一次性写入管道,读取进程的缓冲区大小只有100字节,那么它就需要读取10次才能将管道中的数据全部读完。
      • 相反,写入进程也可以分多次写入少量数据,而读取进程可以一次读取这些累积的数据。比如写入进程每次写入10字节,共写入10次,读取进程可以在管道中有足够数据后一次性读取100字节。
    • 读写速度差异
      • 写入进程和读取进程的执行速度不同。如果写入进程写入数据的速度很快,而读取进程处理数据的速度较慢,那么在管道中就会积累数据。写入进程可能已经完成了多次写入操作,而读取进程还在处理之前写入的数据。
      • 例如,在一个生产者 - 消费者模型中,生产者进程(写入进程)快速生产数据并写入管道,消费者进程(读取进程)可能因为某些复杂的处理(如数据解密、格式转换等)而缓慢地从管道中读取数据,导致写入和读取次数不匹配。
    • 数据处理的灵活性需求
      • 读取进程可能根据实际情况灵活地决定读取的时机和数据量。比如,读取进程可能需要等待特定的条件满足后才开始读取数据,或者只读取管道中的部分数据用于当前的处理步骤,剩余的数据留在管道中等待后续读取。这种灵活性使得读取次数不一定与写入次数相对应。
  3. 示例说明
    • 假设有一个管道用于在进程A和进程B之间通信。进程A向管道写入数据:
   char data1[] = "Hello";
   char data2[] = " World";
   write(pipe_write_fd, data1, sizeof(data1));
   write(pipe_write_fd, data2, sizeof(data2));
  • 进程B从管道读取数据:
   char buffer[20];
   int n1 = read(pipe_read_fd, buffer, 5);
   buffer[n1] = '\0';
   printf("第一次读取: %s\n", buffer);
   int n2 = read(pipe_read_fd, buffer, sizeof(buffer));
   buffer[n2] = '\0';
   printf("第二次读取: %s\n", buffer);
  • 在这个例子中,进程A分两次写入数据,而进程B分两次读取数据,但读取的字节数和写入的字节数以及次数并不完全一致。进程B根据自己的需求和缓冲区大小来决定每次读取的数据量。这体现了管道在通信时面向字节流以及写入和读取次数不匹配的特点。

面向字节流:管道文件在通信时,数据是以字节为单位进行传输的。这意味着写入端可以一次写入多个字节,而读取端可以一次读取多个字节,或者可以分多次读取。

写入次数和读取次数不是一一匹配的:由于管道是半双工的,写入端和读取端的数据传输不是同步进行的。这意味着,写入端可能已经写入了多个字节,而读取端还没有开始读取,或者读取端已经读取了部分数据,而写入端还在继续写入。

数据传输是异步的:管道通信是异步的,这意味着写入端和读取端之间的数据传输不一定是连续的。写入端可以写入数据,然后继续执行其他操作,而读取端可以等待数据准备好后再读取。

数据缓冲:管道内部通常有一个缓冲区,用于存储写入端写入的数据。当读取端开始读取时,它会从缓冲区中读取数据。如果缓冲区满了,写入端可能会阻塞,直到有空间可用。如果缓冲区空了,读取端可能会阻塞,直到有数据可读。

管道通信的完整性:尽管写入次数和读取次数不是一一匹配的,但管道通信的完整性得到了保证。写入端写入的数据最终会被读取端读取,反之亦然。

特征五 管道的通信模式,是一种特殊的半双工模式

  1. 半双工模式(Half - Duplex)
    • 定义:半双工通信模式是指数据可以在两个方向上进行传输,但不能同时进行。在某一时刻,通信通道只能用于发送或者接收数据。就好像是一条单车道的道路,车辆(数据)可以往返行驶,但同一时间只能朝着一个方向。
    • 工作原理
      • 以使用半双工模式通信的设备为例,比如对讲机。当一方按下通话按钮(开始发送数据)时,设备进入发送状态,此时接收功能暂时关闭。发送方的声音(数据)通过对讲机的通信频道发送出去,而接收方只能等待发送方结束发送后,才能按下自己的通话按钮进行回复(发送数据)。
      • 在网络通信或计算机进程间通信的半双工管道中,数据的流向也是如此。例如,匿名管道(用于父子进程通信)通常是半双工的。当父进程向管道写入数据时,管道处于写入状态,此时子进程不能同时从同一管道写入数据,必须等待父进程完成写入并且管道状态切换到可读状态后,子进程才能从管道读取数据。
    • 应用场景
      • 在一些简单的通信系统中比较常见,如早期的以太网(采用同轴电缆共享介质)就使用半双工通信。多个设备连接到同一条电缆上,设备在发送数据时会占用整个通信介质,其他设备需要等待它发送完成后才能发送自己的数据。
      • 在一些资源受限或者对通信实时性要求不是特别高的进程间通信场景中也有应用。比如简单的传感器与控制器之间的通信,传感器采集数据后发送给控制器,控制器处理完数据后再发送指令给传感器,两者交替进行通信,半双工模式就可以满足需求。
  2. 全双工模式(Full - Duplex)
    • 定义:全双工通信模式允许数据同时在两个方向上传输。可以将其想象成一条双向多车道的高速公路,车辆(数据)可以同时在两个方向上行驶,互不干扰。
    • 工作原理
      • 在电话通信中就是典型的全双工模式。通话双方可以同时说话和聆听,声音(数据)在两个方向上同时传输。这是因为通信线路被设计成可以同时处理两个方向的信号。
      • 在计算机网络的全双工以太网连接中,设备有独立的发送和接收线路,或者通过复杂的信号处理技术,可以在同一时间进行数据的发送和接收。在进程间通信中,一些通信机制也支持全双工,如消息队列(通过合理设计可以实现双向通信)和套接字(在TCP协议下支持全双工通信)。
    • 应用场景
      • 对于需要同时进行双向、大量数据传输的场景非常关键。例如,在网络视频会议中,参会者既要发送自己的视频和音频数据,又要同时接收其他参会者的视频和音频数据,就需要全双工通信来保证会议的正常进行。
      • 在分布式系统中,服务器和多个客户端之间的复杂交互也经常需要全双工通信。比如,数据库服务器既要接收客户端的查询请求和数据更新请求,又要同时向客户端发送查询结果和状态信息,全双工通信模式能够高效地支持这种复杂的交互。

总结来说,半双工通信需要轮流发送和接收数据,而全双工通信可以同时进行双向通信。全双工通信通常更高效,因为它允许多个设备或通道同时工作,而不需要等待。

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

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

相关文章

Java之JDBC,Maven,MYBatis

前言 就是用来操作数据库的 1.JDBC快速入门 注意在使用前一定要导入jar包 在模块那里新建目录&#xff0c;新建lib&#xff0c;粘贴复制jar包&#xff0c;我这个jar设置的是模块有效 package test1017;import java.sql.Connection; import java.sql.DriverManager; import…

基于Matlab的碎纸片的自动拼接复原技术

碎纸片的自动拼接复原技术 摘要&#xff1a;破碎文件的拼接在司法物证复原、历史文献修复以及军事情报获取等领域都有着重要的应用。目前发现对碎纸片的拼接大部分由人工完成&#xff0c;准确率较高&#xff0c;但耗费大量人力财力及时间&#xff0c;效率很低。随着计算机技术的…

STM32 设计的较为复杂的物联网项目,包括智能家居控制系统,涵盖了硬件和软件的详细设计。

使用 STM32 设计的较为复杂的物联网项目&#xff0c;包括智能家居控制系统&#xff0c;涵盖了硬件和软件的详细设计。 一、硬件设计 微控制器&#xff1a;选择 STM32F4 系列微控制器&#xff0c;如 STM32F407ZGT6&#xff0c;具有高性能和丰富的外设资源。 传感器模块&#x…

1.7 JS性能优化

从输入url到页面加载完成都做了些什么 输入 URL - 资源定位符 http://www.zhaowa.com - http 协议 域名解析 https://www.zhaowa.com > ip 1. 切HOST&#xff1f; > 浏览器缓存映射、系统、路由、运营商、根服务器 2. 实际的静态文件存放&#xff1f; 大流量 > 多个…

LPDDR4芯片学习(四)——DDR Training

一、ZQ Calibration DDR 学习时间 (Part B - 6)&#xff1a;DRAM ZQ 校正 - 知乎 (zhihu.com) 从原理上解释什么是DDR的ZQ校准&#xff1f; - 知乎 (zhihu.com) LPDDR4的训练(training)和校准(calibration)--ZQ校准(Calibration)_wonder_coole-腾讯云开发者社区 01 ZQ校准的…

pycharm分支提交操作

一、Pycharm拉取Git远程仓库代码 1、点击VCS > Get from Version Control 2、输入git的url&#xff0c;选择自己的项目路径 3、点击Clone&#xff0c;就拉取成功了 默认签出分支为main 选择develop签出即可进行开发工作 二、创建分支&#xff08;非必要可以不使用&#xf…

鸿蒙实战:页面跳转

文章目录 1. 实战概述2. 实现步骤2.1 创建项目2.2 准备图片素材2.3 编写首页代码2.4 创建第二个页面 3. 测试效果4. 实战总结 1. 实战概述 实战概述&#xff1a;本实战通过ArkUI框架&#xff0c;在鸿蒙系统上开发了一个简单的两页面应用。首页显示问候语和“下一页”按钮&…

IDEA部署AI代写插件

前言 Hello大家好&#xff0c;当下是AI盛行的时代&#xff0c;好多好多东西在AI大模型的趋势下都变得非常的简单。 比如之前想画一幅风景画得先去采风&#xff0c;然后写实什么的&#xff0c;现在你只需描述出你想要的效果AI就能够根据你的描述在几分钟之内画出一幅你想要的风景…

深入理解 Spark 中的 Shuffle

Spark 的介绍与搭建&#xff1a;从理论到实践_spark环境搭建-CSDN博客 Spark 的Standalone集群环境安装与测试-CSDN博客 PySpark 本地开发环境搭建与实践-CSDN博客 Spark 程序开发与提交&#xff1a;本地与集群模式全解析-CSDN博客 Spark on YARN&#xff1a;Spark集群模式…

常用在汽车PKE无钥匙进入系统的高度集成SOC芯片:CSM2433

CSM2433是一款集成2.4GHz频段发射器、125KHz接收器和8位RISC&#xff08;精简指令集&#xff09;MCU的SOC芯片&#xff0c;用在汽车PKE无钥匙进入系统里。 什么是汽车PKE无钥匙进入系统&#xff1f; 无钥匙进入系统具有无钥匙进入并且启动的功能&#xff0c;英文名称是PKE&…

人力资源招聘系统-提升招聘效率与质量的关键工具

在当今这个竞争激烈的商业环境中&#xff0c;企业要想在市场中立于不败之地&#xff0c;关键在于拥有高素质的人才队伍。然而&#xff0c;传统的招聘方式往往效率低下&#xff0c;难以精准匹配企业需求与人才特质&#xff0c;这无疑给企业的发展带来了不小的挑战。 随着科技的飞…

R语言贝叶斯分析:INLA 、MCMC混合模型、生存分析肿瘤临床试验、间歇泉喷发时间数据应用|附数据代码...

全文链接&#xff1a;https://tecdat.cn/?p38273 多模态数据在统计学中并不罕见&#xff0c;常出现在观测数据来自两个或多个潜在群体或总体的情况。混合模型常用于分析这类数据&#xff0c;它利用不同的组件来对数据中的不同群体或总体进行建模。本质上&#xff0c;混合模型是…

算法--解决二叉树遍历问题

第一 实现树的结构 class Node(): # 构造函数&#xff0c;初始化节点对象&#xff0c;包含数据和左右子节点 def __init__(self, dataNone): self.data data # 节点存储的数据 self.left None # 左子节点&#xff0c;默认为None self.rig…

华为eNSP:MSTP

一、什么是MSTP&#xff1f; 1、MSTP是IEEE 802.1S中定义的生成树协议&#xff0c;MSTP兼容STP和RSTP&#xff0c;既可以快速收敛&#xff0c;也提供了数据转发的多个冗余路径&#xff0c;在数据转发过程中实现VLAN数据的负载均衡。 2、MSTP可以将一个或多个VLAN映射到一个Inst…

从零到一:利用 AI 开发 iOS App 《震感》的编程之旅

在网上看到一篇关于使用AI开发的编程经历&#xff0c;分享给大家 作者是如何在没有 iOS 开发经验的情况下&#xff0c;借助 AI&#xff08;如 Claude 3 模型&#xff09;成功开发并发布《震感》iOS 应用。 正文开始 2022 年 11 月&#xff0c;ChatGPT 诞生并迅速引发全球关注。…

C++__day1

1、思维导图 2、如果登录失败&#xff0c;提示用户登录失败信息&#xff0c;并且提示错误几次&#xff0c;且重新输入&#xff1b;如果输入错误三次&#xff0c;则退出系统 #include <iostream> using namespace std;int main() {string id , pswd;string user"admi…

MySQL45讲 第二十讲 幻读是什么,幻读有什么问题?

文章目录 MySQL45讲 第二十讲 幻读是什么&#xff0c;幻读有什么问题&#xff1f;一、幻读的定义二、幻读带来的问题&#xff08;一&#xff09;语义问题&#xff08;二&#xff09;数据一致性问题 三、InnoDB 解决幻读的方法四、总结 MySQL45讲 第二十讲 幻读是什么&#xff0…

web与网络编程

使用HTTP协议访问Web 通过发送请求获取服务器资源的Web浏览器等&#xff0c;被成为客户端(client)。 Web使用一种名为HTTP(超文本传输协议)的协议作为规范&#xff0c;完成从客户端到服务器端等一系列运作流程。 可以说&#xff0c;Web时建立在HTTP协议上通信的。 网络基础T…

深入理解接口测试:实用指南与最佳实践5.0(五)

✨博客主页&#xff1a; https://blog.csdn.net/m0_63815035?typeblog &#x1f497;《博客内容》&#xff1a;.NET、Java.测试开发、Python、Android、Go、Node、Android前端小程序等相关领域知识 &#x1f4e2;博客专栏&#xff1a; https://blog.csdn.net/m0_63815035/cat…

2024游戏陪玩app源码的功能介绍/线上陪玩交友上线即可运营软件平台源码搭建流程

一个完整的陪玩交友系统从概念到实现再到维护的全过程得以清晰展现。每一步都需要团队的紧密协作与细致规划&#xff0c;以确保系统既满足用户需求&#xff0c;又具备良好的稳定性和可扩展性。 基础框架 移动端开发框架&#xff1a;如uniapp&#xff0c;它支持多平台开发&…