【Linux下】进程间通信

news2025/1/15 17:09:57

文章目录

    • 进程间通信
        • 进程间通信的目的
        • 进程间通信的分类
        • 进程间通信的本质
      • 管道
        • 初识管道
        • 匿名管道
          • 创建匿名管道
          • 理解协同机制和原子性写入
        • 命名管道
          • 命名管道创建的俩种方式
          • 使用命名管道实现俩个不同进程之间通信
        • **管道实现进程间通信的本质**
        • 匿名管道vs命名管道
      • system V
        • system V共享内存
          • 查看system V资源的命令行指令
          • 创建或获取共享内存
          • 释放共享内存
            • 法一:使用系统调用接口shmctl
            • **法二:使用指令 ipcrm **
          • 内存空间特点即对比管道
          • 相关概念补充

进程间通信

进程间通信的目的

一个进程就是一个执行流,就像工厂的员工一样,一个人就是一个生产力,肯定是需要交互的,而进程间的交互可能就是进程通信,可能是因为数据传输需要,也可能是一个进程控制另一个进程…

主要目的有以下4种:

  • 数据传输:一个进程需要将它的数据发送给另一个进程

  • 资源共享:多个进程之间共享同样的资源。

  • 通知事件:一个进程需要向另一个或一组进程发送消息,通知它(它们)发生了某种事件(如进程终止时要通知父进程)。

  • 进程控制:有些进程希望完全控制另一个进程的执行(如Debug进程),此时控制进程希望能够拦截另一个进程的所有陷入和异常,并能够及时知道它的状态改变

进程间通信的分类

管道

  • 匿名管道pipe

  • 命名管道

System V IPC

  • System V 消息队列

  • System V 共享内存

  • System V 信号量

POSIX IPC

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

进程间通信的本质

我们知道进程是具有独立性的,例如:父子进程之间共用一个全局变量,实际上进行修改时,父子进程之间会发生写时拷贝。

而进程间如果要实现通信,就得先让不同的进程看到同一份资源,俩进程看到资源肯定不是单独一个进程所拥有的,而是被所有进程所共享的,所以这个资源一定不可能由进程自身进行提供,那应该由谁提供呢?当然是os!而且所谓的资源实际上就是一段内存

因为提供的方式不一样;就形成了不同的通信方式:文件形式(管道)链表形式(消息队列)原生内存(共享内存)

管道

初识管道

什么是管道?

  • 管道是Unix中最古老的进程间通信的形式。
  • 我们把从一个进程连接到另一个进程的一个数据流称为一个“管道”

例:

例如我们在命令行执行 who | wc -l 实际上就是在使用管道

匿名管道

特点:

1.单向传输的管道

2.管道是面向字节流的(调用系统接口 open read write)

3.父子进程间血缘依赖关系,即匿名管道其实是含有继承关系的进程之间通信的俩个文件,读端文件 写端文件)

4.管道的生命周期是随进程的

5.管道自带协同机制(同步和互斥),原子性写入

创建匿名管道
int pipe(int pipdfd[2])

作用创建并打开匿名管道

参数fd文件描述符数组 里面装到分别是父子进程读写俩端的文件

返回值:成功即返回0 失败返回-1

如图中所描述:

pipefd[0]:读端的文件的fd

pipefd[1]:写端的文件的fd

灵活记忆:0就像嘴一样,负责吃东西,所以是读取,1就像🖊一样,用来写字,所以是写端

而实际上双端进行通信时,因为管道是面向字节流的,俩方只需要使用read write对管道进行读取即可

调用pipe的实质是,进程打开管道文件,将读写端的struct file*填入进程task_struct的fd_array[]中去

例:父子通信:父进程做读端,子进程做写端

实现方法:

父进程打开匿名管道文件,再fork子进程,而后父进程关闭写端(pipefd[1]),子进程关闭读端(pipefd[0])

注: 一定是在父进程打开匿名管道文件后,再fork子进程,因为需要俩个进程看到的是同一份资源 ,而如果我们先fork之后再创建并打开匿名管道文件,就不能保证父子进程看到的是同一份资源。

逻辑图理解:

os视角下的匿名管道通信:

进行匿名管道操作的代码:

#include <stdio.h>
#include <unistd.h>
#include <sys/wait.h>
#include <string.h>
#include <stdlib.h>
int main()
{
  int pipe_id[2];
  int ret=pipe(pipe_id);   //打开管道文件
  if(ret)
  {
    perror("pipe");
    return 1;
  }
  //pipe_id[0]为管道读入端 pipe_id[1]为管道写入端
  printf("pipe_id[0]:%d pipe_id[1]:%d \n",pipe_id[0],pipe_id[1]);

  int id =fork();
  if(id<0)
  {
    perror("fork");
    return 2;

  }
  else if(id ==0)
  {
    //child 进行写操作
    //关闭管道读文件
    close(pipe_id[0]);
    int count=0;
    while(1)
    { 
      //sleep(100);  //当写端写得满时 读端需要等待 或者是不写
      char * meg="hello  lsh";
      write(pipe_id[1],meg,strlen(meg));
      count++;
      printf("%d\n",count);
      break;
    }
    close(pipe_id[1]);   //而当写端退出时,读端read会返回0 表示已经读到文件末尾了 即写文件已经退出了
    exit(0);
  }
  else{
    //father
    close(pipe_id[1]);
    while(1)
    {
      sleep(1);
      char buffer[1024*2+1]={0};  //当写端太快时,会停下来等一下读端。或者是读端不读时,写端也会停下来等读端
      ssize_t s=read(pipe_id[0],buffer,sizeof(buffer));
      buffer[s]=0; //将末尾设置为\0
      if(s==0)
      {
        printf("child quit\n");
        break;
      }
      else if(s>0)
      {
          printf("parent get child new:%s\n",buffer);
      }
      else{
        printf("fail \n");
      }
    }
    close(pipe_id[0]);

    int status=0;
    int pid=waitpid(-1,&status,0);
    if(pid>0)
    {
      printf("exit code:%d exit sig:%d \n",(status>>8)&0xFF,status&0x7F);  //但读端退出时,写端还在写入,os 会像写端发送13信号,将其停下来
    }
  }
  return 0;
}

理解协同机制和原子性写入

1.读端读得慢,写端会等待读端读取

验证方法:我们让读端先睡眠20秒,再进行数据读取,写端则每次写入一个字节的内容,而我们会发现,写端写到65536字节时,就会停下来等待读端,而且每次读端至少读走4096字节时,写端才会继续朝管道里继续写入

如图:

可知在linux2.6.11之后的版本下管道大小就是为65536字节

2.读端不读或是直接退出,写端被进程使用13号信号终止

实验方法:写端写入,读端不读或者是直接关闭文件

结果:子进程被系统的信号干掉了

站在os系统的角度: 读端关闭了,而写端还在不断写入,就是在不断的往管道里输入数据,相当于在浪费os的资源;os系统当然会把他干掉

3.写端很慢,读端会等待写端写入

结果就是写端一写数据,读端就会将数据读出来,继续等待写端写入

4.写端关闭,读端read函数读到文件末尾

结果:read函数返回0,代表读到文件末尾,此处即代表子进程写端已经关闭

小总结

  • 读端和写端是协同工作的,读写端会互相等待

原子性写入

原子性的通俗理解就是:一件事要么不做,要么就一次做完。例如我们打一桶水一次打完就是原子性的,如果分为俩个半桶就是非原子性的

解释:当我们写入的数据少于4096字节时,我们输入是保持原子性写入的,即我们输入的数据会像连着的字符串一样,而当我们写入的数据超过4096字节时,就会采取非原子性写入原则,即我们写入的数据并不会保持连续状,而是会和其他进程输入的数据打乱和其他进程的数据一起放入管道中

小结:

  • 匿名管道是自带协同机制的(同步)

命名管道

特点

1.以文件形式让毫无关联的进程看到同一份资源

2.命名管道上的数据是不会写入到磁盘里去的,如图:

3.同匿名管道一样,是单向写入的

命名管道创建的俩种方式

一:命令行命令 mkfifo

直接在命令行进行运行该指令,可以直接生成命名管道

也可以在程序中进行命名管道文件写入

二:系统调用接口 mkfifo

参数1:管道文件存放的路径,参数2:管道文件的默认权限,但依然要受我们系统文件掩码的影响

返回值:成功返回0 失败返回-1 并且设置错误码

//创建管道  在读写端创建管道文件都可 但是当写端关闭时,会和匿名管道一样 读端read会返回0
  6   if(mkfifo(PIPE_NAME,PIPE_commison)<0)
  7   {
  8     perror("mkfifo");
  9     return 1;                                                                               
 10   }

使用命名管道实现俩个不同进程之间通信

上面我们说过进程通信是需要不同的进程看到一份相同的资源,匿名管道采用的父子进程,子进程继承父进程的struct files_struct 里面的fd_array实现的不同进程看到同一份资源。

那么os又是以什么样的方式让俩个进程看到同一个命名管道呢?

而实际上,命名管道让俩个不同的进程看到同一份资源的方法是:路径+文件名(具有唯一标识性)—这也是命名管道为什么要有名字的原因

通过指定路径+文件名(可以唯一的标识该文件),让俩个不相关的进程看到同一个资源,同一块内存,然后进行交互

抽象解释图:

:管道文件在读写端创建都可

头文件包含:

#pragma once
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>

#define PIPE_NAME "./fifo"
#define PIPE_commison 0666

写端代码:

#include "com.h"

int main()
{
  //创建管道  在读写端创建管道文件都可 但是当写端关闭时,会和匿名管道一样 读端read会返回0
  if(mkfifo(PIPE_NAME,PIPE_commison)<0)
  {
    perror("mkfifo");
    return 1;
  }
  //打开管道文件
  int fd=open(PIPE_NAME,O_RDWR);
  if(fd==1)
  {
    printf("fd fail\n");
  }
  while(1)
  {
    printf("#请输入 : ");
    fflush(stdout);
    char buffer[64]={0};
    ssize_t s=read(0,buffer,sizeof(buffer)-1);
    if(s>0)
    {
      buffer[s-1]='\0';
      printf("%s\n",buffer);
      write(fd,buffer,strlen(buffer));
    }
    else{
      perror("read");
      break;
    }
  }
  
  return 0;
}

读端代吗:

#include "com.h"
#include <stdlib.h>
int main()
{
  umask(0000);
  //打开管道文件
  int fd=open(PIPE_NAME,O_RDONLY);
  if(fd<0)
  {
    perror("open");
    return 2;
  }
  while(1)
  {
    sleep(1);
    char buffer[64]={0};
    ssize_t s=read(fd,buffer,sizeof(buffer))-1;
    if(s>0)
    {
        //可以定制服务
      if(strcmp(buffer,"show")==0)
      {
       pid_t id=fork();
       if(id==0)
       {
         execl("/usr/bin/ls","ls",NULL);
         exit(0);
       }
       else{
         //parent
         waitpid(-1,NULL,0);
       }
      }
      else 
      {
        printf("%s\n",buffer);
      } 
    }

    else if(s==0)
    {
      printf("client quit\n");
    }
    else{
      perror("read");
    }
  }
  return 0;
}

管道实现进程间通信的本质

我们现在大致知道了管道实现进程间通信实际上是不同的进程对管道文件进行写入和读取实现的,而本质其底层是怎么实现的呢?

先来回忆一下我之前博客写的,当我们向普通文件写入的过程是怎么样

进程1调用系统调用write将数据拷贝进test.txt的文件缓冲区中,而后系统调用write又会调用驱动层的写方法将数据写入到磁盘里去

所以进程1调用write函数,实际上write函数做了俩步

  1. 将数据拷贝到内核级别的文件缓冲区去
  2. 调用驱动层的写方法将数据写入到磁盘中去

而如果我们将第二步,换成另外一个数据将数据读走,不就实现了进程间通信吗?–俩进程通过os级别文件缓冲区进行通信

而这实际上就是管道通信的实现原理

匿名管道vs命名管道

  • 本质是都是文件的缓冲区进行通信
  • 匿名管道只能用于父子进程通信,而命名管道可以用在父子进程之间,也可以用在不同进程间通信
  • 俩种管道都是面向字节流单向通信的

system V

  • system V通信方式有共享内存消息队列信号量(用来实现进程同步和互斥)
  • 共享内存区是最快的IPC形式。一旦这样的内存映射到共享它的进程的地址空间,这些进程间数据传递不再涉及到内核,换句话说是进程不再通过执行进入内核的系统调用来传递彼此的数据

而下面主要介绍system V共享内存

system V共享内存

共享内存空间示意图

如图:共享内存同前面的管道一般,也是让不同进程看见同一资源

使用共享内存的步骤:

  1. 通过系统调用接口在物理内存里创建出一块内存 --创建共享内存

  2. 通过调用将进程挂接该内存 即在我们的页表进行映射–挂接共享内存

释放共享内存的步骤:

  1. 去挂接

  2. 释放共享内存

查看system V资源的命令行指令

ipcs

作用查看共享内存相关信息

选项

  • -a: 查看信号量,消息队列,共享内存空间相关属性

  • -m: 只查看共享内存

  • -q: 只查看消息队列

  • -s: 只查看信号量


创建或获取共享内存

ftok

作用:创建出key值 (在系统层面进行唯一标识)

pathname: 自定义的路劲名

proj_id: 自定义的id名

返回值:

成功返回0 失败返回-1 并设置错误码

shmget

作用: 开辟共享内存 或者获取共享内存的shmid

key: 即我们上面,通过ftok获取的共享内存在系统层面的标识符 ,不同的进程就是通过相同的key值找到同一个共享内存(注:一个进程挂接的共享内存可能不止一个)

size: 我们所需要的共享内存空间大小,而我们一般开辟都是4kb的整数倍 因为我们os系统开辟都是以内存页(内存页单位大小为:4096 byte)为单位给我们共享内存开辟的

例如: 我们将size填成4097个字节,我们使用起来就是按4097个字节来使用的,而os开辟时实际上并不是直接开辟了4097个字节,而是按照俩个内存页大小给我们开辟的,但我们最后使用起来还是4097个字节,因为如果os背着我们多开空间,可能会造成本该越界访问的操作没有被检测出来(os背不起这个锅),

shmflg:

常用:

IPC_CREAT: 单独使用时,作用为当该共享内存空间不存在时,即创建之,若存在就获取他的shimd

IPC_EXCL: 单独使用没有意义,需和IPC_CREAT起用作用为当该共享内存空间不存在时,即创建之,若存在就返回错误,作用为保证我们使用的是没有被使用过的内存空间


释放共享内存
法一:使用系统调用接口shmctl

shmctl

int id=shmctl(shmid, IPC_RMID, NULL );

目前作用:像命令行的命令一样干掉共享内存

shmid: 前面所说用户级别的标识符(shmget函数的返回值)

cmd: 选项

buf: 通过这个 我们可以获取共享内存的大概信息(输出型参数

**法二:使用指令 ipcrm **

ipcrm

作用:释放共享内存

格式:

ipcrm -m shmid

注:只有在挂接共享空间的进程数为零时 才能ipcrm 将共享内存释放掉


挂接和去挂接共享内存

shmat

作用:将进程与共享内存空间挂起链接 本质是:在进程的页表建立起虚拟地址和共享内存的映射关系

shmid: 前面所说用户级别的标识符 ,(shmget的返回值)

shmaddr:设置参数,目前只要使用NULL即可

shmflg: 同之前一样 是个参数 此处我们只需要用0即可

返回值:

就像我们c语言malloc一样 返回的是虚拟地址,而我们操作共享内存,就像操作字符指针(指向字符数组)一样

例:

char *mem =(char*)shmat(shmid,NULL,0);

shmdt

作用:解挂起 本质是:在进程的页表去除虚拟地址和共享内存的映射关系

参数:就是我们上面获取的mem地址

例:

int dt=shmdt(mem);
内存空间特点即对比管道

特点:

1.内存空间是没有协同机制的 例:读端并不会等待写端写入才进行操作

2.内存空间是双向的,并没有区分写端读端,俩边皆可操作

3.内存空间读写效率更高 ,因为是直接对内存进行操作 (减少了调用系统接口(read,write),即少了俩次拷贝–拷贝到文件缓冲区)

4.内存空间的生命周期是随os的,所以只能使用命令或者通过shmctl进行操作(也是调用系统命令) ,而管道的生命周期是随进程的

key vs shmid

不同之处:

  • **key:**key只是用于,不同进程在寻找同一共享内存时的唯一标识,即用于os层面做唯一标识
  • shmid: os给用户进行管理共享内存的唯一标识,用户用于操作共享内存

相同之处:

  • 俩者都是对共享内存的唯一标识,只是用于不同场景

头文件代码

#pragma once 
#include <stdio.h>
#include <sys/shm.h>
#include <sys/ipc.h>
#include <sys/types.h>
#include <unistd.h>
#define PATH_NAME "./"
#define SIZE 4097
#define PROJ_ID 0x6666

client.c代码:

#include "com.h"

int main()
{
  //不需要再打开命名空间 只需要获取共享内存的标识号shimd 即可
  //获取底层的标识符
  key_t key=ftok(PATH_NAME,PROJ_ID);
  if(key<0)
  {
    perror("ftok");
    return 1;
  }
  //获取shimd 命名空间标识符
  int shmid = shmget(key,SIZE,IPC_CREAT);    //单独使用的作用是 没有创建值 存在则获取之
  printf("shmid:%d\n",shmid);
  if(shmid<0)
  {
    perror("shmget");
    return 2;
  }

  //挂接共享内存
  char *mem =(char*)shmat(shmid,NULL,0);
  printf("process attach sucess!\n");
  //if(mem<0)
  //{
  //  perror("shmat");
  //  return 3;
  //}
  //操作
  char c='A';
  while(c<'Z')
  {
    mem[c-'A']=c;
    ++c;
    mem[c-'A']=0;
    sleep(1);
  }
  //去挂接
  int dt=shmdt(mem);
  if(dt<0)
  {
    return 4;
  }
  return 0;
}

注:shmget最后一个参数选项,创建共享内存时,一定要把权限设置好 ,不然是进行不了读写操作的

service.c代码:

#include "com.h"
#include <unistd.h>
int main()
{
  key_t key=ftok(PATH_NAME,PROJ_ID);
  printf("key:%p\n",key);
  if(key<0)
  {
    perror("ftok");
    return 1;
  }
  //如果我们的共享内存已经开辟出来了 就会返回-1 
  int shmid=shmget(key,SIZE,IPC_CREAT|IPC_EXCL|0666);  //俩个选线一起用代表 如果该内存空间已经被人开辟了 即返回错误 若是没有被创建 即创建之 
  printf("shmid:%d\n",shmid);
  //sleep(10);
  //将内存空间与该进程挂起联系 建立起映射
  char *mem=(char*)shmat(shmid,NULL,0);
  printf("mem attach !\n");
  //操作
  while(1)
  {
    sleep(1);
    printf("%s\n",mem); //对进程来说里面的内容就是长字符串
  }

  //去挂起
  int dt=shmdt(mem);
  if(dt<0)
  {
    perror("shmdt");
    return 3;
  }
  //关闭内存空间
  int id=shmctl(shmid, IPC_RMID, NULL );
  if(id<0)
  {
    perror("shmctl");
    return 2;
  }
  //sleep(10);
  printf("shmid:%d -> key: %d rm success\n",shmid,key);
  return 0;
}

相关概念补充

四大概念

  • 临界资源:能被多个执行流同时访问的资源,例如:显示器,以及我们进行进程间通信时的管道 ,共享内存,消息队列等都是临界资源。
  • 临界区:访问临界资源的代码就是临界区
  • 原子性:一个事要么做,要么不做,没有过程即没有中间态,就叫原子性
  • 互斥:任何时刻只允许一个执行流进入临界资源,执行其的临界区

消息队列

是以传输数据为目的

信号量

信号量不是以传输数据为目的的,而是通过“共享资源”的方式,来达到多个进程同步和互斥的目的的

信号量本质是一个计数器:衡量临界资源数目的

信号量也是临界资源,为了保证其安全性,所以其的写入是保持原子性的

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

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

相关文章

【Linux】进程概述和进程状态转换(查看进程、实时显示进程动态、杀死进程等)

目录 进程概述进程状态转换进程的状态进程相关命令 橙色 进程概述 进程是正在运行的程序的实例&#xff0c;是基本的分配单元也是基本的执行单元。 可以用一个程序来创建多个进程&#xff0c;进程是由内核定义的抽象实体&#xff0c;并为该实体分配用以执行程序的各项系统资源…

【Jmeter快速入门】

Jmeter快速入门 Jmeter快速入门1.安装Jmeter1.1.下载1.2.解压1.3.运行 2.快速入门2.1.设置中文语言2.2.基本用法 Jmeter快速入门 1.安装Jmeter Jmeter依赖于JDK&#xff0c;所以必须确保当前计算机上已经安装了JDK&#xff0c;并且配置了环境变量。 1.1.下载 可以Apache Jm…

git在vs可视化界面下变基操作

vs版本&#xff1a;vs2022 天天都在使用git&#xff0c;听说过变基这个名词&#xff0c;但是并没有实操过变基&#xff0c;正好今天有个同事说起一件事情&#xff1a;提交代码的时候有太多的自动合并&#xff0c;如果需要回退版本&#xff0c;操作起来很困难&#xff0c;理想状…

如何在项目中实现登录时的验证码校验功能?

如何在项目中实现登录时的验证码校验功能&#xff1f; 第一步&#xff1a;创建项目&#xff0c;添加依赖第二步&#xff1a;验证码配置&#xff08;CaptchaConfig类&#xff09;第三步&#xff1a;创建CaptchaController第四步&#xff1a;测试注意问题 这里介绍一款老牌的验证…

使用@Resource注解和@Inject注解

1.Resource和Inject概述 1.1.Resource注解 Resource注解是Java规范里面的&#xff0c;也可以说它是JSR250规范里面定义的一个注解。该注解默认按照名称进行装配&#xff0c;名称可以通过name属性进行指定&#xff0c;如果没有指定name属性&#xff0c;当注解写在字段上时&…

KCC南京成立会议如期举办

1915年9月15日&#xff0c;陈独秀等人在上海创立了《新青年》杂志&#xff0c;掀起了中华新文化思潮。接着&#xff0c;在1919年5月4日&#xff0c;陈独秀、李大钊等人发起了五四运动&#xff0c;唤醒了沉睡多年的东方巨狮。从此&#xff0c;五四青年节便成为了青年人的节日。 …

上市后首份财报,紫燕食品去年净利下降超3成

近日&#xff0c;上海紫燕食品股份有限公司&#xff08;下称“紫燕食品”,603517.SH&#xff09;交出了上市后的首份年报。 去年9月26日&#xff0c;紫燕食品在上交所主板上市&#xff0c;成为继绝味食品&#xff08;603517.SH&#xff09;、周黑鸭&#xff08;01458.HK&#…

Jetpack Compose 中的Deep Linking — Android

Jetpack Compose 中的Deep Linking — Android 在本文中&#xff0c;我们将学习如何在 Jetpack Compose 中轻松实现深度链接。 什么是深度链接&#xff1f; 深层链接允许用户直接从外部来源&#xff08;例如网站或其他应用程序&#xff09;导航到应用程序内的特定内容。 添…

联想笔记本系统更新中断后变成蓝屏怎么U盘重装系统?

联想笔记本系统更新中断后变成蓝屏怎么U盘重装系统&#xff1f;有用户使用的联想笔记本电脑在系统更新的过程中&#xff0c;因为自己进行了一些操作&#xff0c;导致系统更新中断了&#xff0c;重启系统之后变成了蓝屏的情况。那么这个时候怎么去通过U盘重装系统来恢复电脑使用…

2d游戏人物动作实现(C语言)

没有接触制作小游戏前&#xff0c;感觉做游戏很不可思议&#xff0c;游戏里的人物是怎么移动的&#xff0c;怎么攻击&#xff0c;释放技能。。。。。。现在逐渐了解到之后&#xff0c;发现2d游戏人物的动作更多是图片的拼接&#xff0c;动作是否精细&#xff0c;由这个动作的帧…

第四十二章 Unity 下拉框 (Dropdown) UI

本章节我们介绍下拉框 (Dropdown)&#xff0c;我们点击菜单栏“GameObject”->“UI”->“Dropdown”&#xff0c;然后调整它的位置&#xff0c;效果如下 其实它的本质就是一个下拉列表&#xff0c;然后选择列表中的一个选项而已。大家在很多网页中应该可以看到类似的UI元…

asp.net+sqlserver学生学籍管理系统

1.系统登录模块&#xff1a;为了保证系统的安全性和保密性&#xff0c;便于用户的管理&#xff0c;对用户设置权限。 界面上需要输入用户名、密码、验证码以及用户类型。 用户类型&#xff1a;普通用户和管理员用户。 2.用户信息管理模块&…

Solr(4):Solr索引库说明及创建

1 索引库概述 索引库类似于mysql的数据库&#xff0c;所以如果要使用Solr必须创建一个索引库才能使用 2 使用solr管理页面去创建【不推荐】 2.1 打开solr的管理页面 2.2 点击add Core name&#xff1a;自定义名字 建议和instanceDir目录保持一样instanceDir&#xff1a;实例名…

两大巨头强强联手,为亿万旅行者提供完美旅程

‍数据智能产业创新服务媒体 ——聚焦数智 改变商业 这个“五一”你买到票了么&#xff1f; 今年五一火爆程度可谓“盛况空前”&#xff0c;4月28号&#xff08;放假前一天&#xff09;上海单日通往全国所有车站的车票售罄&#xff0c;这是115年以来首次出现这种情况。不仅是上…

javaScript---设计模式-提高复用性

目录 1、提高复用性的设计模式 2、基本结构与应用示例 2.1 享元模式 2.2 桥接模式 2.3 模板方法模式 2.4 JS的组合与继承 提高复用性的目的&#xff1a;①遵循DRY&#xff08;Dont Repeat Yourself&#xff09;原则&#xff1b;②减少代码量&#xff0c;节省开销 什么是好的复用…

Android安装apk出现 “安装包无效”或“安装包不兼容”的解决方案

Android 安装apk出现“安装包无效”或“安装包不兼容”解决方案 1. 问题出现2. 配置 build.gradle3. 生成Signed APK 1. 问题出现 使用Android Studio安装apk到手机一切正常&#xff0c;但是分享出去出现安装apk出现“安装包无效”或“安装包不兼容”问题 这种情况需要我们设…

MySQL之从一条记录说起 【InnoDB 记录结构下篇】

前言 本文章收录在MySQL性能优化原理实战专栏&#xff0c;点击此处查看更多优质内容。 本文摘录自 ▪ 小孩子4919《MySQL是怎样运行的&#xff1a;从根儿上理解MySQL》 上一篇文章讲了InnoDB有Compact、Redundant、Dynamic、Compressed4种⾏格式&#xff0c;其中主要讲了Com…

Java——按之字形顺序打印二叉树

题目链接 牛客在线oj题——按之字形顺序打印二叉树 题目描述 给定一个二叉树&#xff0c;返回该二叉树的之字形层序遍历&#xff0c;&#xff08;第一层从左向右&#xff0c;下一层从右向左&#xff0c;一直这样交替&#xff09; 数据范围&#xff1a;0≤n≤1500, 树上每个…

深入浅出字符编码

本文对应视频链接&#xff1a; 深入浅出字符编码 什么是编码格式 从一个小问题引入 我们在学习C语言的时候&#xff0c;有一道必做的题目是将大写字母转换成小写&#xff0c;相信有点基础的同学都能不加思索的写出下面的代码&#xff1a; char toLower(char upper){if (up…

大数据Doris(十四):数据模型的选择建议和列定义建议

文章目录 数据模型的选择建议和列定义建议 一、数据模型的选择建议 1、Aggregate数据模型选择<