进程、线程和进程间的通信-程序员宅基地

技术标签: linux  

技术笔记!

前言

一、概念

1.程序:存放在磁盘上的指令和数据的有序集合(文件),静态的

2.进程:执行一个程序所分配的资源的总称;进程是程序的一次执行过程

                动态的,包括创建、调度、执行和消亡;

3.进程和程序内容区别:

BSS段通常是指用来放程序中初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。

数据段:通常是指用来存放程序中初始化的全局变量的一块内存区域

代码段:通常是指用来存放程序执行代码的一块内存区域。这部分区域的大小在程序运行前就已经确定,在代码段中,也有可能包含一些只读的常数变量,例如字符串常量等。

堆(heap):堆是用于存放进程运行中被动态分配的内存段,当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上(堆被扩张);当利用free等函数释放内存时,被释放的内存从堆中被剔除(堆被缩减)。
栈(stack):栈又称堆栈, 是用户存放程序临时创建的局部变量,(但不包括static声明的变量,static意味着在数据段中存放变量)。除此以外,在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也会被存放回栈中。由于栈的先进后出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上讲,我们可以把堆栈看成一个寄存、交换临时数据的内存区。
4.进程的类型:
5. 进程状态

6. 进程状态图

二、进程

1.查看进程信息

(1)ps        查看系统进程快照

参数:

-e:显示所有进程;

-l:长格式显示更加详细的信息;

-f:全部列出,通常和其他选项;       

(2)top        查看进程动态信息

    shift + >        后翻页

    shift + <        前翻页

     top -p PID    查看某个进程

(3)/proc        查看进程详细信息

2.前后台进程切换

(1)jobs        查看后台进程;

(2)bg        将挂起的进程后台运行;

(3)fg        把后台运行的进程放到前台运行

3.改变进程优先级

(1)nice        按用户指定的优先级运行进程

(2)renice        该变正在运行进程的优先级

ctrl + z        把运行的前台进程转为后台并停止。

./test  &       把test程序后台运行。

4.进程的创建与结束

(1)子进程:由另外一个进程(对应称之为父进程)所创建的进程。
(2)子进程的创建         fork

函数:

#include  <unistd.h>

pid_t fork(void);

返回值:创建新的进程,失败返回-1;成功时父进程返回子进程的进程号,子进程返回0;

                通过fork的返回值区分父进程和子进程。

eg:

注意事项:

        (1)子进程只执行fork之后的代码;

        (2)父子进程执行顺序由操作系统决定的。

子进程继承了父进程的内容;

父子进程有独立的地址空间,互不影响

若父进程先结束

 子进程成为孤儿进程,被init进程收养

 子进程变成后台进程

若子进程先结束

 父进程如果没有及时回收,子进程变成僵尸进程

(2)进程结束        exit

函数:

        #include <stdlib.h>

        #include <unistd.h>

        void exit(int status);

        void _exit(int status);

        void _Exit(int status);

结束当前的进程并将status返回

exit结束进程时会刷新(流)缓冲区

eg:

return 和exit的区别

main函数结束时会隐式地调用exit函数,普通函数return是返回上一级。

5.进程回收        wait

(1)wait

#Include <sys/wait.h>

pid_t wait(int * status);

返回值:成功时返回回收子进程的进程号;失败时返回EOF;

若子进程没有结束,父进程一直堵塞;若有多个子进程,那个子进程先结束就先回收;

status  指定用于保存子进程返回值和结束方式的地址;

status  为NULL表示直接释放子进程的PCB,不接收返回值;

eg:

   int status;

  pid_t pid;



  if ((pid = fork()) < 0) {

     perror(“fork”);  exit(-1);

  }

  else if (pid == 0) {

     sleep(1);  exit(2);

  }

  else {

     wait(&status);  printf(“%x\n”, status)

}

进程返回值和结束方式:

(2)  waitpid

函数:

#include <sys/wait.h>

pid_t waitpid(pid_t pid ,int* status, int option);

返回值:成功时返回回收的子进程的pid或0;失败时返回EOF;

参数:

pid 可用于指定回收哪个子进程或任意子进程;

status  指定用于保存子进程返回值和结束方式的地址;

option 指定收回方式,0或WNOHANG(不管回收的进程是否结束都回收);

eg:

6.exec函数族

        进程调用exec函数族执行某个程序;进程当前内容被指定的程序替换;实现让父子进程执行不同的程序。

(1)execl /execlp

#include <unistd.h>

int execl(const char *path, const char *arg, …);

int execlp(const char *file, const char *arg, …);                //需要使用环境变量

返回值:成功时执行指定的程序;失败返回EOF;

参数:

path        执行的程序名称,包含路径

arg...        传递给执行的程序的参数列表

file             执行的程序的名称,在PATH中查找

eg:

(2)execv /execvp

和execl函数的区别在于将参数封装成指针数组

函数:

#include  <unistd.h>

int execv(const char *path, char *const argv[]);

int execvp(const char *file, char *const argv[]);

返回值:成功时执行指定的程序;失败时返回EOF;

参数:arg...封装成指针数组的形式。

eg:

(3)system

函数:

#include <stdlib.h>

int system(const char* command);

返回值:成功时返回命令command的返回值;失败时返回EOF;

当前进程等待command执行结束后才继续执行。

注意事项:

两个函数区别execlp不需要写文件名全路径,在PATH查找

 最后一个参数必须用空指针(NULL)作结束

进程当前内容被指定的程序替换,但进程号不变

第0个参数必须要写,虽然它没有使用

7.守护进程(Deamon Process)

守护进程:守护进程又叫精灵进程(Daemon Process),它是一个生存期较长的进程,通常独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。

特点:

始终在后台运行,独立于任何终端,周期性的执行某种任务或等待处理特定事件。它是个特殊的孤儿进程,这种进程脱离终端,为什么要脱离终端呢?之所以脱离于终端是为了避免进程被任何终端所产生的信息所打断,其在执行过程中的信息也不在任何终端上显示。由于在 Linux 中,每一个系统与用户进行交流的界面称为终端,每一个从此终端开始运行的进程都会依附于这个终端,这个终端就称为这些进程的控制终端,当控制终端被关闭时,相应的进程都会自动关闭。

守护进程创建:
(1)创建子进程,父进程退出:

if (fork() > 0){

exit(0);

}

子进程变成孤儿进程,被init进程收养;子进程在后台运行。

(2)子进程创建新会话:setsid()

if (setsid() < 0) {

exit(-1);

}

子进程成为新的会话组长;子进程脱离原先的终端。

(3)改变当前工作目录

chdir("/");

chdir("/tmp");

守护进程一直在后台运行,其工作目录不能被卸载;重新设定当前工作目录cwd.

(4)重设文件权限掩码

if (umask(0) < 0){

exit(-1);

}

文件掩码权限设置为0;只影响当前进程。

(5)关闭打开的文件描述符

int i;

for( i=0 ; i< 3;i++){

close(i);

}

关闭所有从父进程继承的打开文件;已脱离终端,stdin/stdout/stderr无法再使用。

eg:

创建守护进程,每隔1秒将系统时间写入文件time.log 。

8.GDB调试多进程程序

set follow-fork-mode child        设置GDB调试子进程

set follow-fork-mode parent        设置GDB调试父进程

set detach-on-fork   on/off           设置GDB跟踪调试单个进程或多个

on: 只调试父进程或子进程的其中一个,(根据follow-fork-mode来决定),这是默认的模式

off父子进程都在gdb的控制之下,其中一个进程正常调试(根据follow-fork-mode来决定),另一个进程会被设置为暂停状态。

info inferiord        显示GDB调试的进程

inferiors        进程序号(1,2,3.....)   切换GDB调试的进程

三、线程

线程查看命令:ps -eLf
1.进程和线程的区别

2.线程的特点

3.线程共享资源

vc

4.线程私有资源

5.linux线程库

使用pthread线程库,在编译时得链接pthread动态库(-lthread);

6.线程创建       pthread_create

返回值:成功时返回0,失败时返回错误码

参数:

thread        线程对象

attr        线程属性,NULL代表默认属性

routine        线程执行的函数

arg        传递给routine的参数,参数是void*,注意传递参数格式。

注意事项:

(1)主进程的退出,它创建的线程也会退出。

(2)线程创建需要时间,如果主进程马上退出,那线程不能得到执行。

7.线程结束        pthread_exit

结束当前进程;retval可被其他进程通过pthread_join获取;线程私有资源被释放。

8.线程查看tid函数

9.线程间参数传递

10.线程回收        pthread_join

对于一个默认属性的线程A来说,线程占用的资源并不会因为执行结束而得到释放,得调用pthread_join函数释放。

返回值:成功时返回0,失败时返回错误码;

thread要收回的线程对象;调用线程阻塞直到thread结束;*retval接收线程trhread的返回值。

11.线程分离        pthread_detach

作用是在线程结束后自动释放线程所占用的系统资源,无需等待主线程回收。

(1)int pthread_detach(pthread_t thread):

成功时返回0;失败返回错误码。

——指定该状态,线程主动于主控线程断开关系。线程结束后(不会产生僵尸线程)。

(2)pthread_attr_t attr;                //通过线程属性来设置游离态(分离态)

eg:

12.线程的取消        pthread_cancel

作用:随时杀死一个线程

int pthread_cancel(pthread_t thread);

注意:线程的取消要有取消点才可以,不是说取消就取消,线程的取消点主要是阻塞的系统调用。

如果没有取消点,可以手动设置一个:void pthread_testcancel(void);

设置取消使用或禁止使用:

int pthread_setcancelstate(int state, int *oldstate);

state的参数:

PTHREAD_CANCEL_ENABLE(enable  可以使用)

PTHREAD_CANCEL_DISABLE(disable 不能使用)

设置取消类型:

int pthread_setcanceltype(int type,int* outtype);

PTHREAD_CANCEL_DEFERRED                等到取消点才取消

PTHREAD_CANCEL_ASYNCHRONOUS           目标线程会立即取消

eg:

13.线程的清理        pthread_cleanup_xx

作用:当线程正常终止,需要清理一些资源。

void pthread_cleanup_push(void(*routine) (void*),void *arg)

void pthread_cleanup_pop(int execute);

成对使用

routine函数被执行的条件:

(1)被pthread_cancel 取消掉。

(2)执行pthread_exit。

(3)非0参数执行pthread_cleanup_pop()。

注意事项:

(1)必须成对使用,即使pthread_cleanup_pop不会被执行到也必须写上,否则编译错误。

(2)pthread_cleanup_pop()被执行且参数为0,pthread_cleanup_push()回调函数routine不会执行。

(3)pthread-cleanup_push和pthread_cleanup_pop可以写成多对,routine执行顺序正好相反,跟栈类似,遵循先创建后调用的规则。

(4)线程内的return可以结束线程,也可以给pthread_join返回值,但不能触发pthread_cleanup_push里面的回调函数,所以我们结束线程尽量使用pthread_exit退出线程。

eg:

14.线程的互斥和同步        pthread_mutex

临界资源:一次只允许一个任务(进程、线程)访问的共享资源。

临界区:访问临界资源的代码。

互斥机制:mutex互斥锁;任务访问临界资源前申请锁,访问完后释放锁。

(1)互斥锁的创建和销毁

创建:

动态方式:

返回值:成功时返回0,失败时返回错误码。

参数:

mutext        指向要初始化的互斥对象;

attr               互斥锁属性,NULL表示缺省属性;

静态方式:

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;

man函数出现No manual entry for pthread_mutex_xxx

解决办法:apt-get install manpages_posix-dev

销毁:

在Linux中,互斥锁并不占用任何资源,因此LinuxThreads中的 pthread_mutex_destroy()除了检查锁状态以外(锁定状态则返回EBUSY)没有其他动作。

(2)互斥锁的申请和释放

申请——pthread_mutex_lock

返回值:成功时返回0;失败时返回错误码。

参数:mutex  指向要初始化的互斥锁。

pthread_mutex_lock 如果无法获得锁,任务阻塞;

pthread_mutex_trylock如果无法获得锁,返回EBUSY而不是挂起等待;

释放——pthread_mutex_unlock

返回值:成功时返回0;失败时返回错误码。

参数:mutex  指向要初始化的互斥锁。

执行完临界区要及时释放锁。

15.读写锁        pthread_rwlock_

作用:提高线程执行效率。

特性:

(1)写者:写者使用写锁,如果当前没有读者,也没有其他写者,写者立即获得写锁;否则写者将等待,直到没有读者和写者。

(2)读者:读者使用读锁,如果当前没有写者,读者立即获得读锁;否则读者等待,直到没有写者。

注意事项:

(1)同一时刻只有一个线程可以获得写锁,同一时刻可以有多个线程获得读锁。

(2)读写锁出于写锁状态时,所有试图对读写锁加锁的线程,不管是读者试图加读锁,还是写者试图加写锁,都会被阻塞。

(3)读写锁处于读锁状态时,有写者试图加写锁时,之后的其他线程的读锁请求会被阻塞,以避免写者长时间的不写锁。

eg:

16.死锁

死锁是一种资源竞争的情况,其中每个进程都无法前进,因为它们都在等待其他进程所持有的资源。

避免方法:

(1)锁越少越好,最好使用一把锁;

(2)调整好锁的顺序;

eg:

四、条件变量和线程池

1.条件变量

应用场景:生产者消费者问题,是线程同步的一种手段。

必要性:为了实现等待某个资源,让线程休眠,提高运行效率。

使用步骤:

(1)初始化:

静态初始化

pthread_cond_t cond = PTHREAD_COND_INITIALZER;        //初始化条件变量

pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALZER;                //初始化互斥量

或使用动态初始化

pthread_cond_init(&cond);

(2)生产资源线程:

pthread_mutex_lock(&mutex);

开始产生资源

pthread_cond_signal(&cond);        //通知一个消费进程

或者

phtread_cond_broadcast(&cond);        //广播通知多个消费进程

pthread_mutex_unlock(&mutex);

(3)消费者线程:

pthread_mutex_lock(&mutex);

while(如果没有资源){//防止惊群效应

        pthread_cond_wait(&cond,&mutex);

}

有资源了,消费资源

pthread_mutex_unlock(&mutex);

注意事项:

(1)pthread_cond_wait(&cond,&mutex),在没有资源等待是先unlock休眠,等资源到了,再lock,所以pthread_cond_wait和pthread_mutex_lock必须配对使用。

(2)如果pthread_cond_signal或者pthread_cond_broadcast早于pthread_cond_wait,则有可能会丢失信号。

(3)pthread_cond_broadcast信号会被多个线程收到,这叫线程的惊群效应。所以需要加上判断条件while循环。

eg:

#include <pthread.h>
#include <unistd.h>
#include <stdio.h>
#include <stdlib.h>
//初始化
pthread_cond_t  hasTaxi=PTHREAD_COND_INITIALIZER;
pthread_mutex_t lock  = PTHREAD_MUTEX_INITIALIZER;
//声明一个结构体
struct taxi{
    struct taxi *next;
    int num;
};

struct taxi *Head=NULL;
//线程函数执行体,taxiarv
void *taxiarv(void *arg){
    printf("taxi arrived thread\n");
    pthread_detach(pthread_self());//线程分离
    struct taxi *tx;
    int i=1;
    while(1){
        tx = malloc(sizeof(struct taxi));
        tx->num = i++;
        printf("taxi %d comming\n",tx->num);

        pthread_mutex_lock(&lock);
        tx->next = Head;
        Head = tx;
        pthread_cond_signal(&hasTaxi);
        //pthread_cond_broadcast(&hasTaxi);
        pthread_mutex_unlock(&lock);
        sleep(1);
    }
    pthread_exit(0);
}
//线程函数执行体,takeTaxi
void *takeTaxi(void *arg){
    printf("take taxi thread\n");
    pthread_detach(pthread_self());
    struct taxi *tx;
    while(1){
        pthread_mutex_lock(&lock);
        while(Head==NULL)
        {
            pthread_cond_wait(&hasTaxi,&lock);
        }
        tx = Head;
        Head=tx->next;
        printf("%d,Take taxi %d\n",(int)arg,tx->num);
        free(tx);
        pthread_mutex_unlock(&lock);
    }
    pthread_exit(0);
}
//主函数
int main(){
    pthread_t tid1,tid2,tid3;

    pthread_create(&tid1,NULL,taxiarv,NULL);
//    sleep(5);
    pthread_create(&tid2,NULL,takeTaxi,(void*)1);
    pthread_create(&tid2,NULL,takeTaxi,(void*)2);
    pthread_create(&tid2,NULL,takeTaxi,(void*)3);

    while(1) {
        sleep(1);
    }

}

2.线程池

通俗的讲就是一个线程的池子,可以循环的完成任务的一组线程集合。

必要性:

我们平时创建一个线程,完成某一个任务,等待线程的退出。但当需要创建大量的线程时,假设T1创建线程时间,T2为在线程任务执行时间,T3线程销毁时间当 T1+T3 > T2,这时候就不划算了,使用线程池可以降低频繁创建和销毁线程所带来的开销,任务处理时间比较短的时候这个好处非常显著

线程池的基本结构:

(1)任务队列,存储需要处理的任务,由工作线程来处理这些任务。

(2)线程池工作线程,它是任务队列任务的消费者,等待新任务的信号。

线程池的实现:
(1)创建线程池的基本结构:

任务队列链表

typedef struct Task;

线程池结构体

typedef struct Thread_poll;

(2)线程池的初始化:

poll_init()

{

        创建一个线程池结构;

        实现任务队列互斥锁和条件变量的初始化;

        创建n个工作线程;

}

(3)线程池添加任务

pool_add_task

{

        判断是否有空闲的工作线程;

        给任务队列添加一个节点;

        给工作线程发送信号newtask;

}

(4)实现工作线程

work_thread

{

        while(1){

                等待newtask任务信号;

                从任务对列中删除节点;

                执行任务;        

        }

}

(5)线程池的销毁

pool_destory

{

        删除任务队列链表所有节点,释放空间;

        删除所有的互斥锁、条件的变量;

        删除线程池,释放空间;

}

eg:

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

#define POOL_NUM 10
//任务结构体
typedef struct Task{
    void *(*func)(void *arg);//指针函数
    void *arg;
    struct Task *next;
}Task;
//线程池结构体
typedef struct ThreadPool{
    pthread_mutex_t taskLock;
    pthread_cond_t newTask;
    pthread_t tid[POOL_NUM];
    Task *queue_head;
    int busywork;

}ThreadPool;

ThreadPool *pool;
//工作线程
void *workThread(void *arg){
    while(1){
        pthread_mutex_lock(&pool->taskLock);
        pthread_cond_wait(&pool->newTask,&pool->taskLock);

        Task *ptask = pool->queue_head;
        pool->queue_head = pool->queue_head->next;

        pthread_mutex_unlock(&pool->taskLock);

        ptask->func(ptask->arg);
        pool->busywork--;
    }
}
//需要执行的任务进程
void *realwork(void *arg){
    printf("Finish work %d\n",(int)arg);
}
//增加任务
void pool_add_task(int arg){
    Task *newTask;
    
    pthread_mutex_lock(&pool->taskLock);
    while(pool->busywork>=POOL_NUM){
        pthread_mutex_unlock(&pool->taskLock);
        usleep(10000);
        pthread_mutex_lock(&pool->taskLock);
    }
    pthread_mutex_unlock(&pool->taskLock);
    
    newTask = malloc(sizeof(Task));
    newTask->func =  realwork;
    newTask->arg = arg;
    
    pthread_mutex_lock(&pool->taskLock);
    Task *member = pool->queue_head;
    if(member==NULL){
        pool->queue_head = newTask;
    }else{
       while(member->next!=NULL){
            member=member->next;
       }
       member->next = newTask;

    }
    pool->busywork++;
    pthread_cond_signal(&pool->newTask);

    pthread_mutex_unlock(&pool->taskLock);
}
//线程池的初始化
void pool_init(){
    pool = malloc(sizeof(ThreadPool));
    pthread_mutex_init(&pool->taskLock,NULL);
    pthread_cond_init(&pool->newTask,NULL);
    pool->queue_head = NULL;
    pool->busywork=0;

    for(int i=0;i<POOL_NUM;i++){
        pthread_create(&pool->tid[i],NULL,workThread,NULL);
    }
}
//销毁线程池
void pool_destory(){
    Task *head;
    while(pool->queue_head!=NULL){
        head = pool->queue_head;
        pool->queue_head = pool->queue_head->next;
        free(head);
    }
    pthread_mutex_destroy(&pool->taskLock);
    pthread_cond_destroy(&pool->newTask);
    free(pool);
}
int main(){
   pool_init();
   sleep(20);
   for(int i=1;i<=20;i++){
       pool_add_task(i);
   }
   sleep(5);
   pool_destory();

}

五、进程间的通信(IPC,InterProcess Communication)

概念:进程和进程之间交换信息。

1.通信方式

常用通信凡是:

无名管道(pipe)

有名管道(fifp)

信号(signal)

内存映射(mmap)

套接字(socket)

过时的IPC通信方式:

system V IPC

共享内存(share memory)

消息队列(message queue)

信号灯集(semaphore set)

2.无名管道(用于亲缘)(常用)

函数:int pipe(int pfd[2]);       

成功时返回0;失败返回-1;

pfd 包含两个元素的整型数组,用来保存文件描述符;

pfd[0]用于读管道;pfd[1]用于写管道。

无名管道的读写特性:

(1)读管道:

        1.管道中有数据,read返回实际读到的字节数。

        2.管道中无数据:

                1)管道写端被全部关闭。read返回0(好像读到文件结尾);

                2) 写端没有全部被关闭,read阻塞等待(不久的将来可能有数据递达,此时会让出cpu);

(2)写管道:

1. 管道读端全部被关闭, 进程异常终止(也可使用捕捉SIGPIPE信号,使进程不终止)

2. 管道读端没有全部关闭:

        1) 管道已满,write阻塞。(管道大小64K)

         2)管道未满,write将数据写入,并返回实际写入的字节数。

注意事项:
  1. 只能用于亲缘关系的进程间通信(父子进程,兄弟进程;
  2. 管道通信是单工的,一端读,一端写(程序实现设计好);
  3. 数据自己读不能自己写;
  4. 管道可以用于大于2个进程共享.

eg:

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

int main(){
    int pfd[2];
    int re;
    char buf[20]={0};
    pid_t pid;

    re = pipe(pfd);
    if(re<0){
        perror("pipe");
        return 0;
    }
    printf("%d,%d\n",pfd[0],pfd[1]);
    pid = fork();
    if(pid<0){
        perror("fork");
        return 0;
    }else if(pid>0){
        close(pfd[0]);
//        close(pfd[1]);
        int j=0;
        while(1){
            j++;
            strcpy(buf,"hhahahahah");
            for(int i=0;i<1000;i++){
                write(pfd[1],buf,strlen(buf));
            }
            printf("write %d times\n",j);
            sleep(1);
        }
    }else{
        close(pfd[1]); 
       // close(pfd[0]);
        sleep(30000);
        exit(0);
       while(1){
            re=read(pfd[0],buf,20);
            if(re>0){
                printf("read pipe=%s\n",buf);
            }else if(re==0){
                printf("re=0\n");
            }
        }
    }
}

3.有名管道(用于非亲缘)(常用)

创建管道:

管道的打开:

open(const char *path, O_RDONLY);//1

open(const char *path, O_RDONLY | O_NONBLOCK);//2

open(const char *path, O_WRONLY);//3

open(const char *path, O_WRONLY | O_NONBLOCK);//4

特点:

1有名管道可以使非亲缘的两个进程互相通信

2通过路径名来操作,在文件系统中可见,但内容存放在内存中

3 文件IO来操作有名管道

4 遵循先进先出规则

5 不支持leek操作

6 单工读写

注意事项:

1 就是程序不能以O_RDWR(读写)模式打开FIFO文件进行读写操作,而其行为也未明确定义,因为如一个管道以读/写方式打开,进程可以读回自己的输出,同时我们通常使用FIFO只是为了单向的数据传递

2 第二个参数中的选项O_NONBLOCK,选项O_NONBLOCK表示非阻塞,加上这个选项后,表示open调用是非阻塞的,如果没有这个选项,则表示open调用是阻塞的

对于以只读方式(O_RDONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_RDONLY),除非有一个进程以写方式打开同一个FIFO,否则它不会返回;如果open调用是非阻塞的的(即第二个参数为O_RDONLY | O_NONBLOCK),则即使没有其他进程以写方式打开同一个FIFO文件,open调用将成功并立即返回。

对于以只写方式(O_WRONLY)打开的FIFO文件,如果open调用是阻塞的(即第二个参数为O_WRONLY),open调用将被阻塞,直到有一个进程以只读方式打开同一个FIFO文件为止;如果open调用是非阻塞的(即第二个参数为O_WRONLY | O_NONBLOCK),open总会立即返回,但如果没有其他进程以只读方式打开同一个FIFO文件,open调用将返回-1,并且FIFO也不会被打开。

4.数据完整性,如果有多个进程写同一个管道,使用O_WRONLY方式打开管道,如果写入的数据长度小于等于PIPE_BUF4K),那么写入全部字节,或者一个字节都不写入,系统就可以确保数据决不会交错在一起。

eg:

fifow

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

int main(){
    int re;
    int fd;
    char buf[32];

    re = mkfifo("/myfifo",0666);
    if(re<0){
        perror("mkfifo");
        //return 0;
    }
    fd = open("/myfifo",O_WRONLY|O_NONBLOCK);
    if(fd<0){
        perror("open");
        return 0;
    }
    printf("after open\n");
    while(1){
        fgets(buf,32,stdin);
        write(fd,buf,strlen(buf));

    }
}

fifor

#include <sys/types.h>
#include <sys/stat.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

int main(){
    int re;
    int fd;
    char buf[32];

    fd = open("/myfifo",O_RDONLY);
    if(fd<0){
        perror("open");
        return 0;
    }
    printf("after open\n");
    while(1){
        re=read(fd,buf,32);
        if(re>0){
            printf("read fifo=%s\n",buf);
        }else if(re==0){
            exit(0);
        }
    }
}

4.内存映射(mmap)(常用)

概念:使一个磁盘文件与内存中的一个缓冲区相映射,进程可以像访问普通内存一样对文件进行访问,不必再调用read,write。

mmap()的优点实现了用户空间和内核空间的高效交互方式。

4.1创建内存映射

函数:

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);     

功能:创建共享内存映射。

函数返回值:成功返回创建的映射区首地址,失败返回MAP_FAILED( ((void *) -1) ),设置errno值 。     

  1. 参数:

  2. (1)addr:指定要映射的内存地址,一般设置为 NULL 让操作系统自动选择合适的内存地址。
  3. (2)length:必须>0。映射地址空间的字节数,它从被映射文件开头 offset 个字节开始算起。
  4. (3)prot:指定共享内存的访问权限。可取如下几个值的可选:PROT_READ(可读), PROT_WRITE(可写), PROT_EXEC(可执行), PROT_NONE(不可访问)。
  5. (4)flags:由以下几个常值指定:MAP_SHARED(共享的) MAP_PRIVATE(私有的), MAP_FIXED(表示必须使用 start 参数作为开始地址,如果失败不进行修正),其中,MAP_SHARED , MAP_PRIVATE必选其一,而 MAP_FIXED 则不推荐使用。MAP_ANONYMOUS(匿名映射,用于血缘关系进程间通信)。
  6. (5)fd:表示要映射的文件句柄。如果匿名映射写-1。
  7. (6)offset:表示映射文件的偏移量,一般设置为 0 表示从文件头部开始映射。
  8. 注意事项:

  9. (1) 创建映射区的过程中,隐含着一次对映射文件的读操作,将文件内容读取到映射区。

    (2) 当MAP_SHARED时,要求:映射区的权限应 <=文件打开的权限(出于对映射区的保护),如果不满足报非法参数(Invalid argument)错误。

    当MAP_PRIVATE时候,mmap中的权限是对内存的限制,只需要文件有读权限即可,操作只在内存有效,不会写到物理磁盘,且不能在进程间共享。

    (3) 映射区的释放与文件关闭无关,只要映射建立成功,文件可以立即关闭。

    (4) 用于映射的文件大小必须>0,当映射文件大小为0时,指定非0大小创建映射区,访问映射地址会报总线错误,指定0大小创建映射区,报非法参数错误(Invalid argument)。

    (5) 文件偏移量必须为0或者4K的整数倍(不是会报非法参数Invalid argument错误)。.

    (6)映射大小可以大于文件大小,但只能访问文件page的内存地址,否则报总线错误 ,超出映射的内存大小报段错误。

(7)mmap创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

4.2  释放内存映射:

函数:

int munmap(void* addr,size_t  length);

返回值:成功时返回0,失败返回-1,并设置errno值。

函数参数:

addr:调用mmap函数成功返回的映射区首地址。

length:映射区大小(即:mmap函数的第二个参数)。

4.3  mmap()映射的种类:

(1)基于文件的映射。

(2)匿名映射。

适用于具有亲缘关系的进程。

eg:

mmap_w

#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(){
    void *addr;
    int fd;

    fd =open("test",O_RDWR);
    if(fd<0){
        perror("open");
        return 0;
    }
    int len = lseek(fd,0,SEEK_END);   //根据重定位函数的返回值获取文件的大小; 
    addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);//六个参数
    if(addr == MAP_FAILED){
        perror("mmap");
        return 0;
    }
    close(fd);
    int i=0;
    while(i<2048){
        memcpy((addr+i),"a",1);
        i++;
        sleep(1);
    }    

}

mmap_r

#include <sys/mman.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

int main(){
    void *addr;
    int fd;
    fd =open("test",O_RDWR);
    if(fd<0){
        perror("open");
        return 0;
    }
    int len = lseek(fd,0,SEEK_END);    
    addr = mmap(NULL,2048, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);
    if(addr == MAP_FAILED){
        perror("mmap");
        return 0;
    }
    close(fd);
    while(1){
        printf("read=%s\n",(char*)(addr));
        sleep(1);
    }

}

5.systm V 共享内存

概念:System V共享内存是一种进程间通信的方式,它允许多个进程共享一块内存区域。

5.1  特点:

(1)共享内存是一种最为高效的进程通信方式,进程可以hi姐读写内存,而不需要任何数据的拷贝。

(2)共享内存在内核空间创建,可被进程映射到用户空间访问,使用灵活。

(3)由于多个进程可同时访问共享内存,因此需要同步和互斥机制配合使用。

5.2  共享内存使用步骤

5.2.1生成key——ftok();

5.2.2  创建/打开共享内存——shmget()

5.2.3  共享内存映射——shmat()

5.2.4  共享内存撤销映射——shmdt()

5.2.5  共享内存控制/删除——shmctl

删除共享内存:shmctl(shmid, IPC_RMID, NULL);

eg:

shm_w

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

int main(){
    key_t key;
    int shmid;
    char *buf;
    key = ftok("keytest",100);
    if(key<0){
        perror("ftok");
        return 0;
    }
    printf("key=%x\n",key);

    shmid = shmget(key,512,IPC_CREAT|0666);

    if(shmid<0){
        perror("shmget");
        return 0;
    }
    
    printf("shmid=%d\n",shmid);
    
    buf = shmat(shmid,NULL,0);
    if(buf<0){
        perror("shmat");
        return 0;
    }    
    strcpy(buf,"hello world");
    
}

shm_r

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

int main(){
    key_t key;
    int shmid;
    char *buf;
    key = ftok("keytest",100);
    if(key<0){
        perror("ftok");
        return 0;
    }
    printf("key=%x\n",key);

    shmid = shmget(key,512,0666);

    if(shmid<0){
        perror("shmget");
        return 0;
    }    
    printf("shmid=%d\n",shmid);
   
    buf = shmat(shmid,NULL,0);
    if(buf<0){
        perror("shmat");
        return 0;
    }    
    printf("share mem=%s\n",buf);
    while(1){
        sleep(1);
    }    
    shmdt(buf);
    shmctl(shmid, IPC_RMID, NULL);

}

查看共享内存命令:ipcs

6.信号机制(常用)

信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。

进程对信号的响应方式:缺省方式、忽略方式、捕捉方式。

6.1基础介绍
6.1 .1 信号的产生:

6.1.2  常用信号:

6.1.3  信号相关命令:

查看所有信号:kill -l

6.2 信号函数

6.2.1  信号发送函数——kill/raise

函数:int  kill (pid_t pid, int sig);//可以给所有进程发信号

            int raise(int sig);        //给自己发信号,等价于kill(getpid(),signo);

返回值:成功  0;失败  EOF;

参数

pid  接收进程的进程号;

pid:          > 0:发送信号给指定进程

                 = 0:发送信号给跟调用kill函数的那个进程处于同一进程组的进程。

                 < -1: 取绝对值,发送信号给该绝对值所对应的进程组的所有组员

                   = -1:发送信号给,有权限发送的所有进程。

   signum:待发送的信号。

6.2.2  定时器函数

unsigned int alarm(unsigned int secondes);

功能:定时发送SIGALRM给进程;

参数:

seconds:定时秒数;

返回值:上次定时剩余时间;

ualarm()         循环发送

useconds_t ualarm(useconds_t usecs, useconds_t interval);

以useconds为单位,第一个参数为第一次产生时间,第二个参数为间隔产生;

int setitimer(int which,const struct itimerval* new_value,struct itimerval* old_value);

功能:定时的发送alarm信号;

参数:

which: 

ITIMER_REAL:以逝去时间递减。发送SIGALRM信号;(常用)

ITIMER_VIRTUAL: 计算进程(用户模式)执行的时间。 发送SIGVTALRM信号;

ITIMER_PROF: 进程在用户模式(即程序执行时)和核心模式(即进程调度用时)均计算时间, 发送SIGPROF信号。

new_value:  负责设定 timeout 时间                

old_value:   存放旧的timeout值,一般指定为NULL

struct itimerval {

struct timeval it_interval;  // 闹钟触发周期

struct timeval it_value;    // 闹钟触发时间

};

struct timeval {

    time_t      tv_sec;         /* seconds */

    suseconds_t tv_usec;        /* microseconds */

};

eg:

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/posix_types.h>
#include <sys/time.h>

typedef void (*sighandler_t)(int);

void handle(int sig){
   if(sig == SIGINT){
        printf("I cath the SIGINT \n");
   }else if (sig==SIGALRM){
       printf("second timer \n");
   }
}


int main(){
    struct sigaction act;
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);

    struct itimerval timevalue;
    timevalue.it_interval.tv_sec = 1;
    timevalue.it_interval.tv_usec = 0;
    timevalue.it_value.tv_sec = 5;
    timevalue.it_value.tv_usec = 0;

    setitimer(ITIMER_REAL,&timevalue, NULL);
    sigaction(SIGALRM,&act,NULL);

    while(1){
    }

} 

6.3  信号的捕捉

6.3.1  限号捕捉过程

1.定义新的信号的执行函数handle。

2.使用signal/sigaction函数,把自定义的handle和指定的信号相关联。

6.3.2  函数
1.signal 函数

typedef void (*sighanler_t )(int) //声明一个指针函数

sighandler_t signal(int signum,sighandler_t handler);

功能:捕捉信号执行自定义函数。

返回值:成功时返回原先的信号处理函数,失败时返回SIG_ERR。

参数:

 signo 要设置的信号类型;

eg:

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/posix_types.h>

typedef void (*sighandler_t)(int);
sighandler_t oldact;

void handle(int sig){
    printf("I cath the SIGINT \n");
//    signal(SIGINT,oldact);
}

int main(){
    oldact = signal(SIGINT,handle);

    while(1){
        sleep(1);
    }    
}

 handler 指定的信号处理函数: SIG_DFL代表缺省方式; SIG_IGN 代表忽略信号;

系统建议使用sigaction函数,因为signal在不同类unix系统的行为不完全一样。

2.sigaction函数

int sigaction(int signum,const struct sigaction* act, struct sigaction* oldact);

struct sigaction{

        void (*sa_handler)(int);

        void (*sa_sigaction)(int, siginfo_t *, void *);

        sigset_t sa_mask;

        int sa_flags;

         void (*sa_restorer)(void);

}

参数

signum:处理的信号.

act,oldact: 处理信号的新行为和旧的行为,是一个sigaction结构体。

sigaction结构体成员定义如下:

sa_handler: 是一个函数指针,其含义与 signal 函数中的信号处理函数类似

sa_sigaction: 另一个信号处理函数,它有三个参数,可以获得关于信号的更详细的信息。

sa_flags参考值如下:

        SA_SIGINFO:使用 sa_sigaction 成员而不是 sa_handler 作为信号处理函数

        SA_RESTART:使被信号打断的系统调用自动重新发起。

        SA_RESETHAND:信号处理之后重新设置为默认的处理方式。

        SA_NODEFER:使对信号的屏蔽无效,即在信号处理函数执行期间仍能发出这个信号。

re_restorer:是一个已经废弃的数据域

eg:

#include <signal.h>
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <linux/posix_types.h>

typedef void (*sighandler_t)(int);

void handle(int sig){
   if(sig == SIGINT){
        printf("I cath the SIGINT \n");
   }else if (sig==SIGALRM){
       printf("second timer \n");
       alarm(1);
   }
}

int main(){
    struct sigaction act;
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,NULL);
    alarm(1);
    sigaction(SIGALRM,&act,NULL);

    while(1){
        sleep(1);
    }

} 

6.3.3  使用SIGCHLD信号实现回收子进程

SIGCHLD的产生条件

1子进程终止时

2子进程接收到SIGSTOP信号停止时

3子进程处在停止态,接受到SIGCONT后唤醒时

eg:

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

void handle(int sig){
    wait(NULL);
    printf("Get sig =%d\n",sig);
}

int main(){
    pid_t pid;
    struct sigaction act;
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);

    pid = fork();

    if(pid>0){
        sigaction(SIGCHLD,&act,NULL);
        while(1){
            printf("this is father process\n");
            sleep(1);
        }

    }else if(pid==0){
        sleep(5);
        exit(0);
    }
}

6.4  信号集、信号的阻塞

有时候不希望在接到信号时就立即停止当前执行,去处理信号,同时也不希望忽略该信号,而是延时一段时间去调用信号处理函数。这种情况可以通过阻塞信号实现。

6.4.1  信号的阻塞

概念:信号的”阻塞“是一个开关动作,指的是阻止信号被处理,但不是阻止信号产生。

信号的状态:

信号递达(Delivery ):实际信号执行的处理过程(3种状态:忽略,执行默认动作,捕获);

信号未决(Pending):从产生到递达之间的状态。

6.4.2  信号集操作函数

sigset_t set;  自定义信号集。  是一个32bit  64bit  128bit的数组。

sigemptyset(sigset_t *set);     清空信号集。

sigfillset(sigset_t *set);   全部置1。

sigaddset(sigset_t *set, int signum);       将一个信号添加到集合中。

sigdelset(sigset_t *set, int signum);        将一个信号从集合中移除。

sigismember(const sigset_t *set,int signum); 判断一个信号是否在集合中。

设定对信号集内的信号的处理方式(阻塞或不阻塞)

#include <signal.h>

int sigprocmask( int how, const sigset_t *restrict set, sigset_t *restrict oset );

功能:对信号集内的信号进行处理。

返回值:若成功则返回0,若出错则返回-1。

首先,若oset是非空指针,那么进程的当前信号屏蔽字通过oset返回。

其次,若set是一个非空指针,则参数how指示如何修改当前信号屏蔽字。

how可选用的值:(注意,不能阻塞SIGKILL和SIGSTOP信号)

SIG_BLOCK :   把参数set中的信号添加到信号屏蔽字中;

SIG_UNBLOCK: 从信号屏蔽字中删除参数set中的信号;

SIG_SETMASK: 把信号屏蔽字设置为参数set中的信号;

eg:

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


void handle(int sig){
    printf("I get sig=%d\n",sig);
}

int main(){ 
    struct sigaction act;
    act.sa_handler = handle;
    sigemptyset(&act.sa_mask);
    act.sa_flags = 0;
    sigaction(SIGINT,&act,NULL);
    sigset_t set;
    sigemptyset(&set);
    sigaddset(&set,SIGINT);

    sigprocmask(SIG_BLOCK,&set,NULL);
    sleep(5);
    sigprocmask(SIG_UNBLOCK,&set,NULL);

    while(1){
        sleep(1);
    }
}

int pause(void);

功能:是进程一直堵塞,直到被信号中断

返回值:-1 并设置errno为EINTR;

函数行为:

1如果信号的默认处理动作是终止进程,则进程终止,pause函数么有机会返回。

2如果信号的默认处理动作是忽略,进程继续处于挂起状态,pause函数不返回。

3 如果信号的处理动作是捕捉,则调用完信号处理函数之后,pause返回-1。

4 pause收到的信号如果被屏蔽,那么pause就不能被唤醒 。

int sigsuspend(const sigset_t* sigmask);

功能:将进程的屏蔽字替换为由参数sigmask给出的信号集,然后挂起进程的执行。

参数:

sigmask:希望屏蔽的信号。

eg:

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

void handle(int sig){
    printf("I get sig=%d\n",sig);
}

void mytask(){
    printf("My task start\n");
    sleep(3);
    printf("My task end\n");

}

int main(){
    struct sigaction act;
    act.sa_handler = handle;
    act.sa_flags = 0;
    sigemptyset(&act.sa_mask);
    sigaction(SIGINT,&act,NULL);
    sigaction(SIGHUP,&act,NULL);
    sigset_t set,set2;
    sigemptyset(&set2);
    sigaddset(&set,SIGHUP);
    sigaddset(&set,SIGINT);
    pause();
    while(1){
        sigprocmask(SIG_BLOCK,&set,NULL);
        mytask();
        sigsuspend(&set2);
    }
    printf("After pause\n");
    while(1){
        sleep(1);
    } 

}    

7.  消息队列

7.1  消息队列的使用步骤

7.1.1  打开或创建消息队列

7.1.2  发送消息

7.1.3  接收消息

7.1.4  消息队列的控制

eg:

#include <stdio.h>
#include <sys/msg.h>
#include <string.h>

typedef struct{
	long msg_type;    //必须包含long表示消息队列的类型,在计算队列大小大小时不包含这个类型声明;
	char buf[128];
}msgT;

#define MSGLEN (sizeof(msgT) - sizeof(long))

int main(int argc, const char *argv[])
{
	key_t key;
	int msgid;
	int ret;
	msgT msg;
	//创建key,用于关联消息队列
	key = ftok(".",100);
	if(key < 0){
		perror("ftok");
		return -1;
	}
	//创建或打开消息队列
	msgid = msgget(key,IPC_CREAT|0666);
	if(msgid < 0){
		perror("msgget");
		return -1;
	}
	//发送消息
	msg.msg_type = 1;
	strcpy(msg.buf,"this msg type 1");
	ret = msgsnd(msgid,&msg,MSGLEN,0);
	if(ret < 0){
		perror("msgsnd");
		return -1;
	}

	msg.msg_type = 2;
	strcpy(msg.buf,"this msg type 2");
	ret = msgsnd(msgid,&msg,MSGLEN,0);
	if(ret < 0){
		perror("msgsnd");
		return -1;
	}

	msg.msg_type = 3;
	strcpy(msg.buf,"this msg type 3");
	ret = msgsnd(msgid,&msg,MSGLEN,0);
	if(ret < 0){
		perror("msgsnd");
		return -1;
	}

	msg.msg_type = 4;
	strcpy(msg.buf,"this msg type 4");
	ret = msgsnd(msgid,&msg,MSGLEN,0);
	if(ret < 0){
		perror("msgsnd");
		return -1;
	}

	msg.msg_type = 5;
	strcpy(msg.buf,"this msg type 5");
	ret = msgsnd(msgid,&msg,MSGLEN,0);
	if(ret < 0){
		perror("msgsnd");
		return -1;
	}

	return 0;
}
#include <stdio.h>
#include <sys/msg.h>
#include <string.h>

typedef struct{
	long msg_type;
	char buf[128];
}msgT;

#define MSGLEN (sizeof(msgT) - sizeof(long))

int main(int argc, const char *argv[])
{
	int msgid;
	key_t key;
	int ret;

	key = ftok(".",100);
	if(key < 0){
		perror("ftok");
		return -1;
	}

	msgid = msgget(key,IPC_CREAT|0666);
	if(msgid < 0){
		perror("msgget");
		return -1;
	}

	msgT msg;
	int count = 0;

	while(1){
		ret = msgrcv(msgid,&msg,MSGLEN,0,0);
		if(ret < 0){
			perror("msgrcv");
			return -1;
		}
		count++;
		if(count > 3){
			break;
		}
		printf("receive msg type=%d,buf=%s\n",(int)msg.msg_type,msg.buf);
	}

	ret = msgctl(msgid,IPC_RMID,NULL);
	if(ret < 0){
		perror("msgctl");
		return -1;
	}

	return 0;
}

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/weixin_52706451/article/details/136952998

智能推荐

5个超厉害的资源搜索网站,每一款都可以让你的资源满满!_最全资源搜索引擎-程序员宅基地

文章浏览阅读1.6w次,点赞8次,收藏41次。生活中我们无时不刻不都要在网站搜索资源,但就是缺少一个趁手的资源搜索网站,如果有一个比较好的资源搜索网站可以帮助我们节省一大半时间!今天小编在这里为大家分享5款超厉害的资源搜索网站,每一款都可以让你的资源丰富精彩!网盘传奇一款最有效的网盘资源搜索网站你还在为找网站里面的资源而烦恼找不到什么合适的工具而烦恼吗?这款网站传奇网站汇聚了4853w个资源,并且它每一天都会持续更新资源;..._最全资源搜索引擎

Book类的设计(Java)_6-1 book类的设计java-程序员宅基地

文章浏览阅读4.5k次,点赞5次,收藏18次。阅读测试程序,设计一个Book类。函数接口定义:class Book{}该类有 四个私有属性 分别是 书籍名称、 价格、 作者、 出版年份,以及相应的set 与get方法;该类有一个含有四个参数的构造方法,这四个参数依次是 书籍名称、 价格、 作者、 出版年份 。裁判测试程序样例:import java.util.*;public class Main { public static void main(String[] args) { List <Book>_6-1 book类的设计java

基于微信小程序的校园导航小程序设计与实现_校园导航微信小程序系统的设计与实现-程序员宅基地

文章浏览阅读613次,点赞28次,收藏27次。相比于以前的传统手工管理方式,智能化的管理方式可以大幅降低学校的运营人员成本,实现了校园导航的标准化、制度化、程序化的管理,有效地防止了校园导航的随意管理,提高了信息的处理速度和精确度,能够及时、准确地查询和修正建筑速看等信息。课题主要采用微信小程序、SpringBoot架构技术,前端以小程序页面呈现给学生,结合后台java语言使页面更加完善,后台使用MySQL数据库进行数据存储。微信小程序主要包括学生信息、校园简介、建筑速看、系统信息等功能,从而实现智能化的管理方式,提高工作效率。

有状态和无状态登录

传统上用户登陆状态会以 Session 的形式保存在服务器上,而 Session ID 则保存在前端的 Cookie 中;而使用 JWT 以后,用户的认证信息将会以 Token 的形式保存在前端,服务器不需要保存任何的用户状态,这也就是为什么 JWT 被称为无状态登陆的原因,无状态登陆最大的优势就是完美支持分布式部署,可以使用一个 Token 发送给不同的服务器,而所有的服务器都会返回同样的结果。有状态和无状态最大的区别就是服务端会不会保存客户端的信息。

九大角度全方位对比Android、iOS开发_ios 开发角度-程序员宅基地

文章浏览阅读784次。发表于10小时前| 2674次阅读| 来源TechCrunch| 19 条评论| 作者Jon EvansiOSAndroid应用开发产品编程语言JavaObjective-C摘要:即便Android市场份额已经超过80%,对于开发者来说,使用哪一个平台做开发仍然很难选择。本文从开发环境、配置、UX设计、语言、API、网络、分享、碎片化、发布等九个方面把Android和iOS_ios 开发角度

搜索引擎的发展历史

搜索引擎的发展历史可以追溯到20世纪90年代初,随着互联网的快速发展和信息量的急剧增加,人们开始感受到了获取和管理信息的挑战。这些阶段展示了搜索引擎在技术和商业模式上的不断演进,以满足用户对信息获取的不断增长的需求。

随便推点

控制对象的特性_控制对象特性-程序员宅基地

文章浏览阅读990次。对象特性是指控制对象的输出参数和输入参数之间的相互作用规律。放大系数K描述控制对象特性的静态特性参数。它的意义是:输出量的变化量和输入量的变化量之比。时间常数T当输入量发生变化后,所引起输出量变化的快慢。(动态参数) ..._控制对象特性

FRP搭建内网穿透(亲测有效)_locyanfrp-程序员宅基地

文章浏览阅读5.7w次,点赞50次,收藏276次。FRP搭建内网穿透1.概述:frp可以通过有公网IP的的服务器将内网的主机暴露给互联网,从而实现通过外网能直接访问到内网主机;frp有服务端和客户端,服务端需要装在有公网ip的服务器上,客户端装在内网主机上。2.简单的图解:3.准备工作:1.一个域名(www.test.xyz)2.一台有公网IP的服务器(阿里云、腾讯云等都行)3.一台内网主机4.下载frp,选择适合的版本下载解压如下:我这里服务器端和客户端都放在了/usr/local/frp/目录下4.执行命令# 服务器端给执_locyanfrp

UVA 12534 - Binary Matrix 2 (网络流‘最小费用最大流’ZKW)_uva12534-程序员宅基地

文章浏览阅读687次。题目:http://acm.hust.edu.cn/vjudge/contest/view.action?cid=93745#problem/A题意:给出r*c的01矩阵,可以翻转格子使得0表成1,1变成0,求出最小的步数使得每一行中1的个数相等,每一列中1的个数相等。思路:网络流。容量可以保证每一行和每一列的1的个数相等,费用可以算出最小步数。行向列建边,如果该格子是_uva12534

免费SSL证书_csdn alphassl免费申请-程序员宅基地

文章浏览阅读504次。1、Let's Encrypt 90天,支持泛域名2、Buypass:https://www.buypass.com/ssl/resources/go-ssl-technical-specification6个月,单域名3、AlwaysOnSLL:https://alwaysonssl.com/ 1年,单域名 可参考蜗牛(wn789)4、TrustAsia5、Alpha..._csdn alphassl免费申请

测试算法的性能(以选择排序为例)_算法性能测试-程序员宅基地

文章浏览阅读1.6k次。测试算法的性能 很多时候我们需要对算法的性能进行测试,最简单的方式是看算法在特定的数据集上的执行时间,简单的测试算法性能的函数实现见testSort()。【思想】:用clock_t计算某排序算法所需的时间,(endTime - startTime)/ CLOCKS_PER_SEC来表示执行了多少秒。【关于宏CLOCKS_PER_SEC】:以下摘自百度百科,“CLOCKS_PE_算法性能测试

Lane Detection_lanedetectionlite-程序员宅基地

文章浏览阅读1.2k次。fromhttps://towardsdatascience.com/finding-lane-lines-simple-pipeline-for-lane-detection-d02b62e7572bIdentifying lanes of the road is very common task that human driver performs. This is important ..._lanedetectionlite

推荐文章

热门文章

相关标签