linux进程间的通信(详细且有demo)

2023-11-11

目录

1、进程间通信常用的几种方式 :

2.无名管道:

1.管道的概念:

2.管道的原理 :

3.管道的局限性:

4.创建匿名管道:

5、demo:

6.父子进程使用管道通信 :

7.管道的读写行为:

8.查看管道缓冲区大小

3.有名管道

(1)特点

(2使用场景:

(3)创建方式

4、消息队列

1.特点

2.相关函数

练习:用父子进程实现消息队列的全双工通信

5.共享内存

1.概念

2.相关函数

练习:实现共享内存间的通信

6.信号:

1.信号的发送(发送信号进程)

2.信号的接收  

3.信号的处理

练习:

7.信号灯


1、进程间通信常用的几种方式

1,管道通信:有名管道,无名管道
2,信号- 系统开销小
3,消息队列-内核的链表
4,信号量-计数器
5,共享内存
6,内存映射
7,套接字

2.无名管道:

1.管道的概念:

 

本质:
                  .内核缓冲区
                  .伪文件-不占用磁盘空间
特点:
          两部分:
                        读端,写端,对应两个文件描述符
                        数据写端流入,读端流出
                        操作管理的进程被销毁之后,管道自动被释放
                        管道默认是阻塞的

2.管道的原理 :

内部实现方式:队列
                        环形队列
                        特点:先进先出
缓冲区大小:
                        默认4K
                        大小可根据实际情况做适当调整

3.管道的局限性:

队列:
                数据只能读取一次,不能重复读取

4.创建匿名管道:

int pipe(int fd[2])
fd‐传出参数:
                        fd[0]‐读端
                        fd[1]‐写端
返回值:
                0:成功
                ‐1:创建失败

5、demo:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int main()
{
	int ret;
	int fd[2];
	ret=pipe(fd);
	if(ret  == -1)
	{
		printf("create pipe failed!\n);
		exit(1);
	}
	
	printf("pipe[0] is %d\n",fd[0]);         //fd[0]=3
	printf("pipe[1] is %d\n",fd[1]);        //fd[1]=4
	
	close(fd[0]);   //关闭读端
	close(fd[1]);   //关闭写端
	return 0;

}

6.父子进程使用管道通信

(1)ps aux| grep "bash"

(2)数据重定向:

                   int dup2(int oldfd, int newfd);

7.管道的读写行为:

(1)读操作:
有数据:read(fd[1]) 正常读,返回读出的字节数
无数据:
        1、 写端被全部关闭,read返回 0 ,相当于读文件到了尾部
        2、没有全部关闭
        3、read阻塞
(2)写操作:
         1、 读端全部关闭
                管道破裂,进程被终止
                        内核给当前进程发送信号SIGPIPE-13,默认处理动作
       2、 读端没全部关闭
        缓冲区写满了
                write阻塞
         缓冲区没满
                write继续写,直到写满,阻塞
(3)如何设置非阻塞?
        默认读写两端都阻塞
        1、设置读端为非阻塞pipe(fd)
fcntl- 变参函数
        2、复制文件描述符-dup
        3、修改文件属性-open 的时候对应 flag 属性
设置方法:
//获取原来的flags
int flags = fcntl(fd[0],F+GETFL);
//设置新的flags
flag |=O_NONBLOCK;
fcntl(fd[0],F_SETFL,flags)
fcntl(fd[0],F_SETFL,flags);

8.查看管道缓冲区大小

1、命令 :ulimit -a

 2、函数: long fpathconf(int fd, int name);

3.有名管道

函数形式:int mkfifo(const char \*filename,mode_t mode);

功能:创建管道文件
参数:管道文件文件名,权限,创建的文件权限仍然和umask有关系。
返回值:创建成功返回0,创建失败返回-1

(1)特点

有名管道
在磁盘上有这样一个文件 ls -l ->p
也是一个伪文件,在磁盘大小永久为 0
数据存在内核中有一个对应的缓冲区
半双工通信方式

(2使用场景:

                没有血缘关系的进程间通信

(3)创建方式

命令: mkfifo 管道名
函数: mkfifo ----- make FIFOs (named pipes)
(4)fifo 文件可以使用 io 函数进程操作
>>open/close
>>read/write
>>不能执行 lseek 操作

4、消息队列

消息队列,是消息的链表,存放在内核中,一个消息队列由一个标识符(队列 ID )来标识。

 

1.特点

(1)消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级
(2)消息队列独立于发送和接收进程,进程终止时,消息队列及其内容仍存在
(3)消息队列可以实现消息的随机查询,消息不一定要先进先出的次序读取,也可以按消息的类型读取。

2.相关函数

1.int msgget(key_t key, int msgflg);
//创建或打开消息队列,
参数:
key:和消息队列关联的key
msgflg:是一个权限标志,表示消息队列的访问权限,它与文件的访问权限一样。msgflg可以与IPC_CREAT做或
操作,表示当key所命名的消息队列不存在时创建一个消息队列,如果key所命名的消息队列存在时,IPC_CREAT标志会被
忽略,而只返回一个标识符。
返回值:成功返回队列ID,失败则返回‐1
2.int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
//读取消息,成功返回消息数据的长度,失败返回‐1
参数:
msgid:消息队列的ID
msgp:指向消息的指针,常用结构体msgbuf如下:
struct msgbuf
{
long mtype; //消息类型
char mtext[N]; //消息正文
}
size:发送的消息正文你的字节数
flag:
        IPC_NOWAIT :消息没有发送完成函数也会立即返回
        0:知道发送完成函数才返回
返回值:
                成功:0
                失败:‐1
3.ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
//从一个消息队列中获取消息
参数:
msgid:消息队列的ID
msgp:要接收消息的缓冲区
size:要接收的消息的字节数
msgtype:
                0:接收消息队列中第一个消息
                大于0:接收消息队列中第一个类型为msgtyp的消息
                小于0:接收消息队列中类型值不大于msgtyp的绝对值且类型值又最小的消息。
flag:
                0:若无消息函数一直阻塞
                IPC_NOWAIT:若没有消息,进程会立即返回ENOMSG
返回值:
                成功:接收到的消息i长度
                出错:‐1
4.int msgctl(int msqid, int cmd, struct msqid_ds *buf);
//控制消息队列,成功返回0,失败返回‐1
参数:
msqid:消息队列的队列ID
cmd
                IPC_STAT:把msgid_ds结构中的数据设置为消息队列的当前关联值,即用消息队列的当前关联值覆 盖msgid_ds的值。
                IPC_SET:如果进程有足够的权限,就把消息列队的当前关联值设置为msgid_ds结构中给出的值
                IPC_RMID:删除消息队列
buf:是指向 msgid_ds 结构的指针,它指向消息队列模式和访问权限的结构
返回值:
                成功:0
                失败:‐1
key_t ftok( char * fname, int id )
//系统建立IPC通讯(如消息队列、共享内存时)必须指定一个ID值。通常情况下,该id值通过ftok函数得到。
参数:
        fname就时你指定的文件名(该文件必须是存在而且可以访问的)
        id是子序号, 虽然为int,但是只有8个比特被使用(0‐255)
返回值:
当成功执行的时候,一个key_t值将会被返回,否则 ‐1 被返回。
在以下两种情况下,msgget将创建一个新的消息队列:
(1) 如果没有与键值 key 相对应的消息队列,并且 flflag 中包含了 IPC_CREAT 标志
(2) key 参数为 IPC_PRIVATE

练习:用父子进程实现消息队列的全双工通信

vim msg_service.c
#include <sys/types.h>
#include<sys/ipc.h>
#include <sys/msg.h>
#include<stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>

struct msgbuf
{
	long mtype;   //消息类型
	char mtest[128];  //正文
	char ID[4];
};
int main()
{
	struct msgbuf  sendbuf,readbuf;
	int msgid;
	int readret;
	key_t   key;
	key=ftok("a.c",1);
	pid_t    pid;
	msgid =msgget( key,IPC_CREAT | 0755);
	if( msgid == -1)
{
	printf("create message queue failed !\n");
	return -1;
}
system("ipcs -q"); 
printf("create message queue success magid =%d\n",msgid);
//init msgbuf
sendbuf.mtype=100;
pid=fork();
//parent progess write 100,父进程写
if(  pid > 0)
{
	while(1)
	{
/*
# include <string.h>
void *memset(void *s, int c, unsigned long n);
将指针变量 s 所指向的前 n 字节的内存单元用一个“整数” c 替换,注意 c 是 int 型。s 是 void* 型的指针变量,所以它可以为任何类型的数据进行初始化。
memset() 函数可以说是初始化内存的“万能函数”,通常为新申请的内存进行初始化工作。
*/

	memset(sendbuf.mtest,0,128);
	printf("please input to message queue :\n");
	fgets(sendbuf.mtest,128,stdin);
	msgsnd(msgid,(void *) &sendbuf,strlen(sendbuf.mtest),0);
	}
}
// child  progess read  200,子进程读
if( pid == 0)
{
	while(1)
	{
	memset(readbuf.mtest,0,128);
//从消息队列获取消息
//ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg); 
//返回接收到消息的长度
	readret=msgrcv(msgid,(void *)&readbuf,128,200,0);
	printf(" receive byte from message queue is :%s\n",readbuf.mtest);
	}

}
return 0;
}

vim msg_client.c

#include <sys/types.h>
#include<sys/ipc.h>
#include <sys/msg.h>
#include<stdlib.h>
#include <string.h>
#include <unistd.h>
#include <stdio.h>
struct msgbuf
{
	long mtype;   //消息类型
	char mtest[128];  //正文
	char ID[4];
};
int main()
{
	struct msgbuf  sendbuf,readbuf;
	int msgid;
	int readret;
	key_t   key;
	key=ftok("a.c",1);
	pid_t    pid;
	msgid =msgget( key,IPC_CREAT | 0755);
	if( msgid == -1)
{
	printf("create message queue failed !\n");
	return -1;
}
system("ipcs -q"); 
printf("create message queue success magid =%d\n",msgid);
//init msgbuf
sendbuf.mtype=200;
pid =fork();
//child  progess write  200
if(  pid == 0)
{
	while(1)
	{
	memset(sendbuf.mtest,0,128);
	printf("please input to message queue :\n");
	fgets(sendbuf.mtest,128,stdin);
	msgsnd(msgid,(void *) &sendbuf,strlen(sendbuf.mtest),0);
	}
}
// parent  progess read  100
if( pid >  0)
{
	while(1)
	{
	memset(readbuf.mtest,0,128);
	readret=msgrcv(msgid,(void *)&readbuf,128,100,0);
	printf(" receive byte from message queue is :%s\n",readbuf.mtest);
	}

}
return 0;
}
执行效果:

 

注意:使用 ipcs -q查看消息队列时:used-bytes 值为0,内容被读走了,相当于删除了内容,但节点还存在

5.共享内存

1.概念

共享内存(Shared Memory)就是允许多个进程访问同一个内存空间,是在多个进程之间共享和传递数据最 高效的方式。操作系统将不同进程之间共享内存安排为同一段物理内存,进程可以将共享内存连接到它们自 己的地址空间中,如果某个进程修改了共享内存中的数据,其它的进程读到的数据也将会改变。

2.相关函数

1.int shmget(key_t key, size_t size, int shmflg);
//用来获取或创建共享内存
参数:
        key:IPC_PRIVATE ftok的返回值
        size:共享内存区大小
        shmflg:同open函数的权限位,也可以用8进制表示法
返回值:
        成功:共享内存段标识符‐‐‐ID‐‐‐文件描述符
        出错:‐1
2.void *shmat(int shm_id, const void *shm_addr, int shmflg);
//把共享内存连接映射到当前进程的地址空间
参数:
        shm_id:ID号
        shm_addr:映射到的地址,NULL为系统自动完成的映射
        shmflg:
                SHM_RDONLY共享内存只读
                默认是0,表示共享内存可读写
返回值:
        成功:映射后的地址
        失败:NULL
3.int shmdt(const void *shmaddr);
//将进程里的地址映射删除
参数:
        shmid:要操作的共享内存标识符
返回值:
        成功:0
        出错:‐1
4.int shmctl(int shm_id, int command, struct shmid_ds *buf);
//删除共享内存对象
参数:
        shm_id:要操作的共享内存标识符
        cmd :
                PC_STAT (获取对象属性)‐‐‐ 实现了命令ipcs ‐m
                IPC_SET (设置对象属性)
                IPC_RMID (删除对象) ‐‐‐实现了命令ipcrm ‐m
        buf :指定IPC_STAT/IPC_SET时用以保存/设置属性
返回值:
        成功:0
        出错:‐1
特点:
1.共享内存创建之后,一直存在于内核中,直到被删除或系统关闭
2.共享内存和管道不一样,读取后,内容仍然在共享内存中

练习:实现共享内存间的通信

vim shm_write.c
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main()
{
	int  shmid;
	int  key;
	char *p;
	key=ftok("a.c",1);
	if(key < 0)
	{
		printf("ftok failed !\n");
		return  -2;
	}
	printf("ftok success key :%x\n",key);
	shmid =shmget(key,128,IPC_CREAT | 0777);
	if (shmid < 0)
	{
		printf("create share memory failed !\n");
		return -1;

	}
	printf("create  share memory  success  shmid =%d\n",shmid);
	//在终端查看内存
	system("ipcs -m");
	//连接
	p=(char *) shmat(shmid,NULL,0);
	if( p== NULL)
	{
		printf("shmat funtion failed!\n");
		return -3;
	}
	printf("please input to share memory:\n");
	//write  to  share  memory
	fgets(p,128,stdin);
	sleep(3);    //延时3s
	shmdt(p);    //断开连接
	shmctl(shmid,IPC_RMID,0);   //删除

	return 0;

}

vim shm_read.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>

int main()
{
	int  shmid;
	int  key;
	char *p;
	key=ftok("a.c",1);
	if(key < 0)
	{
		printf("ftok failed !\n");
		return  -2;
	}
	printf("ftok success key :%x\n",key);
	//共用同一个key值
	shmid =shmget(key,128,0);
	if (shmid < 0)
{
	printf("creat share memory failed !\n");
	return -1;

}
	printf("create  share memory  success  shmid =%d\n",shmid);
	//在终端查看内存
	system("ipcs -m");
	p=(char *) shmat(shmid,NULL,0);
	if( p== NULL)
	{
		printf("shmat funtion failed!\n");
		return -3;
	}
	printf("share memory data: %s\n",p);

	shmdt(p);
	return 0;
	
}

执行效果:

 

6.信号:

信号通信,其实就是内核向用户空间进程发送信号,只有内核才能发信号,用户空间进程不能发送信号。

内核可以发送多少种信号呢?
kill ‐l
信号通信的框架:
                信号的发送(发送信号进程):kill、raisealarm
                信号的接收(接收信号进程) : pause()、 sleepwhile(1)
                信号的处理(接收信号进程) :signal

1.信号的发送(发送信号进程)

kill:
#include<signal.h>
#include<sys/types.h>
函数原型: int kill(pid_t pid, int sig);
参数:
        函数传入值:pid
                              正数:要接收信号的进程的进程号
                              0:信号被发送到所有和pid进程在同一个进程组的进程
                             ‐1:信号发给所有的进程表中的进程(除了进程号最大的进程外 )
        sig:信号
函数返回值:成功 0 出错 ‐1
raise: 发信号给自己 == kill(getpid(), sig)
所需头文件 :
#include<signal.h>
#include<sys/types.h>
函数原型:
int raise(int sig);
参数:
函数传入值: sig :信号
函数返回值:
        成功 0 出错 ‐1
alarm : 发送闹钟信号的函数
alarm raise 函数的比较:
相同点:让内核发送信号给当前进程
不同点:
                alarm 只会发送SIGALARM信号
                alarm 会让内核定时一段时间之后发送信号, raise会让内核立刻发信号
所需头文件 #include <unistd.h>
函数原型 unsigned int alarm(unsigned int seconds)
参数:
        seconds:指定秒数
返回值:
成功:如果调用此 alarm() 前,进程中已经设置了闹钟时间,则 返回上一个闹钟时间的剩余时间,否则返回 0
出错: ‐1

2.信号的接收  

接收信号的进程,要有什么条件:
要想使接收的进程能收到信号,这个进程不能结束
pause:进程状态为sleep
函数原型 int pause(void);
函数返回值 成功: 0 ,出错: ‐1

3.信号的处理

收到信号的进程,应该怎样处理? 处理的方式:
1.进程的默认处理方式(内核为用户进程设置的默认处理方式)
(1) :忽略
(2) :终止进程
(3): 暂停
2. 自己的处理方式
自己处理信号的方法告诉内核,这样你的进程收到了这个信号就会采用你自己的的处理方式
所需头文件 #include <signal.h>
函数原型 void (*signal(int signum, void (*handler)(int)))(int);
函数传入值
                signum:指定信号
                handler:
                        SIG_IGN:忽略该信号。
                        SIG_DFL:采用系统默认方式处理信号
                        自定义的信号处理函数指针
函数返回值
        成功:设置之前的信号处理方式
        出错:‐1
signal 函数有二个参数,第一个参数是一个整形变量(信号值),第二个参数是一个函数指针,是我们自己写的处理函 数;这个函数的返回值是一个函数指针。

练习:

vim kill.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <signal.h>

int main(int argc , char *argv[])
{
	int sig,pid;
	if(argc <3 )
	{
		printf(" input error!\n");
		return -1;
	}
//因为输入的是字符,把字符转换成整型
	sig =atoi(argv[[1]);
	pid =atoi(argv[2]);
	printf("sig = %d,pid =%d\n",sig,pid);
//发送信号进程
	kill(pid,sig);

	return 0;

}

使用:运行一个进程,然后执行./kill 9 pid就可以把刚才运行的进程杀死

vim raise.c

#include <stdio.h>
#include <signal.h>
int main()
{
	pid_t pid;
	pid =fork();
	if( pid > 0)
{
	sleep(8);
	if(waitpid(pid,NULL,WNOHANG) == 0)
	{
		kill(pid,9);
	}
	wait(NULL);
	while(1);
}
	if( pid == 0)
	{
	printf("before sig:\n");
	raise(SIGTSTP);
	printf("after sig \n");
	}
	return 0;
}

vim alarm.c

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

int main()
{
	int i=0;
	printf("before alarm \n");
	alarm(7);
	//pause();
	printf("after alarm \n");
	while(i<20)
	{

		i++;
		sleep(1);
		printf("progess %d\n",i);
	}
	return 0;
}

vim signal.c

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

void myfun(int signum)
{
	int i=0;
	while( i<10)
	{
		printf("progess signum = %d  i= %d\n",signum,i);
		sleep(1);
		i++;
	}
int main()
{
	int i=0;
	//14:SIGALRM
	signal(14,myfun);
	printf("before alarm \n");
	alarm(7);
	//pause();
	printf("after alarm \n");
	while(i<12)
	{
		i++;
		sleep(1);
		printf("progess %d\n",i);
	}
	return 0;
}

7.信号灯

信号灯集合(可以包含多个信号灯)IPC对象是一个信号的集合(多个信号量)

函数原型: int semget(key_t key, int nsems, int semflg);
//创建一个新的信号量或获取一个已经存在的信号量的键值。
所需头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
函数参数:
                key:和信号灯集关联的key
                nsems: 信号灯集中包含的信号灯数目
                semflg:信号灯集的访问权限
函数返回值:
        成功:信号灯集ID
        出错:‐1
函数原型:int semctl ( int semid, int semnum, int cmd…union semun arg(不是地址));
//控制信号量,删除信号量或初始化信号量
所需头文件:
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
函数参数:
        semid:信号灯集ID
        semnum: 要修改的信号灯编号
        cmd :
                GETVAL:获取信号灯的值
                SETVAL:设置信号灯的值
                IPC_RMID:从系统中删除信号灯集合
函数返回值:
                成功:0
                出错:‐1
int semop(int semid ,struct sembuf *sops ,size_t nsops);
//用户改变信号量的值。也就是使用资源还是释放资源使用权
包含头文件:
include<sys/sem.h>
参数:
        semid : 信号量的标识码。也就是semget()的返回值
        sops是一个指向结构体数组的指针。
struct sembuf{
unsigned short sem_num//信号灯编号;
short sem_op;//对该信号量的操作。‐1 P操作,1 V操作
short sem_flg;0阻塞,1非阻塞
};
        sem_op : 操作信号灯的个数
(1)如果其值为正数,该值会加到现有的信号内含值中。通常用于释放所控资源的使用权;(2)如果sem_op的值为负 数,而其绝对值又大于信号的现值,操作将会阻塞,直到信号值大于或等于sem_op的绝对值。通常用于获取资源的使用 权;
(3)如果sem_op的值为0,则操作将暂时阻塞,直到信号的值变为0
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

linux进程间的通信(详细且有demo) 的相关文章

随机推荐

  • 【Android】App开发-动画效果篇

    在我们玩手机的过程中 如果我们点击某一个页面时 会出现一个页面动画加载或者动画效果的现象 现在我们就来看看App开发中是如何实现动画效果的 目录 动画的分类 逐帧动画 补间动画 动画的分类 在常见的app使用的动画中 常见的就是逐帧动画 补
  • confluence安装

    注 安装前确保机器已安装docker 1 执行如下命令一键安装wiki mkdir p data cd data wget http apk lingyun5 com confluence wiki tar gz tar zxvf conf
  • 学习sql,你需要知道这些

    这里写目录标题 数据库的分类 开发式数据库 非开发式数据库 事务 什么是事务 事务的四种特性 死锁 什么是死锁 死锁的四个条件 如何处理死锁 预防死锁 避免死锁 检测死锁 解除死锁 什么是navicat SQL语句 对数据库的操作 对表的操
  • 数字图像处理:使用直方图统计进行图像增强

    一 引言 在 数字图像处理 局部直方图处理 Local Histogram Processing https blog csdn net LaoYuanPython article details 120383974 介绍了基于像素的邻域进
  • 工具类之FastDFSClient

    import org csource common MyException import org csource common NameValuePair import org csource fastdfs import org spri
  • 【GRU时序预测】基于贝叶斯网络优化卷积神经网络结合门控循环单元BO-CNN-GRU实现数据股价预测附matlab代码

    作者简介 热爱科研的Matlab仿真开发者 修心和技术同步精进 matlab项目合作可私信 个人主页 Matlab科研工作室 个人信条 格物致知 更多Matlab仿真内容点击 智能优化算法 神经网络预测 雷达通信 无线传感器 电力系统 信号
  • c语言 --- 指针

    什么是指针 指针就是一个地址 在c语言中任何东西都是有地址的 如何获取地址 用的是 取地址符 指针就是一个整数 获取指针 定义变量时 可以通过取地址符 得到当前变量的地址 gt 一个房间对应一个房间号 地址类比于房间号 所有的指针类型都是
  • jpa 动态参数查询 高级查询

    GetMapping find ApiOperation 通用查询 ScSecurityPermission name 通用查询 public ScResult find FileInfo fileInfo RequestParam nam
  • JS获取json子项/数组的个数/长度

    JS获取json子项 数组的个数 长度 微信小程序获取json格式数据的个数 长度
  • java 请求参数加解密

    项目开发中 需要针对请求参数加密 解密操作 可以使用下列工具类 oap security enabled true oap security enableIgnoreAnnotation true oap security secretKe
  • [4G/5G/6G专题基础-158]: 5G VoNR(Voice over NR)与VoLTE共同组成5G三大语音方案

    目录 第1章 语音方案概述 1 1 VoLTE概述 1 2 5G VoNR概述 第2章 5G VoNR网络架构 2 1 基本原则 2 2 NSA VoLTE方案 2 3 SA EPS Fallback SA组网 早期方案 2 4 SA Vo
  • 对于售点POI标签计算脚本优化总结

    对于售点POI标签计算脚本性能优化总结 减少udf 函数的使用多多使用spark的算子 对于两组经纬度的距离计算可以使用常规的公式计算法 Haversine 公式是一种用于计算两个球面坐标点 经度和纬度 之间的距离的公式 它的原理是基于球面
  • netpoll浅析

    netpoll只是一种框架和一些接口 只有依赖这个框架和接口实现的netpoll实例 netpoll才能发挥它的功能 类似于kernel中的vfs vfs本身并不会去做具体的文件操作 只是为不同的文件系统提供了一个框架 netpoll不依赖
  • 理解编程中的while循环(C/C++)

    固定的语法结构 省略 include 和 using int main printf x 我们写这个代码 打了一个int main 然后打了一个大括号 为什么要这样做 这个main 是一个函数 但为什么一定要叫main 为什么又需要打 又需
  • JS中的递归

    1 什么是递归 如果一个函数在内部可以调用其本身 那么这个函数就是递归函数 简单理解 函数内部自己调用自己 这个函数就是递归函数 比如 但上面代码报错因为递归很容易发生 栈溢出 错误 stack 所以必须要加退出条件 return 递归必须
  • python读取csv文件内容,并保存到数据库中

    coding utf 8 python读取csv文件内容 并保存到数据库中 import csv import time import pymysql import emoji num 0 file path r H 20221103174
  • 快速掌握 Android Studio 中 Gradle 的使用方法

    Gradle是可以用于Android开发的新一代的 Build System 也是 Android Studio默认的build工具 Gradle脚本是基于一种JVM语言 Groovy 再加上DSL 领域特定语言 组成的 因为Groovy是
  • 81. Search in Rotated Sorted Array II

    31 Search in Rotated Sorted Array ll 描述不包含相同的元素情况 Input nums 4 5 6 7 0 1 2 target 0 Output 4 对有序数组进行一定的旋转 进行查找 二分查找and双指
  • VMware player桥接模式不能联网的解决方法

    VMware虚拟机下主要使用两种网络连接方式 桥接模式 NAT模式 桥接模式 直接连接网络 虚拟机独立IP 并与宿主机处于同一网段内 相当于虚拟机是局域网内独立的一台电脑 使用起来较为方便 NAT模式 网络地址转换 借由宿主机的IP访问网络
  • linux进程间的通信(详细且有demo)

    目录 1 进程间通信常用的几种方式 2 无名管道 1 管道的概念 2 管道的原理 3 管道的局限性 4 创建匿名管道 5 demo 6 父子进程使用管道通信 7 管道的读写行为 8 查看管道缓冲区大小 3 有名管道 1 特点 2使用场景 3