UNIX环境高级编程——进程间通信

news2024/11/25 14:52:46

15.1 引言

本章将说明进程之间相互通信技术——进程间通信(InterProcess Communication,IPC)。

IPC类型包括:
在这里插入图片描述

  • 前10种IPC形式通常限于同一台主机的两个进程之间的IPC;
  • 最后2种是仅有的支持不同主机上两个进程之间的IPC。

15.2 管道

管道是通过调用pipe函数创建的:

#include <unistd.h>

int pipe(int fd[2]);
										// 返回值:若成功,返回0;若出错,返回-1
  • 经由参数fd返回两个文件描述符:fd[0]为读而打开,fd[1]为写而打开;fd[1]的输出是fd[0]的输入;
  • fstat函数对管道的每一端都返回一个FIFO类型(命名管道)的文件描述符,可以用S_ISFIFO宏来测试管道。

单个进程中的管道几乎没有任何用处。通常,进程会先调用pipe,接着调用fork,从而创建从父进程到子进程的IPC通道,反之亦然:
在这里插入图片描述

  • 对于从父进程到子进程的管道,父进程关闭管道的读端(fd[0]),子进程关闭写端(fd[1]):
    在这里插入图片描述
  • 对于一个从子进程到父进程的管道,父进程关闭fd[1], 子进程关闭fd[0]。

当管道的一端被关闭后,下列两条规则起作用:
(1)当读(read)一个写端已被关闭的管道时,在所有数据都被读取后,read返回0,表示文件结束;
(2)如果写(write)一个读端已被关闭的管道,则产生信号SIGPIPEwrite返回-1,errno设置为EPIPE

15.3 函数popen和pclose

常见的操作是创建一个连接到另一个进程的管道,然后读其输出或向其输入端发送数据,为此,标准I/O库提供了两个函数popenpclose

#include <stdio.h>

FILE *popen(const char *cmdstring, const char *type);
										// 返回值:若成功,返回文件指针;若出错,返回NULL
int pclose(FILE *fp);// 返回值:若成功,返回cmdstring的终止状态;若出错,返回-1
  • 这两个函数实现的操作是:创建一个管道,fork一个子进程,关闭未使用的管道端,执行一个shell运行命令,然后等待命令终止;
  • 函数popen先执行fork,然后调用exec执行cmdstring,并且返回一个标准I/O文件指针;如果type是“r”,则文件指针连接到cmdstring的标准输出;如果type是“w”,则文件指针连接到cmdstring的标准输入:
    在这里插入图片描述
  • pclose函数关闭标准I/O流,等待命令终止,然后返回shell的终止状态。

15.4 协同进程

协同进程通常在shell的后台运行,其标准输入和标准输出通过管道连接到另一个程序。协同进程有连接到另一个进程的两个单向管道:一个接到其标准输入,另一个则来自其标准输出。我们想将数据写到其标准输入,经其处理后,再从标准输出读取数据。

15.5 FIFO

FIFO有时被称为命名管道。未命名的管道只能在两个相关的进程之间使用,而且这两个相关的进程还要有一个共同的创建了它们的祖先进程。但是,通过FIFO,不相关的进程也能交换数据。

FIFO是一种文件类型,通过stat结构的st_mode成员的编码可以知道文件是否是FIFO类型,可以通过S_ISFIFO宏对此进行测试。

FIFO的路径名存在于文件系统中,创建FIFO类似于创建文件:

#include <sys/stat.h>

int mkfifo(const char *path, mode_t mode);
int mkfifoat(int fd, const char *path, mode_t mode);
												// 两个函数的返回值:若成功,返回0;若出错,返回-1
  • mode参数的规格说明与open函数中的mode相同;
  • mkfifoat函数可以被用来在fd文件描述符表示的目录相关的位置创建一个FIFO,像其他*at函数一样,有3种情形:
    (1)如果path参数指定的是绝对路径名,则fd参数会被忽略掉,并且mkfifoat函数的行为和mkfifo类似;
    (2)如果path参数指定的是相对路径名,则fd参数是一个打开目录的有效文件描述符,路径名和目录有关;
    (3)如果path参数指定的是相对路径名,并且fd参数有一个特殊值AT_FDCWD,则路径名以当前目录开始,mkfifoatmkfifo类似。
  • 当用mkfifo或者mkfifoat创建FIFO时,要用open来打开它,当open一个FIFO时,非阻塞标志(O_NONBLOCK)会产生下列影响:
    (1)在一般情况下(没有指定O_NONBLOCK),只读open要阻塞到某个其他进程为写而打开这个FIFO为止;类似地,只写open要阻塞到某个其他进程为读而打开它为止;
    (2)如果指定了O_NONBLOCK,则只读open立即返回;但是,如果没有进程为读而打开一个FIFO,那么只写open将返回-1,并将errno设置成ENXIO

15.6 XSI IPC

有3种称作XSI IPC的IPC:消息队列、信号量以及共享存储器。

15.6.1 标识符和键

  • 每个内核中的IPC结构(消息队列、信号量或共享内存段)都用一个非负整数的标识符(identifier)加以引用;
  • 标识符是IPC对象的内部名,为使多个合作进程能够在同一IPC对象上汇聚,需要提供一个外部命名方案,为此,每个IPC对象都与一个(key)相关联,将这个键作为该对象的外部名;这个键的数据类型是基本系统数据类型key_t,通常被定义为长整型,这个键由内核变换成标识符。

15.6.2 权限结构

XSI IPC为每一个IPC结构关联了一个ipc_perm结构,该结构规定了权限和所有者,它至少包括下列成员:

struct ipc_perm {
	uid_t	uid;	/* owner's effective user id */
	gid_t	gid;	/* owner's effective group id */
	uid_t	cuid;	/* creator's effective user id */
	gid_t	cgid;	/* creator's effective group id */
	mode_t	mode;	/* access modes */
	...
};

15.6.3 结构限制

15.6.4 优点和缺点

15.7 消息队列

消息队列是消息的链接表,存储在内核中,由消息队列标识符标识,每个队列都有一个msqid_ds结构与其相关联:

struct msgid_ds {
	struct	ipc_perm	msg_perm;		/* see Section 15.6.2 */
	msgqnum_t			msg_qnum;		/* # of messages on queue */
	msglen_t			msg_qbytes;		/* max # of bytes on queue */
	pid_t				msg_lspid;		/* pid of last msgsnd() */
	pid_t				msg_lrpid;		/* pid of last msgrcv() */
	time_t				msg_stime;		/* last-msgsnd() time */
	time_t				msg_rtime;		/* last-msgrcv() time */
	time_t				msg_ctime;		/* last-change time */
	...
};		

msgget用于创建一个新队列或打开一个现有队列:

#include <sys/msg.h>

int msgget(key_t key, int flag);
										// 返回值;若成功,返回消息队列ID;若出错,返回-1

msgctl函数对队列执行多种操作:

#include <sys/msg.h>

int msgctl(int msqid, int cmd, struct msqid_ds *buf);
										// 返回值:若成功,返回0;若出错,返回-1
  • cmd参数指定对msqid指定的队列要执行的命令:
    • IPC_STAT:取此队列的msqid_ds结构,并将它存放在buf指向的结构中;
    • IPC_SET:将字段msg_perm.uid、msg_perm.gid、msg_perm.mode和msg_qbytes从buf指向的结构赋值到与这个队列相关的msqid_ds结构中;
    • IPC_RMID:从系统中删除该消息队列以及仍在该队列中的所有数据,这种删除立即生效。

msgsnd将新消息添加到队列尾端:

#include <sys/msg.h>

int msgsnd(int msqid, const void *ptr, size_t nbytes, int flag);
										// 返回值:若成功,返回0;若出错,返回-1
  • ptr参数指向一个长整型数,它包含了正的整型消息类型,其后紧接着的时消息数据(若nbytes是0,则无消息数据)。若发送的最长消息是512字节的,则可定义下列结构,ptr就是一个指向mymesg结构的指针:

    struct mymesg {
    	long	mtype;			/* positive message type */
    	char	mtext[512];		/* message data, of length nbytes */
    };
    
  • 参数flag的值可以指定为IPC_NOWAIT,这类似于文件I/O的非阻塞I/O标志。若消息队列已满,则指定IPC_NOWAIT使得msgsnd立即出错返回EAGAIN;如果没有指定IPC_NOWAIT,则进程会一直阻塞到:有空间可以容纳要发送的消息;或者从系统中删除了此队列;或者捕捉到一个信号,并从信号处理程序返回;在第二种情况下,会返回EIDRM错误(“标识符被删除”),最后一种情况则返回EINTR错误;

  • msgsnd返回成功时,消息队列相关的msqid_ds结构会随之更新,表明调用的进程ID(msg_lspid)、调用的时间(msg_stime)以及队列中新增的消息(msg_qnum)。

msgrcv用于从队列中取消息:

#include <sys/msg.h>

ssize_t msgrcv(int msqid, void *ptr, size_t nbytes, long type, int flag);
										// 返回值:若成功,返回消息数据部分的长度;若出错,返回-1
  • ptr参数指向一个长整型数(其中存储的是返回的消息类型),其后跟随的是存储实际消息数据的缓冲区;
  • nbytes指定数据缓冲区的长度;
  • 若返回的消息长度大于nbytes,而且在flag中设置了MSG_NOERROR位,则该消息会被截断;如果没有设置这一标志,而消息又太长,则出错返回E2BIG(消息仍留在队列中);
  • 参数type可以指定想要哪一种消息,type值非0用于以非先进先出次序读消息:
    • type == 0:返回队列中的第一个消息;
    • type > 0:返回队列中消息类型为type的第一个消息;
    • type < 0:返回队列中消息类型值小于等于type绝对值的消息,如果这种消息有若干个,则取类型值最小的消息。
  • 可以将flag值指定为IPC_NOWAIT,使操作不阻塞,这样,如果没有所指定类型的消息可用,则msgrcv返回-1,error设置为ENOMSG;如果没有指定IPC_NOWAIT,则进程会一直阻塞到有了指定类型的消息可用,或者从系统中删除了此队列(返回-1,errno设置为EIDRM),或者捕捉到一个信号并从信号处理程序返回(返回-1,errno设置为EINTR);
  • msgrcv成功执行时,内核会更新与该消息队列相关联的msqid_ds结构,以指示调用者的进程ID(msg_lrpid)和调用时间(msg_rtime),并指示队列中的消息数减少了1个(msg_qnum)。

15.8 信号量

信号量是一个计数器,用于为多个进程提供对共享数据对象的访问。

内核为每个XSI信号量集合维护者一个semid_ds结构:

struct semid_ds {
	struct	ipc_perm	sem_perm;	/* see Section 15.6.2 */
	unsigned	short	sem_nsems;	/* # of semaphores in set */
	time_t				sem_otime;	/* last-semop() time */
	time_t				sem_ctime;	/* last-change time */
	...
};

每个信号量由一个无名结构表示,它至少包含下列成员:

struct {
	unsigned	short	semval;		/* semaphore value, always >= 0 */
	pid_t				sempid;		/* pid for last operation */
	unsigned	short	semncnt;	/* # processes awaiting semval > curval */
	unsigned 	short	semzcnt;	/* # processes awaiting semval == 0 */
	...
};

想使用XSI信号量时,首先需要通过函数semget来获得一个信号量ID:

#include <sys/sem.h>

int semget(key_t key, int nsems, int flag);
										// 返回值:若成功,返回信号量ID;若出错,返回-1
  • nsems是该集合中的信号量数;如果是创建新集合,则必须指定nsems;如果是引用现有集合,则将nsems指定为0

semctl函数包含了多种信号量操作:

#include <sys/sem.h>

int semctl(int semid, int semnum, int cmd, ... /* union semun arg */);
										// 返回值:(见下)
  • 第4个参数是可选的,是否使用取决于所请求的命令,如果使用该参数,则其类型是semun,它是多个命令特定参数的联合:

    union semun {
    	int						val;		/* for SETVAL */
    	struct		semid_ds	*buf;		/* for IPC_STAT and IPC_SET */
    	unsigned	short		*array;		/* for GETALL and SETALL */
    };
    
  • cmd参数指定下列10种命令中的一种,这些命令是运行在semid指定的信号量集合上的。其中有5种命令是针对一个特定的信号量值的,它们用semnum指定该信号量集合中的一个成员。semnum值在0和nsems-1之间,包括0和nsems-1:

    • IPC_STAT:对此集合取semid_ds结构,并存储在由arg.buf指向的结构中;
    • IPC_SET:按arg.buf指向的结构中的值,设置与此集合相关的结构中的sem_perm.uid、sem_perm.gid和sem_perm.mode字段;
    • IPC_RMID:从系统中删除该信号量集合,这种删除是立即发生的;
    • GETVAL:返回成员semnum的semval值;
    • SETVAL:设置成员semnum的semval值,该值由arg.val指定;
    • GETPID:返回成员semnum的sempid值;
    • GETNCNT:返回成员semnum的semncnt值;
    • GETZCNT:返回成员semnum的semzcnt值;
    • GETALL:取该集合中所有的信号量值,这些值存储在arg.array指向的数组中;
    • SETALL:将该集合中所有的信号量值设置成arg.array指向的数组中的值;
  • 对于除GETALL以外的所有GET命令,semctl函数都返回相应值。对于其他命令,若成功则返回值为0,若出错,则设置errno并返回-1。

函数semop自动执行信号量集合上的操作数组:

#include <sys/sem.h>

int semop(int semid, struct sembuf semoparray[], size_t nops);
										// 返回值:若成功,返回0;若出错,返回-1
  • 参数semoparray是一个指针,它指向一个由sembuf结构表示的信号量操作数组,参数nops规定该数组中操作的数量(元素数):
    struct sembuf {
    	unsigned	short	sem_num;	/* member # in set (0, 1, ..., nsems-1) */
    	short				sem_op;		/* operation(negative, 0 or pasitive)*/
    	short				sem_flg;	/* IPC_NOWAIT, SEM_UNDO */
    };
    
    • sem_op为正值,对应于进程释放的占用的资源数,sem_op值会加到信号量的值上;
    • 若sem_op为负值,则表示要获取由该信号量控制的资源;
    • 若sem_op为0,则表示调用进程希望等待到该信号量值变成0。
  • semop函数具有原子性,它或者执行数组中的所有操作,或者一个也不做。

15.9 共享存储

共享存储允许两个或多个进程共享一个给定的存储区,因为数据不需要在客户进程和服务器进程之间复制,所以这是最快的一种IPC。

内核为每个共享存储段维护着一个结构,该结构至少要为每个共享存储段包含以下成员:

struct shmid_ds {
	struct	ipc_perm	shm_perm;	/* see Section 15.6.2 */
	size_t				shm_segsz;	/* size of segment in bytes */
	pid_t				shm_lpid;	/* pid of last shmop() */
	pid_t				shm_cpid;	/* pid of creator */
	shmatt_t			shm_nattch;	/* number of current attaches */
	time_t				shm_atime;	/* last-attach time */
	time_t				shm_dtime;	/* last-detach time */
	time_t				shm_ctime;	/* last-change time */
	...
};

调用的第一个函数通常是shmget,它获得一个共享存储标识符:

#include <sys/shm.h>

int shmget(key_t key, size_t size, int flag);
										// 返回值:若成功,返回共享存储ID;若出错,返回-1
  • 参数size是该共享存储段的长度,以字节为单位,实现通常将其向上取为系统页长的整数倍;如果正在创建一个新段(通常在服务器进程中),则必须指定其size,如果正在引用一个现存的段(一个客户进程),则将size指定为0。

shmctl函数对共享存储段执行多种操作:

#include <sys/shm.h>

int shmctl(int shmid, int cmd, struct shmid_ds *buf);
										// 返回值:若成功,返回0;若出错,返回-1
  • cmd参数指定下列5种命令中的一种,使其在shmid指定的段上执行:

    • IPC_STAT:取此段的shmid_ds结构,并将它存储在buf指向的结构中;
    • IPC_SET:按buf指向的结构中的值设置与此共享存储段相关的shmid_ds结构中的下列3个字段:shm_perm.uid、shm_perm.gid、shm_perm.mode;
    • IPC_RMID:从系统中删除该存储段。因为每个共享存储段维护着一个连接计数(shmid_ds结构中的shm_nattach字段),所以除非使用该段的最后一个进程终止或与该段分离,否则不会实际上删除该存储段。不管此段是否仍在使用,该段标识符都会被立即删除,所以不能再用shmat与该段连接。
  • Linux和Solaris提供了另外两种命令,它们只能由超级用户执行,但它们并非Single UNIX Specification的组成部分:

    • SHM_LOCK:在内存中对共享存储段加锁;
    • SHM_UNLOCK:解锁共享存储段。

一旦创建了一个共享存储段,进程就可调用shmat将其连接到它的地址空间中:

#include <sys/shm.h>

void *shmat(int shmid, const void *addr, int flag);
										// 返回值:若成功,返回指向共享存储段的指针;若出错,返回-1
  • 共享存储段连接到调用进程的哪个地址上与addr参数以及flag中是否指定SHM_RND位有关:
    • 如果addr0,则此段连接到由内核选择的第一个可用地址上,这是推荐的使用方式;
    • 如果addr0,并且没有指定SHM_RND,则此段连接到addr所指定的地址上;
    • 如果addr0,并且指定了SHM_RND,则此段连接到 (addr - (addr mod SHMLBA))所表示的地址上。SHM_RND命令的意思是“取整”;SHMLBA的意思是“低边界地址倍数”,它总是2的乘方;该算式是将地址向下取最近1个SHMLBA的倍数。
  • 如果在flag中指定了SHM_RDONLY位,则以只读方式连接此段,否则以读写方式连接此段;
  • 如果shmat成功执行,那么内核将使与该共享存储段相关的shmid_ds结构中的shm_nattch计数其值加1。

当对共享存储段的操作已经结束时,则调用shmdt与该段分离:

#include <sys/shm.h>

int shmdt(const void *addr);
										// 返回值:若成功,返回0;若出错,返回-1
  • addr参数是以前调用shmat时的返回值;
  • 如果成功,shmdt将使相关shmid_ds结构中的shm_nattch计数器值减1;
  • shmdt并不从系统中删除其标识符以及相关的数据结构,该标识符仍然存在,直至某个进程(一般是服务器进程)带IPC_RMID命令的调用shmctl特地删除它为止。

15.10 POSIX信号量

POSIX信号量有两种形式:命名的未命名的。未命名信号量只存在于内存中,并要求能使用信号量的进程必须可以访问内存,这意味着它们只能应用在同一进程中的线程,或者不同进程中已经映射相同内存内容到它们的地址空间中的数据;命名信号量可以通过名字访问,可以被任何已知它们名字的进程中的线程使用。

可以调用sem_open函数来创建一个新的命名信号量或者使用一个现有信号量:

#include <semaphore.h>

sem_t *sem_open(const char *name, int oflag, .../* mode_t mode, unsigned int value */);
										// 返回值:若成功,返回指向信号量的指针;若出错,返回SEM_FAILED
  • 当使用一个现有的命名信号量时,仅需指定信号量的名字nameoflag参数的0值;
  • oflag参数有O_CREAT标志集时,如果命名信号量不存在,则创建一个新的;如果它已经存在,则会被使用,但是不会有额外的初始化发生;
  • 当指定O_CREAT标志时,需要提供两个额外的参数:mode参数指定谁可以访问信号量,mode的取值和打开文件的权限位相同;value参数用来指定信号量的初始值。

当完成信号量操作时,可以调用sem_close函数来释放任何信号量相关的资源:

#include <semaphore.h>

int sem_close(sem_t *sem);
										// 返回值:若成功,返回0;若出错,返回-1

可以使用sem_unlink函数来销毁一个命名信号量:

#include <semaphore.h>

int sem_unlink(const char *name);
										// 返回值:若成功,返回0;若出错,返回-1
  • sem_unlink函数删除信号量的名字,如果没有打开的信号量引用,则该信号量会被销毁;否则,销毁将延迟到最后一个打开的引用关闭。

可以使用sem_wait或者sem_trywait函数来实现信号量的减1操作:

#include <semaphore.h>

int sem_trywait(sem_t *sem);
int sem_wait(sem_t *sem);
										// 两个函数的返回值:若成功,返回0;若出错,返回-1
  • 使用sem_wait函数时,如果信号量计数是0就会发生阻塞,直到成功使信号量减1或者被信号中断时才返回;
  • 可以使用sem_trywait函数来避免阻塞,如果信号量是0,则不会阻塞,而是会返回-1并且将errno置为EAGAIN

sem_timewait函数阻塞一段确定的时间:

#include <semaphore.h>
#include <time.h>

int sem_timedwait(sem_t *restrict sem,
				  const struct timespec *restrict tsptr);
										// 返回值:若成功,返回0;若出错,返回-1
  • 如果超时到期并且信号量计数没能减1,sem_timedwait将返回-1且将errno设置为ETIMEDOUT

可以调用sem_post函数使信号量值增1:

#include <semaphore.h>

int sem_post(sem_t *sem);
										// 返回值:若成功,返回0;若出错,返回-1
  • 调用sem_post时,如果在调用sem_wait(或者sem_timedwait)中发生进程阻塞,那么进程会被唤醒并且被sem_post增1的信号量计数会再次被sem_wait(或者sem_timedwait)减1。

可以调用sem_init函数来创建一个未命名的信号量:

#include <semaphore.h>

int sem_init(sem_t *sem, int pshared, unsigned int value);
										// 返回值:若成功,返回0;若出错,返回-1
  • pshared参数表明是否在多个进程中使用信号量,如果是,将其设置成一个非0值;
  • value参数指定了信号量的初始值;
  • 需要声明一个sem_t类型的变量并把它的地址传递给sem_init来实现初始化,如果要在两个进程之间使用信号量,需要确保sem参数指向两个进程之间共享的内存范围。

对未命名信号量的使用已经完成时,可以调用sem_destroy函数丢弃它:

#include <semaphore.h>

int sem_destroy(sem_t *sem);
										// 返回值:若成功,返回0;若出错,返回-1

sem_getvalue函数可以用来检索信号量的值:

#include <semaphore.h>

int sem_getvalue(sem_t *restrict sem, int *restrict valp);
										// 返回值:若成功,返回0;若出错,返回-1
  • 成功后,valp指向的整数值将包含信号量值。

15.11 客户进程-服务器进程属性

实例代码

chapter15

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

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

相关文章

5G NR SRS功率控制

3GPP TS 38.213 7.3.1节中&#xff0c;关于SRS的功率控制算法如下&#xff1a; 其中&#xff0c; : 表示在载波f服务小区c以及SRS的发送时隙i UE被配置的最大发射功率&#xff1b; : 表示在载波f&#xff0c;BWP b, 服务小区c&#xff0c;SRS资源集qs所配置的p0值&#xff1…

【GateWay快速入门】 —— 每天一点小知识

&#x1f4a7; Z o o K e e p e r 快速入门 \color{#FF1493}{ZooKeeper快速入门} ZooKeeper快速入门&#x1f4a7; &#x1f337; 仰望天空&#xff0c;妳我亦是行人.✨ &#x1f984; 个人主页——微风撞见云的博客&#x1f390; &#x1f433; 《数据结构与算法》专…

子线程不显示Toast?

Handler不仅在ANR过程中有用到&#xff0c;Toast中也用到了Handler。 代码如下&#xff1a; public class MainActivity extends AppCompatActivity {Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.lay…

chatgpt赋能python:Python如何进行高效的查询?

Python如何进行高效的查询&#xff1f; 在Python中&#xff0c;查询是一项非常常见的操作&#xff0c;而高效的查询在数据量增大时可以显著提高程序的性能和效率。本文将介绍Python中的查询方式以及如何进行高效的查询操作。 Python中的查询方式 Python中查询的方式主要分为…

嵌入式容器源码解析

问题分析 不同于使用springmvc,在我们使用springboot时无需配置tomcat就可以直接使用&#xff0c;这就说明springboot已经在我们启动项目时将tomcat配置好了&#xff0c;接下来我们就来看看springboot底层是怎么实现的。 源码解析 ServletWebServerFactoryAutoConfiguratio…

基于Java乡镇自来水收费系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

chatgpt赋能python:如何使用Python查看当前路径

如何使用Python查看当前路径 作为一名有10年Python编程经验的工程师&#xff0c;我今天想和大家分享如何使用Python来查看当前路径。这在开发过程中非常有用&#xff0c;尤其是当需要导入文件或访问某个文件夹时。在本文中&#xff0c;我将介绍在Windows、Mac和Linux系统上如何…

深蓝学院C++基础与深度解析笔记 第 3 章 数组、 vector 与字符串

第 3 章 数组、 vector 与字符串 一、数组 将一到多个相同类型的对象串连到一起&#xff0c;所组成的类型 int a → int b[10] b的类型是int[10]; A、 数组的初始化方式&#xff1a; ● 缺省初始化 ● 聚合初始化&#xff08; aggregate initialization &#xff09; int b[3…

ansible的部署和命令模块和playbooks剧本

系列文章目录 文章目录 系列文章目录一、Ansible1、Ansible简介2、Ansible特点及优势3、Ansible核心程序4、Ansible工作原理及流程5.部署Ansible自动化运维工具6、Ansible常用模块 二、playbooks剧本1.playbooks2、playbooks组成部分3、playbooks启动及检测 总结 一、Ansible …

简单的TCP网络程序·多进程、多线程(后端服务器)

前文链接 -- 简单的TCP网络程序单进程 上篇文章中&#xff0c;实现了TCP网络通信的的单进程版本&#xff0c;因为实现的是一个死循环的逻辑&#xff0c;是串行实运行的&#xff0c;显然这和实际中的TCP通信是不同的&#xff0c;为了解决这方面的问题&#xff0c;需要使用多进程…

B+树:MySQL数据库索引的实现

作为一个软件开发工程师&#xff0c;你对数据库肯定再熟悉不过了。作为主流的数据存储系统&#xff0c;它在我们的业务开发中&#xff0c;有着举足轻重的地位。在工作中&#xff0c;为了加速数据库中数据的查找速度&#xff0c;我们常用的处理思路是&#xff0c;对表中数据创建…

chatgpt赋能python:Python怎么横着输出?

Python怎么横着输出&#xff1f; 如果你是一名有10年Python编程经验的工程师&#xff0c;你一定已经经历过许多项目&#xff0c;很可能你曾经需要对代码进行一些横向格式化或输出。Python作为一种高级编程语言&#xff0c;有各种各样的技巧和技能。在本文中&#xff0c;我们将…

node安装后的全局环境变量配置

安装node时&#xff0c;位置最好不要装在c盘&#xff0c;这里&#xff0c;我在D盘下创建了文件夹"node"&#xff0c;安装地址选择在该文件夹下 一直next&#xff0c;直到安装结束&#xff0c;打开"node"文件夹&#xff0c;安装完后&#xff0c;里面的配置…

C++【STL】之vector的使用

文章目录&#xff1a; vector介绍vector使用1. 默认成员函数1.1 默认构造1.2 拷贝构造1.3 析构函数1.4 赋值重载 2. 迭代器2.1 正向迭代器2.2 反向迭代器 3. 容量操作3.1 获取空间数据3.2 空间扩容3.3 大小调整3.4 空间缩容 4. 数据访问4.1 下标随机访问4.2 获取首尾元素 5. 数…

chatgpt赋能python:Python怎么横向键盘输入?

Python怎么横向键盘输入&#xff1f; 如果你是一位使用Python进行编程的工程师&#xff0c;你肯定明白快速而准确地输入代码的重要性。现在&#xff0c;许多程序员都找到了一个方法来更快地输入代码-横向键盘输入。 什么是横向键盘输入&#xff1f; 横向键盘输入是一种方法&…

基于Java校园驿站管理系统设计实现(源码+lw+部署文档+讲解等)

博主介绍&#xff1a; ✌全网粉丝30W,csdn特邀作者、博客专家、CSDN新星计划导师、java领域优质创作者,博客之星、掘金/华为云/阿里云/InfoQ等平台优质作者、专注于Java技术领域和毕业项目实战 ✌ &#x1f345; 文末获取源码联系 &#x1f345; &#x1f447;&#x1f3fb; 精…

力扣题库刷题笔记5--最长回文子串

1、题目如下&#xff1a; 2、个人Python代码实现&#xff1a; 首先想到的是通过类似冒泡排序的方式进行切片&#xff0c;然后判断切片的子字符串是否为回文字符串&#xff0c;然后记录出最长的回文字符串&#xff0c;代码如下&#xff1a; 可以看到&#xff0c;通过切片的方式&…

合宙Air724UG Cat.1模块硬件设计指南--电源供电

电源供电 简介 在模块应用设计中&#xff0c;电源设计是很重要的一部分&#xff0c;供电部分的电路设计不当会造成模块出现工作异常、指标恶化等现象&#xff0c;而良好的电源设计方案能够给模块提供稳定的工作状态。 特性 模块主供电VBAT&#xff1a;3.3V~4.3V&#xff0c;推…

设计模式—模板方法模式

模板方法模式&#xff1a; 定义一个操作的流程框架&#xff0c;而将流程中一些步骤延迟到子类中实现。使得子类在不改变流程结构的情况下&#xff0c;重新定义流程中的特定步骤。 主要角色&#xff1a; 抽象类: 负责给出操作流程的轮廓或框架&#xff0c;由模板方法和若干基…

用户模块的增删改查接口设计

MongoDB 数据库常用操作 MongoDB数据库中常用的操作包括&#xff1a; 插入数据&#xff1a;使用insertOne()或insertMany()方法向集合中插入数据。查询数据&#xff1a;使用find()方法查询满足条件的数据。更新数据&#xff1a;使用updateOne()或updateMany()方法更新满足条件…