第二阶段上课笔记

4 篇文章 0 订阅

第二阶段上课笔记

进程间通信

一.前言

二.管道

1.pipe

PIPE: 利用文件的接口方式来实现进程间通信

PIPE就是一个文件(此文件不存在于外设中,而是在内存中),操作PIPIE就是操作文件一样。
PIPE只能用于有亲缘关系的进程间通信(要么你是我的晚辈,要么我们有同一个祖宗)。
PIPE就是水管的意思,又名无名管道。

PIPE的特点:

1. 一端读,一端写
2. 按顺序读,不支持lseek
3. 内容读走了,就没有了
4. 存在于内存,随进程持续性
5. 只能用于有亲缘关系的进程间通信(why?)

一个东西的生命周期:

(1)随代码块持续性: 局部变量
(2)随进程持续性:  全局变量、static局部变量
(3)随内核持续性:  管道文件
(4)随文件系统持续性:   文件

相关的API

1. 创建一个管道
	NAME
       pipe, pipe2 - create pipe

SYNOPSIS
       #include <unistd.h>

    pipe用来在内核中创建一个无名管道,只能用于有亲缘关系的进程间通信。
       int pipe(int pipefd[2]);
       		@pipefd: 是一个整型数组的首地址,用来保存该管道文件的两个文件描述符。
       				一个是读的文件描述符,一个是写的文件描述符。
       				pipefd[0]保存读的文件描述符
       				pipefd[1]保存写的文件描述符
       	返回值:
       		成功返回0
       		失败返回-1,errno被设置

    注意: 
    	(1)pipe创建的管道是阻塞方式的。如果读管道的时候,管道里没有数据,则会阻塞。 管道文件的关闭与普通文件一样,用close来关闭。
        (2)当管道文件没有任何进程使用时会自动关闭。

示例

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

int main()
{
	int pipefd[2];
	int ret = pipe(pipefd);
	if(ret == -1)
	{
		perror("pipe error!!\n");
		exit(-1);
	}
	pid_t pid;
	pid = fork();

	if(pid == -1)
	{
		perror("fork error!!\n");
		exit(-1);
	}
	else if(pid == 0)//子进程 读端
	{
		close(pipefd[1]);
		while(1)
		{
			char str[128]={0};
			ret = read(pipefd[0],str,128);
			if(ret == 0)
				break;
			write(STDOUT_FILENO,str,ret);
		}
        close(pipefd[0]);
	}
	else if(pid > 0)//父进程 写端
	{
		close(pipefd[0]);
		char str[1024]={0};
		ret = read(STDIN_FILENO,str,1024);
		write(pipefd[1],str,ret);
        close(pipefd[1]);
	}

	return 0;
}

2.fifo

FIFO:是为了用于没有亲缘关系的进程间进行通信的,是在PIPE的基础上改进的。

FIFO的特点:

1. 一端读,一端写
2. 按顺序读(不支持lseek)
3. 数据读走了,就没有了
4. 只存在于内存,随内核持续性
5. 它支持所有的进程

FIFO相关API

FIFO相关的API函数
1. 创建一个有名管道(fifo)
	
NAME
       mkfifo, mkfifoat - make a FIFO special file (a named pipe)

SYNOPSIS
       #include <sys/types.h>
       #include <sys/stat.h>

    mkfifo用来在文件系统中创建一个有名管道(fifo),但是这个文件只存在于内存中,只不过
    在文件系统中有一个路径而已。
       int mkfifo(const char *pathname, mode_t mode);
       		@pathname: 要创建的有名管道的文件名(带路径)
       		@mode: 创建的FIFO文件的权限,如 0664
       	返回值:
       		成功返回0
       		失败返回-1,errno被设置

fifow.c

#include<stdio.h>
#include<unistd.h>
#include<sys/stat.h>
#include<fcntl.h>
#define FIFONAME "/home/china/test/进程/temp.fifo"

int main()
{
	//1.创建一个管道
	int ret = mkfifo(FIFONAME,0664);
	if(ret == -1)
	{
		perror("mkfifo error!!!\n");
	}

	//2.打开管道
	int fd = open(FIFONAME,O_WRONLY);
	if(fd == -1)
	{
		perror("open fifo error!!!");
	}

	//3.操作管道
	char str[128]={"hello"};
	//	ret = read(STDIN_FILENO,str,128);
	
	write(fd,str,10);

	//4.关闭管道
	close(fd);

}

fifor.c

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

#define FIFONAME "/home/china/test/进程/temp.fifo"

int main()
{

	//1.打开管道
	int fd = open(FIFONAME,O_RDONLY);
	if(fd == -1)
	{
		perror("open fifo error!!!");
	}

	//2.操作管道
	char str[128]={0};
	int ret = read(fd,str,128);
	puts(str);
	unlink(FIFONAME);
	//4.关闭管道
	close(fd);

}

三.共享内存

1.原理

共享内存的原理:
在内核中开辟一段内存空间,然后把这段空间映射到各个进程的地址空间中去。

图示

结构体

	struct shmid_ds {
       struct ipc_perm shm_perm;    //共享内存区的权限
       size_t          shm_segsz;   //共享内存区的大小(字节)
       time_t          shm_atime;   //最近映射时间
       time_t          shm_dtime;   //最近解映射时间
       time_t          shm_ctime;   //最近修改信息结构体的时间
       pid_t           shm_cpid;    //创建共享内存的进程的ID
       pid_t           shm_lpid;    //最近操作共享内存的进程的ID
       shmatt_t        shm_nattch;  //当前的映射数量
       ...
   };
2.相关API
1.申请一个 system_v_ipc 的 key
NAME   ftok  -  convert  a pathname and a project identifier to a System V IPC key
SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>
    
ftok用来生成一个system_v_ipc的key,key用来唯一标识一个system_v_ipc对象在Linux内核中ftok用一个路径名和一个整数,以某种算法来生成一个唯一的key。

       key_t ftok(const char *pathname, int proj_id);
       		@pathname: 一个路径名(要真实存在的目录)
       		@proj_id : 一个整数,一般可以取工程代号。
       	返回值:
       		如果成功,返回一个system_v_ipc 对象的key
       		如果失败,返回-1,且errno被设置
2.创建或打开一个system v 共享内存区
NAME  shmget - allocates a System V shared memory segment
SYNOPSIS #include <sys/ipc.h>
         #include <sys/shm.h>

    shmget用来创建或打开一个system v 共享内存区
       int shmget(key_t key, size_t size, int shmflg);
       		@key: system v ipc 对象的key(由ftok生成)
       		@size: 以字节为单位指定创建的共享内存的大小。
       		       当实际操作为创建一个新的内存区时,必须指定一个不为0的值。
       		       如果实际操作伪访问一个已经存在的共享内存区,那么size应为0。
       		       一般size为PAGE_SIZE(4096)的整数倍。
       		@shmflg: 操作标志位
       			(1)创建
       					IPC_CREAT | 权限位
       					eg:  IPC_CREAT | 0660
       			(2)打开
       					0
       	返回值:
       		如果成功,返回 system v 共享内存区的id,后续操作该共享内存区都需要这个ID
       		如果失败,返回 -1, errno被设置
3.shmat/shmdt
NAME
       shmat, shmdt - System V shared memory operations

SYNOPSIS
       #include <sys/types.h>
       #include <sys/shm.h>

    shmat用来把共享内存区映射到进程的地址空间
       void *shmat(int shmid, const void *shmaddr, int shmflg);
       		@shmid: 共享内存区的id,表示你想要映射哪个共享内存。即shmget的返回值
       		@shmaddr: 指定映射到进程地址空间的具体哪个地址去。
       		         一般写为NULL,让操作系统自动来确定映射地址。
       		@shmflg: 映射标记
       			(1) SHM_RDONLY  只读
       			(2) 0       可读可写
       	返回值:
       		成功返回映射后的地址
       		失败返回NULL,且errno被设置
shmdt用来把映射的共享内存区解映射
       int shmdt(const void *shmaddr);
       		@shmaddr : 指向要接映射的共享内存区的首地址
       	返回值:
       		成功返回0
       		失败返回-1,errno被设置
4.shmctl 对共享内存区进行控制操作
NAME shmctl - System V shared memory control
SYNOPSIS
       #include <sys/ipc.h>
       #include <sys/shm.h>

       int shmctl(int shmid, int cmd, struct shmid_ds *buf);
       		@shmid: 共享内存的ID,表示要操作哪个共享内存区
       		@cmd :  命令号,常用的有三个
       			IPC_RMID 用来删除该共享内存,此时第三个参数buf填为NULL
       			IPC_STAT 用来获取共享内存的状态,通过buf指向用来保存该共享内存区状态信息的结构体变量。
       			IPC_SET  用来设置共享内存的状态信息,通过buf指向用来存该共享内存区状态信息的结构体变量。
       返回值:
       		成功返回0
       		失败返回-1,errno被设置
3.示例代码

1.shmr.c

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

#define SHMNAME "/home/china/test/进程" 
#define PROCID 0
int main()
{
	//1.映射KEY
	key_t key = ftok(SHMNAME,PROCID);
	if(key == -1)
	{
		perror("ftok error");
	}
	//2.打开1块内存
	int id = shmget(key,0,0);
	if(id == -1)
	{
		perror("shmget error");
		return -1;
	}
	//3.映射
	char *str = shmat(id,NULL,SHM_RDONLY);
	if(str == NULL)
	{
		perror("shmat error");
		return -1;
	}
	//4.操作
	puts(str);
	//5.解映射
    int	ret = shmdt(str);
	if(ret == -1)
	{
		perror("shmdt error!!!");
		return -1;
	}
	//6.销毁共享空间
	ret = shmctl(id,IPC_RMID,NULL);
	if(ret == -1)
	{
		perror("shmctl error!!");
		return -1;
	}
	return 0;
}

2.shmw.c

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

#define SHMNAME "/home/china/test/进程" 
#define PROCID 0
int main()
{
	//1.映射KEY
	key_t key = ftok(SHMNAME,PROCID);
	if(key == -1)
	{
		perror("ftok error");
	}
	//2.创建1块内存
	int id = shmget(key,4096,IPC_CREAT | 0664);
	if(id == -1)
	{
		perror("shmget error");
		return -1;
	}
	//3.映射
	char *str = shmat(id,NULL,0);
	if(str == NULL)
	{
		perror("shmat error");
		return -1;
	}
	//4.操作
	strcpy(str,"hello caojiayi");
	//5.解映射
    int	ret = shmdt(str);
	if(ret == -1)
	{
		perror("shmdt error!!!");
		return -1;
	}

	return 0;
}

四.信号量

1.共享资源和互斥访问
多个进程(线程)都可以访问的资源,我们称为共享资源,而在很多情况在我们需要解决,各进程(线程)对共享资源的竞争问题,即互斥。我们把只允许有限数量的进程访问的设备,称为互斥设备。
2.什么是信号量
信号量(semaphore)是一种用于提供不同进程间或一个给定进程的不同线程间同步手段的原语。信号量机制其实是程序设计者一种君子约定,是用来保护共享资源的。比如说,进程A和进程B都要访问一个互斥设备(同时只允许一个人访问),那么我们可以约定用一个信号量来表示能不能访问该设备。然后每个进程在访问该设备之前先去访问信号量(P操作),访问完互斥设备之后,再释放信号量(V操作)。
3.信号量的操作

一个进程或线程可以在某个信号量上执行三种操作:

(1)创建(create)一个信号量,同时还要求调用者指定初始值。

创建(create)一个信号量,同时还要求调用者指定初始值。信号量的值,表示它保护的对象可以同时被多少个进程(或线程)访问。

(2)等待wait一个信号量。

该操作会去测试这个信号量的值,如果其值小于或等于0,那就等待(阻塞)。
一旦其值变为大于0,就将它减1(获取该信号量),并继续执行后面的代码。
其函数实现类似于以下代码:

P(S)
{
    S--;
    if(S < 0)
	{
		阻塞该进程;将该进程插入信号量S的等待队列。
    }
}

P操作: Proberen(尝试) 荷兰语
a.   wait
b.   down(lock) ==> -1

(3)释放一个信号量。

该操作将信号量的值加1,其函数实现类似如下代码
V(S)
{
	S++;
	if(S <= 0)
	{
		从信号量S的等待队列中取出队首进程,将其插入就绪队列。
	}
}

V操作  Verhogen(增加) 荷兰语
up (unlock) ==> +1

操作

注意:
我们通常在对共享资源进行操作的代码区前后进行P/V操作,形成临界区。
所以信号量实质上保护的是操作共享资源的一段代码!

p操作()
/*操作共享资源的代码*/
。。。。
。。。。

V操作()
4.Posix Semaphore

(1).POSIX 是可移植性操作系统接口,即用Posix标准写的代码,在Posix操作系统上都能编译执行。
posix不局限于UNIX/Linux。比如,DEC,Open vms 等。

(2).Posix Semaphore 又分为有名信号量和无名信号量
有名信号量:在文件系统中有一个文件名,但不存在于文件系统中
无名信号量:没有文件名,只适用于线程或者有亲缘关系的进程间使用

(3).有名信号量与无名信号量的比较

			 有名信号量              无名信号量
创建/初始化   sem_open()            sem_init()

P操作               sem_wait()/sem_trywait()

V操作                   sem_post()

获取信号量的值           sem_getvalue()

销毁信号量   sem_unlink()           sem_destroy()
5.POSIX Semaphore API
1.创建并初始化POSIX信号量
	sem_open():创建并初始化POSIX有名信号量
		"有名":它在文件系统中存在一个名字,可用于任意进程或线程间同步(互斥),
			  随内核持续性。
	sem_init(): 创建并初始化POSIX无名信号量
		"无名":它在文件系统中没有名字。是基于内存的信号量,它只能用于有亲缘关系的进程,
		      或者线程,随进程持续性。

NAME
       sem_open - initialize and open a named semaphore

SYNOPSIS
       #include <fcntl.h>           /* For O_* constants */
       #include <sys/stat.h>        /* For mode constants */
       #include <semaphore.h>

    POSIX semaphore用类型sem_t来表示一个信号量(有名信号量或无名信号量),它
    存在于内核中。

       sem_t *sem_open(const char *name, int oflag);
       sem_t *sem_open(const char *name, int oflag,
                       mode_t mode, unsigned int value);
            	@name: 你要创建或打开的有名信号量的路径名,只能在根目录下
            	@flag: 打开标志
            		(1)打开   0
            		(2)创建   O_CREAT
            	@mode: 创建权限位。如 0664
            	@value: 要创建的信号量的初始值。
        返回值:
        	如果成功,返回一个sem_t指针
        	如果失败,返回 SEM_FAILED,同时errno被设置
       注意: 在编译时,需要指定选项 -pthread.
NAME
       sem_init - initialize an unnamed semaphore

SYNOPSIS
       #include <semaphore.h>

   	sem_init用来创建并初始化一个无名信号量
       int sem_init(sem_t *sem, int pshared, unsigned int value);
       		@sem: 指向sem_t指针
       		@pshared: 共享方式
       			0 : 不共享。用于进程内部线程间使用
       			1 : 共享。用于亲缘进程间使用。注意,如果为这种情况,需要
       			   保证sem指向的内存,在各进程间都能访问(共享内存)。
       		@value:
       			创建的无名信号量的初始值。
       返回值:
       		成功返回0
       		失败返回-1,errno被设置
       注意: 在编译时,需要指定选项 -pthread.
2.POSIX Semaphore的P操作
	
NAME
       sem_wait, sem_timedwait, sem_trywait - lock a semaphore

SYNOPSIS
       #include <semaphore.h>

    sem_wait用来阻塞的等待获取指定的信号量,如果获取到了返回0。
            如果出错了,返回-1,并errno被设置。
       int sem_wait(sem_t *sem);

    sem_trywait不去阻塞,能获取就获取,不能获取就拉倒。
    		如果获取到了呢就返回0,不能获取就返回-1(出错也返回-1)
       int sem_trywait(sem_t *sem);
3.POSIX semaphore的V操作
	NAME
       sem_post - unlock a semaphore

SYNOPSIS
       #include <semaphore.h>

    sem_post用来释放一个信号量
       int sem_post(sem_t *sem);
       		@sem: 指向你要释放的那个信号量
       	返回值:
       		成功返回0
       		失败返回-1,errno被设置
       Link with -pthread.
4.POSIX semaphore的其他操作
NAME
       sem_getvalue - get the value of a semaphore

SYNOPSIS
       #include <semaphore.h>

    sem_getvalue用来获取sem指向的信号量的值,把值保存到sval指向的内存空间中
       int sem_getvalue(sem_t *sem, int *sval);

       Link with -pthread.
5.销毁一个POSIX信号量
销毁一个POSIX有名信号量
	NAME
       sem_unlink - remove a named semaphore

SYNOPSIS
       #include <semaphore.h>

       int sem_unlink(const char *name);
       		@name: 要销毁的那个信号量的路径名
       	返回值:
       		成功返回0
       		失败返回-1,errno被设置

 销毁一个无名信号量
    NAME
       sem_destroy - destroy an unnamed semaphore

SYNOPSIS
       #include <semaphore.h>

       int sem_destroy(sem_t *sem);
       		@sem : 指向你要销毁的无名信号量
       	返回值:
       		成功返回0
       		失败返回-1,errno被设置
#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <errno.h>

#define POSIX_SEM_NAME "/test.sem"

int main()
{
	int s_value;
	/*step1: 打开或创建一个POSIX有名信号量*/
	sem_t * sem = sem_open(POSIX_SEM_NAME, O_CREAT,0664, 1);//创建一个信号量
	if(sem == SEM_FAILED)
	{
		perror("sem_open failed");
		return -1;
	}

	
	sem_wait(sem);//P操作
	/*操作共享资源的代码*/
	printf("i get the semaphore la !\n");
	int r = sem_getvalue(sem,&s_value);
	if(r != -1)
	{
		printf("the sem value is %d\n",s_value);
	}
	getchar();
	sem_post(sem);//V操作

	printf("i have released the semaphore,you can use it now!\n");
	r = sem_getvalue(sem,&s_value);
	if(r != -1)
	{
		printf("the sem value is %d\n",s_value);
	}
	
}
#include <stdio.h>
#include <semaphore.h>
#include <fcntl.h>           /* For O_* constants */
#include <sys/stat.h>        /* For mode constants */
#include <errno.h>
#define POSIX_SEM_NAME "/test.sem"

int main()
{
	int s_value;
	/*step1: 打开或创建一个POSIX有名信号量*/
	sem_t * sem = sem_open(POSIX_SEM_NAME, 0);//打开一个信号量
	if(sem == SEM_FAILED)
	{
		perror("sem_open failed");
		return -1;
	}

	
	sem_wait(sem);//P操作
	/*操作共享资源的代码*/
	printf("thank you, are you ok? !\n");
	int r = sem_getvalue(sem,&s_value);
	if(r != -1)
	{
		printf("the sem value is %d\n",s_value);
	}
	
	sem_post(sem);//V操作

	printf("bey bey!\n");
	r = sem_getvalue(sem,&s_value);
	if(r != -1)
	{
		printf("the sem value is %d\n",s_value);
	}
	
}

6.posix 和 system_v
可移植操作系统接口(英语:Portable Operating System Interface,缩写为POSIX)是IEEE为要在各种UNIX操作系统上运行软件,而定义API的一系列互相关联的标准的总称,其正式称呼为IEEE Std 1003,而国际标准名称为ISO/IEC 9945。此标准源于一个大约开始于1985年的项目。POSIX这个名称是由理查德·斯托曼(RMS)应IEEE的要求而提议的一个易于记忆的名称。它基本上是Portable Operating System Interface(可移植操作系统接口)的缩写,而X则表明其对Unix API的传承。
System V, 曾经也被称为 AT&T System V,是Unix操作系统众多版本中的一支。它最初由 AT&T 开发,在1983年第一次发布。一共发行了4个 System V 的主要版本:版本1234。System V Release 4,或者称为SVR4,是最成功的版本,成为一些UNIX共同特性的源头,例如 ”SysV 初始化脚本“ (/etc/init.d),用来控制系统启动和关闭,System V Interface Definition (SVID) 是一个System V 如何工作的标准定义。
7.system_v信号量集
1.结构体
system v 信号量其实是计数信号量集(信号量的数组)
对于系统中的每个system v 信号量集,内核维护一个如下的信息结构,
它定义在<sys/sem.h>头文件中:
	struct semid_ds {
       struct ipc_perm sem_perm;  //表示权限
       time_t          sem_otime; //最近操作信号量的时间
       time_t          sem_ctime; //最近创建或设置信号量的时间
       unsigned long   sem_nsems; //该信号量集(信号量数组)中信号量的个数
   };
2.System v 信号量操作步骤
 1,申请一个system v IPC 对象的key
 2,调用semget来创建或打开一个system v 信号量
 3,调用semctl给信号量赋初值
 4, 调用semop对信号量进行P/V 操作
 5,调用semctl销毁该信号量
3.System v 信号量相关的API函数:

(1)用ftok向内核申请一个system v ipc对象的key

NAME
       ftok  -  convert  a pathname and a project identifier to a System V IPC
       key

SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>

       key_t ftok(const char *pathname, int proj_id);

(2).用semget来创建或打开一个system v信号量对象

NAME
       semget - get a System V semaphore set identifier

SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

    semget用来创建或打开一个system v 信号量集。
       int semget(key_t key, int nsems, int semflg);
       		@key: system v ipc key
       		@nsems: 你要创建的信号量集中信号量的个数
       			如果我们不创建一个新的信号量集,而是访问一个已经存在的信号量集,
       			此处参数应该指定为0,一旦创建完一个信号量集后,我们就不能改变其中
       			的信号量个数。
       		@semflg: 标志位
       			(1)创建
       				IPC_CREAT | 权限位,如: IPC_CREAT|0664
       			(2)	打开
       				0
       	返回值:
       		成功返回信号量集的ID
       		失败返回-1,errno被设置

(3)用semctl来控制操作system v 信号量(不包括P/V操作)

	NAME
       semctl - System V semaphore control operations

SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

       int semctl(int semid, int semnum, int cmd, ...);
			@semid : 用来标识控制操作哪个信号量集,即 semget函数的返回值
			@semnum: 标识操作该信号量集的那个成员(0,1,2,3...nsems-1)
			@cmd: 操作命令号,常用的有:
				GETVAL: 获取某一个信号量的值
				GETALL: 获取该信号量集中的所有信号量的值
				SETVAL : 设置某一个信号量的值
				SETALL: 设置该信号量集中的所有信号量的值
				IPC_RMID: 删除该信号量集
				IPC_SET: 设置该信号量集的状态信息
				IPC_STAT: 获取该信号量集的状态信息
			@...arg:  可选的参数,根据cmd是什么情况,它的值是不一样的。
				if cmd == GETVAL,arg则不需要用,semctl的返回值就是该信号量的值
				if cmd == GETALL,arg应该为一个short数组的首地址,用来保存所有信号量的值
				if cmd == SELVAL,arg应该是一个short的整数,用来指定该信号量的值
				if cmd == SETALL,arg应该为一个short数组的首地址,用来保存所有信号量的值
				该参数建议用以下的共用体来表示:
                union semun arg;
                ==>
				union semun {
               		int              val;  //只用于cmd == SETVAL
               		struct semid_ds *buf;  //用于 cmd == IPC_STAT, IPC_SET 
              		unsigned short  *array; //用于 cmd == GETALL, SETALL 
               		struct seminfo  *__buf;  /* Buffer for IPC_INFO (Linux专用) */
           		};

        返回值:
        	失败返回-1,errno被设置
        	如果成功,根据不同的cmd有不同的返回值。
        		如果cmd == GETVAL,则返回指定信号量的值
        		其他返回 0 

(4)用 semop 对system v 信号量进行操作(主要是P/V操作)

NAME
       semop, semtimedop - System V semaphore operations

SYNOPSIS
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>

    semop可以同时对一个信号量集中多个或所有的信号量进行不同的操作。
    我们用一个结构体struct sembuf来表示对某一个信号量进行的操作。
    如果需要对多个信号量进行操作,则就用struct sembuf结构体数组来表示。
    	struct sembuf
    	{
    	   unsigned short sem_num; //表示您对哪个信号量进行操作(0,1,2..nsems-1)
           short          sem_op;  //表示您对该信号量进行何种操作。无论是P操作还是V操作,
                                  //实质都是对信号量的值进行操作
                    /*
                    	sem_op < 0 ,表示p操作
                    	sem_op > 0 ,表示v操作
                    	sem_op ==0 ,表示不操作
                    	最后,semval的值等==>  原semval + sem_op

                     */
           short          sem_flg; //标志位
           		/*
           		0: 	一般情况。p操作如果semval<=0,则wait
                IPC_NOWAIT: 非阻塞(不等待)。P操作如果 semval<=0,则不等待,直接返回-1
                SEM_UNDO: 撤销。在进程退出时,操作系统会还原该进程对该信号量所做的操作。
                        为了防止进程带锁死亡(退出)而造成死锁。  

           		 */
    	};
       int semop(int semid, struct sembuf *sops, size_t nsops);
       		@semid : 你要操作的信号量所属的信号量集的id
       		@sops: struct sembuf的数组的首地址,因为你可以同时对多个信号量进行操作。
       		@nsops: 第二参数struct sembuf的数组的元素个数。
       返回值:
       		如果成功返回0,对于p操作而言,表示你获取到了该信号量了
       					  对于v操作而言,你释放了该信号量。
       		失败返回-1,errno被设置。

(5)实例代码(老师版)

#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdio.h>

#define SEM_PATH "/home/china"
#define PROJ_ID  888

union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };

int main()
{
	int sem_id,r;
	/*step1: 先申请一个system_v_ipc key*/
	key_t key = ftok(SEM_PATH, PROJ_ID);
	if(key == -1)
	{
		perror("ftok failed");
		return -1;
	}

	/*step2: 用semget 创建或打开一个system v sem*/
	sem_id = semget(key, 1 , IPC_CREAT|0664); //创建一个信号量集
	if(sem_id == -1)
	{
		perror("semget failed");
		return -1;
	}

	union semun arg;
	unsigned short val = 1;
	arg.array = &val;
	r = semctl(sem_id, 0,SETALL, arg);//初始化信号量集的值
	r = semctl(sem_id, 0,GETVAL, NULL);//获取信号量的值
	printf("the sem value is %d\n",r);

	/*step3: P/V操作*/

	// P 操作
	struct sembuf sops;
	sops.sem_num = 0;//对第0个信号量进行操作
	sops.sem_op = -1;//进行P操作
	sops.sem_flg = SEM_UNDO;//撤销
	r = semop(sem_id, &sops, 1);

	printf("hello, i am using it now!\n");
	r = semctl(sem_id, 0,GETVAL, NULL);//获取信号量的值
	printf("the sem value is %d\n",r);
	
	getchar();

	printf("haha,my work is over!\n");

	// V 操作
	sops.sem_num = 0;//对第0个信号量进行操作
	sops.sem_op = 1;//进行P操作
	sops.sem_flg = SEM_UNDO;//撤销
	r = semop(sem_id, &sops, 1);
	printf("i have release the semaphore,you can using it now!");
	r = semctl(sem_id, 0,GETVAL, NULL);//获取信号量的值
	printf("the sem value is %d\n",r);
}
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
#include <errno.h>
#include <stdio.h>

#define SEM_PATH "/home/china"
#define PROJ_ID  888

union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };

int main()
{
	int sem_id,r;
	/*step1: 先申请一个system_v_ipc key*/
	key_t key = ftok(SEM_PATH, PROJ_ID);
	if(key == -1)
	{
		perror("ftok failed");
		return -1;
	}

	/*step2: 用semget 创建或打开一个system v sem*/
	sem_id = semget(key, 0 , 0); //打开一个信号量集
	if(sem_id == -1)
	{
		perror("semget failed");
		return -1;
	}

	/*
	union semun arg;
	unsigned short val = 1;
	arg.array = &val;
	r = semctl(sem_id, 0,SETALL, arg);//初始化信号量集的值
	*/
	r = semctl(sem_id, 0,GETVAL, NULL);//获取信号量的值
	printf("the sem value is %d\n",r);

	/*step3: P/V操作*/

	printf("i am waiting the sem!\n");
	// P 操作
	struct sembuf sops;
	sops.sem_num = 0;//对第0个信号量进行操作
	sops.sem_op = -1;//进行P操作
	sops.sem_flg = SEM_UNDO;//撤销
	r = semop(sem_id, &sops, 1);

	printf("haha, i get the sem!\n");
	printf("thany you!\n");
	getchar();

	// V 操作
	sops.sem_num = 0;//对第0个信号量进行操作
	sops.sem_op = 1;//进行P操作
	sops.sem_flg = SEM_UNDO;//撤销
	r = semop(sem_id, &sops, 1);
	printf("beybey");

	/*step4: 销毁信号量*/
	r = semctl(sem_id, 0,IPC_RMID, NULL);//销毁信号量
}

自己版

#include<stdio.h>
#include<sys/types.h>
#include<sys/sem.h>
#include<sys/ipc.h>
#define SME_NAME "/home/china"
#define PROCID 0 
union semun {
               int              val;    /* Value for SETVAL */
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */
               unsigned short  *array;  /* Array for GETALL, SETALL */
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
};

int main()
{
	//1.映射KEY
	key_t key = ftok(SME_NAME,PROCID);
	if(key == -1)
	{
		perror("ftoc error");
		return -1;
	}
	//2.建立1个信号集
	int sem_id = semget(key,1,IPC_CREAT | 0664);
    
	
	//3.初始化信号集
	union semun sem;
	unsigned short buf[1]={1};
	sem.array= &buf[0];
	int ret = semctl(sem_id,0,SETALL,sem);
	ret = semctl(sem_id,0,GETVAL);
	printf("P操作之前信号量为signal = %d\n",ret);
	
	//4.p操作
    struct sembuf op[1];
    op[0].sem_num = 0;
	op[0].sem_op = -1;
    op[0].sem_flg = SEM_UNDO;	
	int r = semop(sem_id,op,1);
	if(r == -1)
	{
		perror("semop error");
		return -1;
	}
	//5.获取信号量的值
	printf("hello caojiayi!!!\n");	
	ret = semctl(sem_id,0,GETVAL);
	printf("P操作之后信号量为signal = %d\n",ret);
	getchar();
	//6.v操作
    op[0].sem_num = 0;
	op[0].sem_op = 1;
    op[0].sem_flg = SEM_UNDO;	
	r = semop(sem_id,op,1);
	if(r == -1)
	{
		perror("semop error");
		return -1;
	}
	ret = semctl(sem_id,0,GETVAL);
	printf("V操作之后信号量为signal = %d\n",ret);
}
#include<stdio.h>
#include<sys/types.h>
#include<sys/sem.h>
#include<sys/ipc.h>
#define SME_NAME "/home/china"
#define PROCID 0 

int main()
{
	//1.映射KEY
	key_t key = ftok(SME_NAME,PROCID);
	if(key == -1)
	{
		perror("ftoc error");
		return -1;
	}
	//2.打开1个信号集
	int sem_id = semget(key,0,0);
    
    int	ret = semctl(sem_id,0,GETVAL);
	printf("P操作之前信号量为signal = %d\n",ret);
	
	//4.p操作
    struct sembuf op[1];
    op[0].sem_num = 0;
	op[0].sem_op = -1;
    op[0].sem_flg = SEM_UNDO;	
	int r = semop(sem_id,op,1);
	if(r == -1)
	{
		perror("semop error");
		return -1;
	}
	//5.获取信号量的值
	printf("hello caojiayi!!!\n");	
	ret = semctl(sem_id,0,GETVAL);
	printf("P操作之后信号量为signal = %d\n",ret);
	getchar();
	//6.v操作
    op[0].sem_num = 0;
	op[0].sem_op = 1;
    op[0].sem_flg = SEM_UNDO;	
	r = semop(sem_id,op,1);
	if(r == -1)
	{
		perror("semop error");
		return -1;
	}
	ret = semctl(sem_id,0,GETVAL);
	printf("V操作之后信号量为signal = %d\n",ret);

	//7.销毁信号量集
	ret = semctl(sem_id,0,IPC_RMID);
	if(ret == -1)
	{
		perror("semctl error");
	}
}

五.IPC对象的持续性

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EA8gYz5b-1648221147423)(C:\Users\cjy\AppData\Roaming\Typora\typora-user-images\image-20210813124531873.png)]

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6eSCHIUU-1648221147429)(file:///C:\Users\cjy\Documents\Tencent Files\2869284943\Image\C2C\U11FRH}AV{U`HE~$J521@K.png)]

六.信号

1.什么是信号
信号是在软件层次上对中断机制的一种模拟,是一种异步通信方式。信号能够直接进行用户空间进程和内核进程之间的交互,内核进程也能利用它来通知用户空间进程发生了哪些系统事件。
2.信号的值
	man 7 signal

	   Signal     Value     Action   Comment
       ──────────────────────────────────────────────────────────────────────
       SIGHUP        1       Term    Hangup detected on controlling terminal
                                     or death of controlling process
       SIGINT        2       Term    Interrupt from keyboard
       SIGQUIT       3       Core    Quit from keyboard
       SIGILL        4       Core    Illegal Instruction
       SIGABRT       6       Core    Abort signal from abort(3)
       SIGFPE        8       Core    Floating-point exception
       SIGKILL       9       Term    Kill signal
       SIGSEGV      11       Core    Invalid memory reference
       SIGPIPE      13       Term    Broken pipe: write to pipe with no
                                     readers; see pipe(7)
       SIGALRM      14       Term    Timer signal from alarm(2)
       SIGTERM      15       Term    Termination signal
       SIGUSR1   30,10,16    Term    User-defined signal 1
       SIGUSR2   31,12,17    Term    User-defined signal 2
       SIGCHLD   20,17,18    Ign     Child stopped or terminated
       SIGCONT   19,18,25    Cont    Continue if stopped
       SIGSTOP   17,19,23    Stop    Stop process
       SIGTSTP   18,20,24    Stop    Stop typed at terminal
       SIGTTIN   21,21,26    Stop    Terminal input for background process
       SIGTTOU   22,22,27    Stop    Terminal output for background process
3.信号的原理图

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-r5s0mGrw-1648221147433)(C:\Users\cjy\AppData\Roaming\Typora\typora-user-images\image-20210816102444185.png)]

4.信号相关的API
1,发送一个信号 kill/raise/alarm

kill

NAME
       kill - send signal to a process

SYNOPSIS
       #include <sys/types.h>
       #include <signal.h>

    kill用来发送一个特定的信号给指定的进程
       int kill(pid_t pid, int sig);
       		@pid: 指定信号的接收者(可能是多个进程)
       			pid>0: 接受者的进程ID
       			pid==0:发送信号给调用进程同组的所有进程
       			pid==-1:发送信号给调用进程有权限发送的所有进程
       			pid<-1: 发送信号给组id等于pid的绝对值的所有进程
       		@sig: 要发送的信号值
       	返回值:
       		成功(至少有一个进程成功接收到信号)返回0
       		失败返回-1,errno被设置

示例

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


//练习: 创建一个子进程,子进程里面打印1到100,父进程里面打印100~200

int main()
{
	int wstatus;

	pid_t pid = fork();

	if(pid > 0)//表示父进程返回
	{
		getchar();
		kill(pid,SIGKILL);//给子进程发一个SIGKILL的信号
		wait(&wstatus);
		
	}
	else if(pid == 0)//表示子进程返回 
	{
		while(1)
		{
			printf("hello\n");
			sleep(1);
		}
	}

}

raise

NAME
       raise - send a signal to the caller

SYNOPSIS
       #include <signal.h>

    raise用来发送一个信号给自己
       int raise(int sig);
       <===> kill(getpid(),sig)

示例

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


//练习: 创建一个子进程,子进程里面打印1到100,父进程里面打印100~200

int main()
{
	int i=0;

	while(i<=10)
	{
		printf("hello 123\n");
		if(i == 5)
		{
			raise(SIGKILL);
		}
		sleep(1);
		i++;
	}
	
}

alarm

NAME
       alarm - set an alarm clock for delivery of a signal

SYNOPSIS
       #include <unistd.h>

    alarm用来给调用进程设置一个闹钟,闹钟在超市的时候,就会闹(发送一个SIGALRM信号
    给调用进程),一个进程在同一时刻只能有一个闹钟。
       unsigned int alarm(unsigned int seconds);
       		@seconds: 设置闹钟的超时时间,以秒为单位
       	返回值:
       		成功返回上一个闹钟的剩余时间(秒数)
    注意: alarm实际上是修改调用进程的闹钟剩余时间的。

示例

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


//练习: 创建一个子进程,子进程里面打印1到100,父进程里面打印100~200

int main()
{
	int i=0;
	alarm(10);
	alarm(5);
	while(i<=30)
	{
		printf("hello 123\n");
		sleep(1);
		i++;
	}
	
}
2.等待信号的到来
NAME
       pause - wait for signal

SYNOPSIS
       #include <unistd.h>

    pause 用来等待一个信号的到来,导致调用进程(或线程)休眠(阻塞在那里),直到收到一个信号
    (被干掉/执行自己的信号处理函数)int pause(void);

       返回值: 收到信号,并处理信号后返回-1,且errno被设置

实例

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


//练习: 创建一个子进程,子进程里面打印1到100,父进程里面打印100~200

int main()
{
	int wstatus;

	pid_t pid = fork();

	if(pid > 0)//表示父进程返回
	{
		getchar();
		kill(pid,SIGKILL);//给子进程发一个SIGKILL的信号
		wait(&wstatus);
		
	}
	else if(pid == 0)//表示子进程返回 
	{
		pause();
	}

}
ps -aux | grep ./a.out 看是否有两个进程存在
3.改变信号的处理方式
NAME
       signal - ANSI C signal handling

SYNOPSIS
       #include <signal.h>

    sighanlder_t是定义的一个新类型,该类型是一个函数指针类型。指向的函数返回一个void,
    带一个int类型的参数,该函数的类型,就是我们信号处理的函数类型。
    OS把实际收到的信号值作为信号处理函数的实际参数传递,而每个进程都有自己的信号处理方式表。

       typedef void (*sighandler_t)(int);

   	signal用来改变指定信号的处理方式。实际上就是把信号与信号处理函数相关联起来。
       sighandler_t signal(int signum, sighandler_t handler);
       		@signum: 要改变处理方式的信号的值
       		@handler: 指向信号处理函数的指针。有三种情况:
       			(1)SIG_IGN: ignore 忽略
       			(2)SIG_DFL: defult采用操作系统默认的处理方式
       			(3)你自己写的处理函数指针

       	返回值:
       		如果成功返回该信号的前任处理方式
       		如果失败返回 SIG_ERR
       	注意:
       		信号处理方式,同样可以被fork克隆。

实例

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


int terminate=0;//程序结束标志。1代表程序结束,0点程序未结束

/*信号处理函数*/
void sig_handler(int sig)
{
	switch(sig)
	{
		case SIGINT:
			terminate = 1;
			break;
		case SIGKILL:
			terminate = 1;
			break;
		default:
			break;
	}
}


int main()
{
	signal(SIGINT, sig_handler);
	signal(SIGKILL, sig_handler);
	while(!terminate)
	{
		printf("hello, are you ok?\n");
		sleep(1);
	}


	puts("over~\n");

}

4.实例

模拟计算

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


//练习: 创建一个子进程,子进程里面打印1到100,父进程里面打印100~200

int main()
{
	int wstatus;

	pid_t pid = fork();

	if(pid > 0)//表示父进程返回
	{
		while(1)
		{
			char c = getchar();
			if(c == '*')
			{
				kill(pid,16);//给子进程发一个SIGKILL的信号
			}
			else if(c == '/')
			{
				kill(pid,17);
			}
			else if(c == '+')
			{
				kill(pid,10);
			}
			else if(c == '-')
			{
				kill(pid,12);
			}
			else if(c == '%')
			{
				kill(pid,30);
			}
	    }
		wait(&wstatus);
		
	}
	else if(pid == 0)//表示子进程返回 
	{
		int r = execl("./modu1", "./modu1",NULL);
		if(r == -1)
		{
			perror("execl error");
			return -1;
		}
	}

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


int terminate=0;//程序结束标志。1代表程序结束,0点程序未结束

/*信号处理函数*/

int i=1;
int a = 0;
int b = 0;
int ret = 0;
int n;
void sig_handler(int sig)
{
	switch(sig)
	{
		case SIGINT:
			terminate = 1;
			break;
		case SIGKILL:
			terminate = 1;
			break;
		case 16: ret = a*b;printf("%d\n",ret);
			break;
		case 17: ret = a/b;printf("%d\n",ret);
			break;
		case 10: ret = a+b;printf("%d\n",ret);
			break;
		case 12: ret = a-b; printf("%d\n",ret);
			break;
		case 30: ret = a%b;printf("%d\n",ret);
			break;
		default:
			break;
	}
}

int main(int argc, char *argv[])
{
	signal(SIGINT, sig_handler);
	signal(SIGKILL, sig_handler);
	signal(16, sig_handler);
	signal(17, sig_handler);
	signal(10, sig_handler);
	signal(12, sig_handler);
	signal(30, sig_handler);
	
	while(!terminate);
	puts("over~\n");

}

线程

1.问题的引入

一、问题的引入
	前面讲到,为了并发执行程序(任务),现代操作系统特地引入"进程"的概念。
	分析:
		1,进程地址空间是独立的,创建一个进程的系统开销比较大,要拷贝整个地址空间,
		  进程切换开销比较大。
		2,进程间的数据是独立的,分开的。如果进程间需要进行数据交换,则需要用到进程间
		  通信(IPC),进程间通信开销比较大。
		  ...
		于是,就有人提出能不能在同一个进程内实现"任务(代码块)"的并发执行?

		=====>  线程。

2.线程的概念

1. 线程是比进程更小的活动单位,它是进程中的一个执行路径(执行分支)。
	2. 线程同进程内其他线程共享进程的地址空间。

	==> 线程的特点:
	(1) 创建一个线程比创建一个进程开销要小的多(why?)
		因为,在进程内部创建一个线程,不需要拷贝进程的地址空间,因为线程和其他的线程
		共享进程的地址空间。
	(2) 实现线程间通信十分方便,因为一个进程创建的多个线程直接共享整个进程的内存区域。
	(3) 线程也是一个动态的概念。
	(4) 进程是操作系统资源分配的最小单位,线程是调度的最小单位。
	(5) 在进程内创建多线程,可以提高系统的并行处理能力,加快进程的处理速度。
	(6) 每个进程会自动有一个主线程,就是main函数,这个主线程如果执行完了,那么进程就结束了。

3.线程相关的API

1.概述
POSIX(可移植操作系统接口)里面实现了线程接口,所以我们称之为
		POSIX thread===> pthread

	在linux中查看pthread的接口函数:
		sudo apt-get install manpages-posix-dev

	线程的操作:
	(1)创建一个线程
	(2)线程的退出
	(3)线程资源的回收
	(4)线程间同步机制
2.创建一个线程
SYNOPSIS
       #include <pthread.h>

    pthread_create用来创建一个线程。线程用来并发执行一个任务,"任务"就是代码。
    在C语言中,代码块是以函数形式组织。
    在pthread中,每个线程都有一个唯一的ID,用类型pthread_t来表示。
    而且我们可以通过函数: pthread_t pthread_self(void); 来获取自身线程的ID
    而且每个线程都有自己的属性,用类型 pthread_attr_t来表示。

       int pthread_create(pthread_t *thread, const pthread_attr_t *attr,
                          void *(*start_routine) (void *), void *arg);
                @thread:指向pthread_t类型的变量,用来保存新创建的线程的ID。
                @attr : 指向线程属性结构体,一般填为NULL,表示采用默认属性。
                @start_routine : 这是一个函数指针。该指向的函数的参数类型为void*,
                	返回值类型为void*。start_routine就是线程函数,即新创建的线程
                	要执行的任务。
                @arg: 作为线程函数的实际参数传给线程函数。
        返回值:
        	成功返回0
        	失败返回错误编码,errno被设置

       Compile and link with -pthread.

示列

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

char str1[1024]={"hello"};
int a=0,b=0;
void* mywork(void* value)
{
	char* str=value;
	puts(str);

	while(1)
	{
		sleep(1);
		for(int i=a;i<=b;i++)
		{
			printf("%d ",i);
		}
		printf("\n");
	}
	return NULL;
}
int main()
{
	//1.创建一个线程
	pthread_t pid;
	char str[] = "I strat!!!\n";
	if(pthread_create(&pid,NULL,mywork,str))
	{
		perror("pthread_create  error!!!");
	}

	while(1)
	{
		scanf("%d%d",&a,&b);
	}

	return 0;
}

3.线程的退出
有三种方式可以使线程结束:
  		(1)线程函数返回
  		(2)在线程函数中调用 pthread_exit()
  		(3)it is cancelled(被别的线程调用 pthread_cancel把你给"取消"了)
pthread_exit
NAME
       pthread_exit - terminate calling thread

SYNOPSIS
       #include <pthread.h>

    pthread_exit用来结束调用者自身这个线程
       void pthread_exit(void *retval);
       		@retval :指针,用来指向返回值。等价于return后面的值。该值
       			可以被pthread_join()函数接受

       Compile and link with -pthread.
pthread_cancel
NAME
       pthread_cancel - send a cancellation request to a thread

SYNOPSIS
       #include <pthread.h>

    pthread_cancel用来发送一个"取消请求"给thread指定的线程,
    但是接收到"取消"请求的线程,不一定会死,这得取决于接收线程的属性(cancel state)。

    我们可以调用pthead_setcancelstate来enable或disable线程这个属性
       int pthread_cancel(pthread_t thread);
       		@thread: 指定要取消的线程ID
       返回值:
       		成功返回0
       		失败返回错误编码

       Compile and link with -pthread.
pthread_setcancelstate
NAME
       pthread_setcancelstate, pthread_setcanceltype - set cancelability state
       and type

SYNOPSIS
       #include <pthread.h>

    pthead_setcancelstate用来enable或disable线程这个cancel state属性
       int pthread_setcancelstate(int state, int *oldstate);
       		@state: 要设置的取消状态
       				PTHREAD_CANCEL_ENABLE 可被别人取消
       				PTHREAD_CANCEL_DISABLE 不可被别人取消
       		@oldstate : 用来保存上次的状态值
       	返回值:
       		成功返回0
       		失败返回错误编码
实例
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>

char str1[1024]={"hello"};
void* mywork(void* value)
{
	char* str=value;
	puts(str);

	while(1)
	{
		int old;
		pthread_setcancelstate(PTHREAD_CANCEL_DISABLE,&old);
		printf("state=%d\n",old);
		sleep(1);
		puts(str1);
		if(strcmp(str1,"exit")==0)
			pthread_exit(NULL);
	}
	return NULL;
}
int main()
{
	//1.创建一个线程
	pthread_t pid;
	char str[] = "I strat!!!\n";
	if(pthread_create(&pid,NULL,mywork,str))
	{
		perror("pthread_create  error!!!");
	}

	while(1)
	{
		gets(str1);
		if(strcmp(str1,"mainexit")==0)
		{
			pthread_cancel(pid);
		}
	}

	return 0;
}
4.线程的回收
pthread_join
PTHREAD_JOIN(3)            Linux Programmer's Manual           PTHREAD_JOIN(3)

NAME
       pthread_join - join with a terminated thread

SYNOPSIS
       #include <pthread.h>

    pthread_join用来等待一个指定的线程退出,该函数会阻塞调用进程,直到被等待的线程退出,
    它有两个作用:
    	(1)阻塞至被等待线程退出
    	(2)回收被等待线程的资源

    线程退出了,不代表其资源完全释放了,这个时候就需要调用pthread_join去回收资源。
    线程退出了是否代表其资源可以完全释放,这个取决一个线程属性:
    	detach state (分离属性)

       int pthread_join(pthread_t thread, void **retval);
       		@thread: 用来指定等待哪个线程退出
       		@retval: 是一个二级指针,指向的类型是void*,用来保存线程函数的返回值
       返回值:
       		成功返回0
       		失败返回错误编码
       Compile and link with -pthread.
pthread_detach
NAME
       pthread_detach - detach a thread

SYNOPSIS
       #include <pthread.h>

    pthread_detach用来把指定的线程设置为detach state属性,当该线程退出时,其
    资源会被完全回收。
       int pthread_detach(pthread_t thread);

       Compile and link with -pthread.

    注意: 试图去分离一个已经处于分离状态的线程将会导致不确定的情况,应该避免此操作。
       我们一般的做法是在线程函数中调用该函数。
       ==>  void *fun(void *arg)
       		{
       			pthread_detach(pthread_self());
       			//...

       		}
实例
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
#include<string.h>

char str1[1024]={"hello"};
int i = 0;
void* mywork(void* value)
{
	char* str=value;
	puts(str);
    pthread_detach(pthread_self());
	while(1)
	{
		sleep(1);
		puts(str1);
		if(i == 10)
			pthread_exit(NULL);
		i++;
	}
	return NULL;
}
int main()
{
	//1.创建一个线程
	pthread_t pid;
	char str[] = "I strat!!!\n";
	if(pthread_create(&pid,NULL,mywork,str))
	{
		perror("pthread_create  error!!!");
	}

		gets(str1);
		pthread_join(pid,NULL);

	return 0;
}

4.线程互斥锁

1.线程同步机制
4.线程间同步机制
	(1)互斥
			多个线程同时访问一个共享资源时,我们应该要"避免竞争"
		a. 信号量机制
			SYSTEM V 信号量
			POSIX 信号(有名/无名)
		b. 线程互斥锁(pthread mutex)
	
	(2)同步
		==> 条件变量
2.线程互斥锁的概念
线程互斥锁的概念
	线程互斥锁(pthread mutex)专门用来实现在线程间互斥用的。
	它的原理和功能,作用等都和信号量一样。

	pthread_mutex_init  创建一个互斥锁
	pthread_mutex_destroy 销毁一个互斥锁
	pthread_mutex_lock/trylock  P操作
	pthread_mutex_unlock  V操作
3.线程互斥锁相关的API
pthread_mutex_init
NAME
       pthread_mutex_init — destroy and initialize a mutex

SYNOPSIS
       #include <pthread.h>

    pthread_mutex_init 用来初始化一个线程互斥锁,在Linux中用pthread_mutex_t来
    表示一个线程互斥锁
       int pthread_mutex_init(pthread_mutex_t *restrict mutex,
           const pthread_mutexattr_t *restrict attr);
            @mutex : 执行要初始化的线程互斥锁。
                    restrict限定符,表示该指针指向的空间,只能用该指针去修改。
            @attr : 指向线程互斥锁的属性。一般填为NULL,表示采用默认属性。
        返回值:
        	成功返回0
        	失败返回错误码
pthread_mutex_destroy
NAME
       pthread_mutex_destroy, pthread_mutex_init — destroy  and  initialize  a
       mutex

SYNOPSIS
       #include <pthread.h>

    pthread_mutex_destroy用来销毁 mutex指定的线程互斥锁
       int pthread_mutex_destroy(pthread_mutex_t *mutex);
       		@mutex : 指向要销毁的线程互斥锁
       返回值:
        	成功返回0
        	失败返回错误码

P V 操作 ==> lock/trylock unlock

pthread_mutex_lock
NAME
       pthread_mutex_lock, pthread_mutex_trylock, pthread_mutex_unlock —  lock
       and unlock a mutex

SYNOPSIS
       #include <pthread.h>

    pthread_mutex_lock用来进行P操作
       int pthread_mutex_lock(pthread_mutex_t *mutex);
       		@mutex: 获取哪一个互斥锁
       	返回值:
       		返回0表示,获取到了互斥锁
       		返回非0,表示错误码
pthread_mutex_trylock
pthread_mutex_trylock是非阻塞版本的P操作
       int pthread_mutex_trylock(pthread_mutex_t *mutex);
       		@mutex: 获取哪一个互斥锁
       	返回值:
       		返回0表示,获取到了互斥锁
       		返回非0,表示没获取到或者出错
pthread_mutex_unlock
pthread_mutex_unlock用来进程 V 操作
       int pthread_mutex_unlock(pthread_mutex_t *mutex);

5.条件变量笔记

1.作用
条件变量是用来实现线程间同步的。
同步: 旨在阻塞某个线程,直到另外一个线程达到某种条件之后再唤醒它。
		==> 等

	pthread_cond_init 创建条件变量
	pthread_cond_destroy 销毁条件变量
	pthread_cond_wait/timewait 阻塞
	pthread_cond_signal/broadcast 唤醒
2.问题的引入
问题的引入: 写出下列题目的伪代码
	题目:  现有一进程内 共享一个int a =0 ;
	实现:  线程A,循环把a++,延时1s
	       线程B,当a<=10阻塞,直到a>10,这是打印 game over~

老方法

 线程A :  while(1)
	         {
	         	a++;
	         	sleep(1);
	         }
	 线程B :
	 	     while(a<=10);
	 	     printf("game over~\n");

新方法

用条件变量:
	 线程A:
	 	     while(1)
	 	     {
	 	     	a++;
	 	     	if(a>10)
	 	     	{
	 	     		signal();//唤醒
	 	     	}
	 	     }
	 线程B:
	 		if(a<=10)
	 		{
	 			wait();//休眠
	 		}
	 		printf("game over~\n")

区别

区别:
	条件变量在wait时会让出CPU控制权,而while循环一直在访问变量a
3.条件变量相关的API
初始化/销毁一个条件变量
pthread_cond_init
NAME  pthread_cond_destroy, pthread_cond_init —destroy and initialize condition variables
SYNOPSIS
       #include <pthread.h>

    pthread_cond_init用来初始化cond指向的条件变量
       int pthread_cond_init(pthread_cond_t *restrict cond,
           const pthread_condattr_t *restrict attr);
           	@cond : 指向要初始化的条件变量
           	@attr: 指向条件变量的属性。一般填为NULL,表示采用默认属性
        返回值:
        	成功返回0
        	失败返回错误编码
pthread_cond_destroy
pthread_cond_destroy用来销毁cond指向的条件变量
       int pthread_cond_destroy(pthread_cond_t *cond);
      		@cond : 指向要销毁的条件变量
      	返回值:
        	成功返回0
        	失败返回错误编码
等待一个条件变量
pthread_cond_wait
	NAME
       pthread_cond_timedwait, pthread_cond_wait — wait on a condition

SYNOPSIS
       #include <pthread.h>

    条件变量本身就是一个共享对象(多个线程都可以访问它),所以它本身也需要保护。
    	==> MC(Mutex Condition)

    pthread_cond_wait:死等,直到被被人唤醒
       int pthread_cond_wait(pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex);
           @cond : 指向要等的那个条件变量
           @mutex: 该条件变量绑定的互斥锁,因为条件变量本身是一个共享资源,需要互斥
       返回值:
       		返回0,条件产生,被唤醒了
       		失败返回错误码
pthread_cond_timedwait
pthread_cond_timedwait: 有限的等待
       int pthread_cond_timedwait(pthread_cond_t *restrict cond,
           pthread_mutex_t *restrict mutex,
           const struct timespec *restrict abstime);
           @cond : 指向要等的那个条件变量
           @mutex: 该条件变量绑定的互斥锁,因为条件变量本身是一个共享资源,需要互斥
           @abstime: 截止时间,abs的意思是绝对时间
           		struct timespec
           		{
           			time_t tv_sec;//秒
           			long tv_nsec;//纳秒
           		};
           		eg:
           			struct timespec ts;
           			clock_gettime(CLOCK_REALTIME,&ts);//获取当前系统时间
           			ts.tv_sec += 10;
           			ts.tv_nsec += 10000;
唤醒一个条件变量
pthread_cond_broadcast
NAME
       pthread_cond_broadcast, pthread_cond_signal —  broadcast  or  signal  a
       condition

SYNOPSIS
       #include <pthread.h>

    pthread_cond_broadcast用来唤醒在cond指定的条件变量上等待的所有线程
       int pthread_cond_broadcast(pthread_cond_t *cond);
       		@cond : 指定要唤醒的条件变量
       	返回值:
       		成功返回0
       		失败返回错误码	
pthread_cond_signal
pthread_cond_signal用来至少唤醒一个在cond指定的条件变量上等待的线程   		
       int pthread_cond_signal(pthread_cond_t *cond);
4.例程
例程1
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>


int a=0;
pthread_mutex_t mutex;
pthread_cond_t cond;


void *fun1(void *arg)
{
	pthread_detach(pthread_self());//设置线程为分离属性
	
	while(1)
	{
		a++;
		if(a > 10)
		{
			pthread_mutex_lock(&mutex);//P操作
			
			pthread_cond_broadcast(&cond);
			
			pthread_mutex_unlock(&mutex);//V操作
		}
		sleep(1);
		printf("a:%d\n",a);
	}
	
}
void *fun2(void *arg)
{
	pthread_detach(pthread_self());//设置线程为分离属性
	if(a<=10)
	{
		pthread_mutex_lock(&mutex);//P操作

		printf("i will sleep!\n");
		pthread_cond_wait(&cond, &mutex);
		
		pthread_mutex_unlock(&mutex);//V操作

		printf("i am signaled by other thread!\n");
		
	}
}




int main()
{
	int n=5;
	srand(time(NULL));
	pthread_t tid1,tid2,tid3;

	if(pthread_mutex_init(&mutex,NULL))
	{
		perror("pthread_mutex_init failed");
		return -1;
	}

	if(pthread_cond_init(&cond,NULL))
	{
		perror("pthread_cond_init failed");
		return -1;
	}
           
	if(pthread_create(&tid1, NULL, fun1, &n))
	{
		perror("pthread_create error");
		return -1;
	}
	if(pthread_create(&tid2, NULL, fun2, &n))
	{
		perror("pthread_create error");
		return -1;
	}
	
	
	
	while(1);
	pthread_mutex_destroy(&mutex);
	pthread_cond_destroy(&cond);
}
例程2
/*
	练习:
		有一个全局变量 int a=100,一个线程A循环把a加10并打印a的值,间隔为1s
		一个线程循环把a减10并打印a的值,间隔为0.5秒。
		要求a的值的变化不能超过20。
*/
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>


int a=100;
pthread_mutex_t mutex1;
pthread_mutex_t mutex2;

pthread_cond_t cond1;
pthread_cond_t cond2;

void *fun1(void *arg)
{
	pthread_detach(pthread_self());//设置线程为分离属性
	while(1)
	{
		pthread_mutex_lock(&mutex2);//P操作
		pthread_cond_wait(&cond2, &mutex2);
		pthread_mutex_unlock(&mutex2);//V操作
		
		a += 10;
		sleep(1);
		
		pthread_mutex_lock(&mutex1);//P操作
		pthread_cond_broadcast(&cond1);
		pthread_mutex_unlock(&mutex1);//V操作
		
		
		printf("fun1 a:%d\n",a);
	}
	
}
void *fun2(void *arg)
{
	pthread_detach(pthread_self());//设置线程为分离属性
	
	pthread_mutex_lock(&mutex2);//P操作
	pthread_cond_broadcast(&cond2);
	pthread_mutex_unlock(&mutex2);//V操作
	
	while(1)
	{
		
		pthread_mutex_lock(&mutex1);//P操作
		pthread_cond_wait(&cond1, &mutex1);
		pthread_mutex_unlock(&mutex1);//V操作
		
		usleep(500000);
		
		pthread_mutex_lock(&mutex2);//P操作
		pthread_cond_broadcast(&cond2);
		pthread_mutex_unlock(&mutex2);//V操作
		
		a -= 10;
		
		printf("fun2 a:%d\n",a);	
	}
}




int main()
{
	int n=5;
	srand(time(NULL));
	pthread_t tid1,tid2,tid3;

	if(pthread_mutex_init(&mutex1,NULL))
	{
		perror("pthread_mutex_init failed");
		return -1;
	}

	if(pthread_mutex_init(&mutex2,NULL))
	{
		perror("pthread_mutex_init failed");
		return -1;
	}

	if(pthread_cond_init(&cond1,NULL))
	{
		perror("pthread_cond_init failed");
		return -1;
	}

	if(pthread_cond_init(&cond2,NULL))
	{
		perror("pthread_cond_init failed");
		return -1;
	}
           
	if(pthread_create(&tid1, NULL, fun1, &n))
	{
		perror("pthread_create error");
		return -1;
	}
	
	sleep(1);
	
	if(pthread_create(&tid2, NULL, fun2, &n))
	{
		perror("pthread_create error");
		return -1;
	}
	
	
	
	while(1);
	pthread_mutex_destroy(&mutex1);
	pthread_mutex_destroy(&mutex2);
	pthread_cond_destroy(&cond1);
	pthread_cond_destroy(&cond2);
}
例程3
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

struct cpfile_t
{
	char file1[256];
	char file2[256];
};

int filecount1=0;
int filecount2=0;

void *copy_file(void *arg)
{
	pthread_detach(pthread_self());//设置线程为分离属性
	struct cpfile_t *p = arg;
	int fd1 = open(p->file1,O_RDONLY);
	if(fd1 == -1)
	{	
		puts(p->file1);
		perror("open file failed");
		return NULL;
	}
	int fd2 = open(p->file2, O_CREAT | O_WRONLY, 0664);
	if(fd2 == -1)
	{
		puts(p->file2);
		perror("create file failed");
		return NULL;
	}

	unsigned char buf[256];
	while(1)
	{
		int r = read(fd1,buf,256);
		if(r == 0)
			break;
		write(fd2,buf,r);
	}
	close(fd1);
	close(fd2);
	
	filecount2++;
	free(p);
}


/*
	copy_dir:把path1指定的目录进行拷贝,生成path2
*/
void copy_dir(const char *path1, const char *path2)
{
	pthread_t tid;
	printf("copy file %s ==> %s\n",path1,path2);
	int r = mkdir(path2, 0777);//目录要有可执行的权限,才能打开
	if(r == -1)
	{
		puts(path2);
		perror("mkdir path2 failed");
		return;
	}
	
	/*step1: 打开一个目录*/
	struct stat statbuf;
	char newpath1[1024]={0};
	char newpath2[1024]={0};
	
	DIR *dirp = opendir(path1);
	if(dirp == NULL)
	{
		perror("opendir  failed");
		return ;
	}

	/*step2: 读取目录中的文件*/
	while(1)
	{
		struct dirent *dt = readdir(dirp);//读取其中的一个目录项,数组下标加1
		if(dt == NULL)//读完了
			break;
		sprintf(newpath1,"%s/%s",path1,dt->d_name);//把目录和文件名合成一个路径
		sprintf(newpath2,"%s/%s",path2,dt->d_name);//把目录和文件名合成一个路径
		
		int r = stat(newpath1 ,&statbuf);//读取文件属性
		if(r == -1)
		{
			perror("stat failed!");
			return ;
		}

		if(S_ISDIR(statbuf.st_mode))//如果是目录
		{
			if(strcmp(dt->d_name,".")==0 || strcmp(dt->d_name,"..")==0)//排除. .. 目录
			{
				continue;
			}
			copy_dir(newpath1,newpath2);//递归访问
		}
		else //非目录文件,打印路径名
		{
			struct cpfile_t *pnew = malloc(sizeof(*pnew));
			strcpy(pnew->file1, newpath1);
			strcpy(pnew->file2, newpath2);
			if(pthread_create(&tid, NULL, copy_file, (void*)pnew))
			{
				perror("pthread_create error");
				return ;
			}
			filecount1++;
		}
	}

	/*step3: 关闭目录*/
	closedir(dirp);
}



int main()
{
	copy_dir("Music", "My_music");
	while(filecount1 != filecount2);
}

线程池

一.生产者消费者模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-tkDE6TAq-1648221147443)(C:\Users\cjy\AppData\Roaming\Typora\typora-user-images\image-20210818111011127.png)]

二.线程池模型

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Xk5WToCn-1648221147445)(C:\Users\cjy\AppData\Roaming\Typora\typora-user-images\image-20210818181358662.png)]

三.线程池的实现

1.thread_pool.h
#ifndef __THREAD_POOL_H__
#define __THREAD_POOL_H__
#include <pthread.h>
#include <stdbool.h>
/*
线程池包括两部分,1.任务线程 ---- 生产者 用链表保存
			    2.活动线程 ----- 消费者  用数组保存线程ID 即创建多少个线程
*/
typedef struct node{
	void *(*fun)(void *);//函数指针变量
	void *arg;           //参数指针变量
	struct node *next;
}Node_thr;

typedef struct{
	Node_thr *first;
	Node_thr *last;
	int NodeNum;		//结点数量
	int thr_Num;		//活动线程数量
	int shutdown;		//是否关闭线程池标志
	pthread_t *tid;     //保存活动线程ID的数组的首地址
	pthread_mutex_t *mutex;//与条件变量绑定的互斥锁
	pthread_cond_t  *cond;//解决互斥问题的条件变量
	pthread_mutex_t *mutex_head;//保护头结点的互斥锁
}Head_thr; //头结点保存的是线程池的属性

/**
 * @brief 初始化线程池,即初始化线程池头结点
 * 
 * @param thr_Num 活动线程的数量,即需要创建几个线程
 * @return Head_thr* 返回头节点的指针
 */
extern Head_thr *thread_pool_init(int thr_Num);						//初始化线程池
extern int finish_thread_pool(Head_thr *thread_pool);				//关闭线程池(等每个线程把任务做完后,再销毁线程池)
extern int thread_pool_destroy(Head_thr *thread_pool);				//销毁线程池函数

/**
 * @brief 添加任务线程
 * 
 * @param thread_pool 任务线程
 * @param fun 需要执行任务的函数,这个函数指针会作为参数传递给活动线程
 * @param arg 任务函数的参数
 * @return int 
 */
extern int add_task(Head_thr *thread_pool,void *(*fun)(void *),void *arg);	//添加任务
extern int start_thread_pool(Head_thr *thread_pool);						//创建活动线程
extern void *active_thread(void *arg);										//活动线程对应的线程函数


extern bool is_exit_thread_pool(Head_thr *thread_pool);
extern bool is_empty_thread_pool(Head_thr *thread_pool);

#endif
2.thread_pool.c
#include "thread_pool.h"
#include <stdlib.h>
#include <stdio.h>

bool is_exit_thread_pool(Head_thr *thread_pool)
{
    if(thread_pool == NULL)
        return false;
    return true;
}

bool is_empty_thread_pool(Head_thr *thread_pool)
{
    if(thread_pool->NodeNum == 0)
        return true;
    return false;
}

//初始化线程池(只创建线程池,不创建活动线程和任务线程)
Head_thr *thread_pool_init(int thr_Num)
{
    //1.给头结点分配空间,创建线程池
    Head_thr * pool = (Head_thr *)malloc(sizeof(*pool));

    //2.给线程池的属性赋初始值
    //任务线程
    pool->first = NULL;
    pool->last = NULL;
    pool->NodeNum = 0;

    //活动线程,即线程池需要创建的线程个数
    pool->thr_Num = thr_Num;
    pool->tid = malloc(sizeof(pthread_t)*thr_Num);

    //同步问题,当任务线程为空,活动线程需要任务执行时会等待,这里需要同步
    //创建同步变量
    pool->cond = malloc(sizeof(pthread_cond_t));
    if(pthread_cond_init(pool->cond,NULL))
    {
        perror("pthread_cond_init(pool->cond,NULL) error");
        return NULL;
    }      
    //创建同步变量需要的互斥锁,因为同步变量也需要保护
    pool->mutex = malloc(sizeof(pthread_mutex_t));
    if(pthread_mutex_init(pool->mutex,NULL))
    {
        perror("pthread_mutex_init(pool->mutex,NULL) error");
        return NULL;
    }
        
    //互斥问题,头节点的属性只允许一个任务操作,为了避免同时加或同时减而导致与实际情况不符
    pool->mutex_head = malloc(sizeof(pthread_mutex_t));
    if(pthread_mutex_init(pool->mutex_head,NULL))
    {
         perror("pthread_mutex_init(pool->mutex_head,NULL) error");
         return NULL;
    }

    //判断是否可以销毁的标志,如果链表为空且活动进程都执行完毕则退出,shutown = 0 为活动状态
    pool->shutdown = 0;

    return pool;
}


int add_task(Head_thr *thread_pool,void *(*fun)(void *),void *arg)//添加任务
{
    //1.判断是否有线程池的存在
    if(!is_exit_thread_pool(thread_pool))
        return -1;

    //2.开始创建任务
    //创建一个任务结点
    Node_thr *pnew = malloc(sizeof(*pnew));
    pnew->fun=fun;
    pnew->arg=arg;
    pnew->next=NULL;

    //插入到任务队列中,需要对头结点进行写保护
    pthread_mutex_lock(thread_pool->mutex_head);
    //从无到有
    if(is_empty_thread_pool(thread_pool))
    {
        thread_pool->first = pnew;
        thread_pool->last = pnew;
    }
    else
    {
       thread_pool->last->next = pnew;
       thread_pool->last = pnew;
    }
    //任务队列数值加1
    thread_pool->NodeNum++;

    pthread_mutex_unlock(thread_pool->mutex_head);

    //唤醒线程
    pthread_mutex_lock(thread_pool->mutex);
    pthread_cond_signal(thread_pool->cond);
    pthread_mutex_unlock(thread_pool->mutex);
    return 0;
}

//创建活动线程
int start_thread_pool(Head_thr *thread_pool)
{
    for(int i = 0;i < thread_pool->thr_Num;i++)
    {
        if(pthread_create(&thread_pool->tid[i],NULL,active_thread,thread_pool))
        {
            perror("pthread_create(&thread_pool->tid[i],NULL,active_thread,thread_pool) error");
            return -1;
        }
    }
    return 0;
}

//活动线程对应的函数
void *active_thread(void *arg)
{
    Head_thr * thread_pool = arg;
    Node_thr *p = NULL;
    //pthread_detach(pthread_self());//设置分离属性,自动回收资源
    while(thread_pool->shutdown == 0)
    {
        if(is_empty_thread_pool(thread_pool) && thread_pool->shutdown == 0)
        {
            printf("%lu***********\n",pthread_self());
            pthread_mutex_lock(thread_pool->mutex);
            pthread_cond_wait(thread_pool->cond,thread_pool->mutex);
            pthread_mutex_unlock(thread_pool->mutex);
            printf("%lu#############\n",pthread_self());
        }

        printf("pthread = %lu,shutdown = %d\n",pthread_self(),thread_pool->shutdown);
        if(thread_pool->shutdown == 1)
            break;

        //将函数头结点取出来
        p = thread_pool->first;
        pthread_mutex_lock(thread_pool->mutex_head);//先把任务取出来,再执行任务
        if(thread_pool->NodeNum == 1)//表示这是最后一个结点
        {
            thread_pool->first = NULL;
            thread_pool->last = NULL;
        }
        else
        {
            thread_pool->first = thread_pool->first->next;
        }
        thread_pool->NodeNum--;
        pthread_mutex_unlock(thread_pool->mutex_head);

        p->next = NULL;//断结点
        p->fun(p->arg);//执行任务
        free(p);//消毁结点

        if(thread_pool->shutdown == 1)
        {
            printf("pthread = %lu,shutdown = %d\n",pthread_self(),thread_pool->shutdown);
        }
    }
    printf("%lu exit\n",pthread_self());
    return NULL;
}

//关闭线程池(等每个线程把任务做完后,再销毁线程池)
int finish_thread_pool(Head_thr *thread_pool)
{
    //如果线程池不存在,则返回-1
    if(!is_exit_thread_pool(thread_pool))
        return -1;

    while(!is_empty_thread_pool(thread_pool));
    thread_pool->shutdown = 1;

    pthread_mutex_lock(thread_pool->mutex);
    pthread_cond_broadcast(thread_pool->cond);
    pthread_mutex_unlock(thread_pool->mutex);
    //等待资源退出
    for(int i = 0;i < thread_pool->thr_Num;i++)
    {
         printf("ptid = %ld\n",thread_pool->tid[i]); 
         pthread_join(thread_pool->tid[i],NULL);
         printf("tid = %ld\n",thread_pool->tid[i]);     
    }
       
    thread_pool_destroy(thread_pool);
}

//销毁线程池函数
int thread_pool_destroy(Head_thr *thread_pool)
{
    //释放tid
    free(thread_pool->tid);
    //释放同步量和互斥锁
    pthread_cond_destroy(thread_pool->cond);
    pthread_mutex_destroy(thread_pool->mutex);
    pthread_mutex_destroy(thread_pool->mutex_head);
    free(thread_pool->cond);
    free(thread_pool->mutex);
    free(thread_pool->mutex_head);

    //释放头结点
    free(thread_pool);
}			
3.递归复制目录调用线程池实现
#include <stdio.h>
#include <pthread.h>
#include <errno.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <dirent.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include "thread_pool.h"

struct cpfile_t
{
	char file1[256];
	char file2[256];
};

void *copy_file(void *arg)
{
	struct cpfile_t *p = arg;
    //pthread_detach(pthread_self());
	int fd1 = open(p->file1,O_RDONLY);
	if(fd1 == -1)
	{	
		puts(p->file1);
		perror("open file failed");
		return NULL;
	}
	int fd2 = open(p->file2, O_CREAT | O_WRONLY, 0664);
	if(fd2 == -1)
	{
		puts(p->file2);
		perror("create file failed");
		return NULL;
	}

	unsigned char buf[256];
	while(1)
	{
		int r = read(fd1,buf,256);
		if(r == 0)
			break;
		write(fd2,buf,r);
	}
	close(fd1);
	close(fd2);
	
	free(p);
}


/*
	copy_dir:把path1指定的目录进行拷贝,生成path2
*/
void copy_dir(const char *path1, const char *path2,Head_thr* pool)
{
	pthread_t tid;
	printf("copy file %s ==> %s\n",path1,path2);
	int r = mkdir(path2, 0777);//目录要有可执行的权限,才能打开
	if(r == -1)
	{
		puts(path2);
		perror("mkdir path2 failed");
		return;
	}
	
	/*step1: 打开一个目录*/
	struct stat statbuf;
	char newpath1[1024]={0};
	char newpath2[1024]={0};
	
	DIR *dirp = opendir(path1);
	if(dirp == NULL)
	{
		perror("opendir  failed");
		return ;
	}

	/*step2: 读取目录中的文件*/
	while(1)
	{
		struct dirent *dt = readdir(dirp);//读取其中的一个目录项,数组下标加1
		if(dt == NULL)//读完了
			break;
		sprintf(newpath1,"%s/%s",path1,dt->d_name);//把目录和文件名合成一个路径
		sprintf(newpath2,"%s/%s",path2,dt->d_name);//把目录和文件名合成一个路径
		
		int r = stat(newpath1 ,&statbuf);//读取文件属性
		if(r == -1)
		{
			perror("stat failed!");
			return ;
		}

		if(S_ISDIR(statbuf.st_mode))//如果是目录
		{
			if(strcmp(dt->d_name,".")==0 || strcmp(dt->d_name,"..")==0)//排除. .. 目录
			{
				continue;
			}
			copy_dir(newpath1,newpath2,pool);//递归访问
		}
		else //非目录文件,打印路径名
		{
			struct cpfile_t *pnew = malloc(sizeof(*pnew));
			strcpy(pnew->file1, newpath1);
			strcpy(pnew->file2, newpath2);
			add_task(pool,copy_file,pnew);
		}
	}

	/*step3: 关闭目录*/
	closedir(dirp);
}


int main()
{
	Head_thr * pool = thread_pool_init(3);
    start_thread_pool(pool);
    copy_dir("test", "/home/china/new",pool);
    finish_thread_pool(pool);
}


网络编程

一.相关概念

1.OSI七层模型和TCP/IP协议四层模型

层次(level): 模块 应用层: 负责应用协议

	传输层: 规定数据在网络中是怎样传输的
		TCP: Transport Control protocol传输控制协议,是一种面向连接的传输层协议。
			在通信之前要先建立连接==>"三次握手"
			它能提供高可靠性通信(即数据无误,数据无丢失,数据无失序,数据无重复)

		UDP: User Datagram Protocol 用户数据协议
			不需要连接,也没有握手
			是不可靠的无连接的协议。在数据发送前,因为不需要进行连接,所以可以进行
			高效的数据传输。
			"实时应用"

	网络层: IP协议,给每一台联网的主机分配一个逻辑上的地址(编号)。

	网络接口层: 网卡以及网卡驱动,MAC地址(网卡物理地址),数据传输信道。

2.封装和拆包。

​ 网络数据在两台主机之间到底是怎样传输的呢?

3.因特网(互联网)地址(IP地址)

​ 互联网上的每个接口(网卡)必须有一个唯一的internet地址(也成为IP地址),
​ 它是协议上的一个逻辑地址。
​ IPV4协议规定一个IP地址用一个无符号的32bits的整数表示。也就是说有 2^32个地址。
​ 那么这么多的地址,如何去管理呢?
​ 像日常生活中的电话号码一样,分"区号"和"主机号",
​ IP地址也分为两个部分: 网络号,主机号。
​ 网络号: 属于哪一个网络,不同网络里的主机不能直接通信。
​ 主机号: 某个网络里的主机的编号(也叫子网主机号)。

那么网络号和主机号各占多少bits位呢?分两步:(1)看你是哪类的地址(2)看netmask

地址分类                      IP地址范围                  私有地址范围

A: 0+网络号(7)+主机号(24) 0.0.0.0~127.255.255.255 10.0.0.0~10.255.255.255
B: 10+网络号(14)+主机号(16) 128.0.0.0~191.255.255.255 172.16.0.0~172.31.255.255
C: 110+网络号(21)+主机号(8) 192.0.0.0~223.255.255.255 192.168.0.0~192.168.255.255
D: 1110+多播组号(28) 224.0.0.0~239.255.255.255 232.0.0.0~232.255.255.255
E: 11110 保留待用 240.0.0.0~247.255.255.255

"子网掩码(netmask)":分为缺省子网掩码,自定义的子网掩码。子网掩码是用来确定网络号和
	主机号在IP地址中各占多少位的。
一般规定: 网络号在IP地址的连续高bit位(左)
		 主机号在IP地址的连续低bit位(右)
		 IP地址与上netmask的到的数值为网络号
		 IP地址与上netmask的按位取反,得到主机号

	如: 192.168.31.66 ==> netmask: 255.255.255.0
		网络号: 192.168.31.00
		主机号: 66
点分式:把每个字节用十进制来表示,用.分开。

作业: 网上搜索 网桥,集线器,交换机,路由器,网关的作用和区别。

4.端口号

TCP和UDPP都是采用无符号16bits的端口号来识别不同的应用程序的。
IP地址只能唯一标识网络中的主机,但每台主机上并不是只跑一个网络应用程序,
它可以跑多个网络应用程序,而且网络应用从传输层来看可以分为TCP应用和UDP应用。
所以为了区分这些不同的网络应用,故提出了端口号的概念。

注意: TCP端口号和UDP端口号是独立的。

==> 一台主机上的网络应用是由: IP地址 + 传输层协议(TCP/UDP) + 端口号 确定。

端口号由IANA(Internet Assigned Numbers Authority)管理:
众所周知的端口号: 1~1023
http(TCP应用,端口号: 80)
ftp:(TCP应用,端口号: 21)
tftp:(UDP应用, 端口号: 69)
。。。

注册端口: 1024~49151
动态或私有端口: 49152~65535

5.字节序

在CPU内部数据是存放在寄存器(16bits,32bits,64bits),分高bit位和低bit位
但是内存却是按字节来编号的,3000800,300080。
那么如果一个寄存器数据存放到内存中去,该怎么放呢?
(1)大端(Big-Endian)模式: 数据的高字节存放在内存的低地址存储单元
(2)小端(Little-Endian)模式: 数据的低字节存放在内存的低地址存储单元

我们怎么知道我们的处理器采用的是大端模式还是小端模式呢?
方法: 用共用体
union test
{
int a;
char b;
};

	union test data;
	data.a = 0x12345678;
	printf("%x\n",data.b);

网络字节序:
网络字节序就是采用大端模式的字节序。

网络通信是在不同的主机之间进行通信,当我们发送一个整数的时候,如果都采用
自己的字节序去处理就会发生问题。
因此所有的主机在进行网络通信时,都必须采用网络字节序。

6.socket

​ socket即套接字文件,是一种网络编程接口,一种特殊的文件描述符,并不仅限于TCP/IP协议。
​ ===>独立于具体协议栈的网络编程接口,位于应用层与传输层之间。

socket 类型:
(1)SOCK_STREAM: 流式套接字
	主要针对的是 TCP 传输层协议
(2)SOCK_DGRAM: 数据报套接字
	主要针对的是 UDP 传输层协议
(3)SOCK_RAW :原始套接字
	直接逃过传输层

二.socket相关的API

1,socket用来创建一个套接字接口

NAME
       socket - create an endpoint for communication

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

    socket用来创建一个套接字接口
       int socket(int domain, int type, int protocol);
       		@domain: 指定域,协议族
       			AF_INET: IPV4协议族
       			AF_INET6: IPV6协议族
       			AF_UNIX/AF_LOCAL : UNIX协议族
       			AF_BLUETOOTH:  蓝牙协议
       			...
       		@type: 指定创建的套接字类型。
       			SOCK_STREAM: 流式套接字 ==> tcp
       			SOCK_DGRAM: 数据报套接字 ==> udp
       			SOCK_RAW: 原始套接字
       		@protocol : 指定具体的协议。一般为0,不指定。

       返回值:
       	成功返回套接字描述符(文件描述符)
       	失败返回-1,errno被设置

2, bind :把一个IP地址+端口号绑定到套接字上面去

作用: 相当于给该套接字绑定一个身份编号,用来标识接收或发送数据的身份。

 注意: 客户端会自动绑定地址和端口号,所以可以不需要此步骤。而服务器必须要(固定端口号)"地址" = IP地址 + 端口号,我们用通用结构struct sockaddr来表示
 	#include<linux/socket.h>
 	struct sockaddr
 	{
 		sa_family_t  sa_family;//协议族
 		char sa_data[14];//你爱怎么用就怎么用,爱怎么解释就怎么解释。
 	};

 	Internet 协议地址结构(以太网)
 	struct sockaddr_in
 	{
 		sa_family_t  sin_family;//协议族==> AF_INET
 		u_int16_t sin_port;//指定端口号
 		struct  in_addr   sin_addr;//IP地址

 		char sin_zero[8];//无用,作填充
 	};

 	struct in_addr
 	{
 		in_addr_t s_addr;//32位无符号整数, typedef u_int32_t in_addr_t
 	};

 例子:
 	struct sockaddr_in sAddr;//定义一个以太网的地址结构
 	memset(&sAddr,0,sizeof(sAddr));//把结构体清空
 	sAddr.sin_family = AF_INET;//IPV4协议
 	//sAddr.sin_port = 5678;//这种用法是错误的!
 		sin_port是两个字节,所以必须要用网络字节序来保存!
 		内核提供了几个用来在主机字节序和网络字节序之间进行转换的函数:
 		#include <arpa/inet.h>
 		
 		//h:host   n:net   l:long   s:short
 		uint32_t htonl(uint32_t hostlong);//把32位的整数从host转化为net
        uint16_t htons(uint16_t hostshort);//把16位的整数从host转化为net
        uint32_t ntohl(uint32_t netlong);//把32位的整数从net转化为host
        uint16_t ntohs(uint16_t netshort);//把16位的整数从host转为为net
    sAddr.sin_port = htons(5678);//right!
    //sAddr.sin_addr.s_addr = "192.168.31.55";//这种用法是错误的!
    sAddr.sin_addr.s_addr = inet_addr("192.168.31.55");//right
    	
    	#include <sys/socket.h>
        #include <netinet/in.h>
        #include <arpa/inet.h>

        inet_addr用来把一个"点分式"的字符串转换成in_addr_t(无符号32位整数)类型
        	 in_addr_t inet_addr(const char *cp);

       	inet_ntoa用来把一个in_addr_t(无符号32位整数)类型转换成"点分式"字符串
        	 char *inet_ntoa(struct in_addr in);

NAME
       bind - bind a name to a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

    bind :把一个IP地址+端口号绑定到套接字上面去
       int bind(int sockfd, const struct sockaddr *addr,socklen_t addrlen);
       		@sockfd : 要绑定的套接字描述符
       		@addr : 绑定的地址,这里需要把struct sockaddr_in*
       		        强转为 struct sockaddr* 类型
       		@addrlen: 第二个参数指向的是地址结构体的长度。
       			为什么要指定呢?
       			因为不同的协议族的地址结构体长度不一样,为了安全起见。
    返回值:
    	成功返回0
    	失败返回-1,errno被设置

3, listen 监听

NAME
       listen - listen for connections on a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

    listen 用来在套接字上面设置监听数。
    "监听数": 表示同一时刻,允许的最大连接请求数。
       int listen(int sockfd, int backlog);
       		@sockfd: 要监听的套接字
       		@backlog: 监听数。
       	返回值:
       		成功返回0
       		失败返回-1,errno被设置。
    注意: listen后的sockfd称为"监听套接字"

4, connect (客户端)主动去连接一个套接字

NAME
       connect - initiate a connection on a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

    connect用来(客户端)去连接一个套接字(服务器)
       int connect(int sockfd, const struct sockaddr *addr,
                   socklen_t addrlen);
            @sockfd : 套接字描述符,发送接收数据都通过它
            @addr: 指针,指向结构体保存的是服务器的网络地址,表示你要连哪个
            @addrlen: addr指向的网络地址结构体的长度。
       返回值:
       		成功返回0
       		失败返回-1,errno被设置

5, accept (服务器)等待被连接

NAME
       accept, accept4 - accept a connection on a socket

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>

    accept用来从一个监听套接字上接收一个新的连接请求,如果接收了,把"连接套接字"返回。
       int accept(int sockfd, struct sockaddr *addr, socklen_t *addrlen);
       		@sockfd : 监听套接字
       		@addr: struct sockaddr 指针,用来保存客户端的网络地址信息。
       		@addrlen: 指针,用来保存客户端网络地址信息的长度。
       			注意: 此处在调用时,addrlen指向变量的值应为addr指向的结构体的大小,
       			即 addrlen == sizeof(addr)。 当函数返回后,addrlen指向的变量的内容
       			变为保存的客户端地址的实际大小。
       	返回值:
       		成功返回一个"连接套接字"(>0,表示一个新的连接),后续跟客户端的数据通信
       		都通过它。
       		失败返回-1,errno被设置。

6, 发送网络数据write/send/sendto

TCP应用程序三个都可以用
UDP应用程序只能用sendto

send
NAME
       send, sendto, sendmsg - send a message on a socket

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

    send: 是用于TCP通信。
       ssize_t send(int sockfd, const void *buf, size_t len, int flags);
       		@sockfd:  套接字描述符
       		@buf: 要发送的数据的首地址
       		@len: 要发送多少个字节的数据
       		@flags: 一般为0
       			MSG_DONTWAIT: 非阻塞模式发送
       	返回值:
       		成功,返回实际发送的数据字节数
       		失败,返回-1,errno被设置
sendto
只能用于UDP通信
       ssize_t sendto(int sockfd, const void *buf, size_t len, int flags,
                      const struct sockaddr *dest_addr, socklen_t addrlen);
            @sockfd:  套接字描述符
       		@buf: 要发送的数据的首地址
       		@len: 要发送多少个字节的数据
       		@flags: 一般为0
       			MSG_DONTWAIT: 非阻塞模式发送
       		@dest_addr:指针,指向目标地址,表示要发送给谁
       		@addrlen : dest_addr指向的结构体的长度

       	返回值:
       		成功,返回实际发送的数据字节数
       		失败,返回-1,errno被设置

7, 接收网络数据read/recv/recvfrom

TCP应用程序三个都可以用 UDP应用只能使用recvfrom

recv
NAME
       recv, recvfrom, recvmsg - receive a message from a socket

SYNOPSIS
       #include <sys/types.h>
       #include <sys/socket.h>

    recv用于TCP通信。
       ssize_t recv(int sockfd, void *buf, size_t len, int flags);
       		@sockfd:  套接字描述符
       		@buf: 用来保存接收到的数据的空间的首地址
       		@len: 最多接收多少个字节的数据
       		@flags: 一般为0
       	返回值:
       		成功返回实际接收到的字节数
       		失败返回-1,errno被设置
revfrom
recvfrom用于UDP通信
       ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags,
                        struct sockaddr *src_addr, socklen_t *addrlen);
            @sockfd:  套接字描述符
       		@buf: 用来保存接收到的数据的空间的首地址
       		@len: 最多接收多少个字节的数据
       		@flags: 一般为0
       		@src_addr: 指针,指向的地址用来保存对方的网络地址。
       		@addrlen: 指针,用来保存对方的网络地址的长度。
       			注意: 此处在调用时,addrlen指向变量的值应为src_addr指向的结构体的大小,
       			即 addrlen == sizeof(src_addr)。 当函数返回后,addrlen指向的变量的内容
       			变为保存的发送者的地址的实际大小。
       	返回值:
       		成功返回实际接收到的字节数
       		失败返回-1,errno被设置

8,shutdown 关闭套接字(关闭某些功能)

NAME
       shutdown - shut down part of a full-duplex connection

SYNOPSIS
       #include <sys/socket.h>

       int shutdown(int sockfd, int how);
			@sockfd : 指定要关闭的套接字
			@how : 关闭方式,有三种情况
				SHUT_RD: shut read 关闭读
				SHUT_WR: shut write 关闭写
				SHUT_RDWR: shut read and write 关闭读写
		返回值:
			成功返回0
			失败返回-1,errno被设置

三.广播通信笔记

只有传输层协议是UDP协议时,才支持广播功能。

1.广播地址

(1)子网内广播地址: x.x.x.255
IP: 192.168.31.4
netmask : 255.255.255.0
==> 该子网的广播地址是: 192.168.31.255
(2)全网广播地址: 255.255.255.255
会导致网络风暴,会被路由禁掉

2.广播编程思路(广播发送者,广播接收者)

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-1ZU47pKQ-1648221147456)(F:\summer\net\UDP\广播通信\广播编程思路.png)]

3.套接字选项(socket options)

​ 每个套接字在不同的协议层(级别)上有不同的行为属性,那么这个这个行为属性
​ 我们把它叫做套接字选项(socket options)

我们可以通过下面这两个函数来获取或设置这些套接字选项。
	getsockopt: 获取套接字选项
	setsockopt: 设置套接字选项
getsockopt
NAME
       getsockopt, setsockopt - get and set options on sockets

SYNOPSIS
       #include <sys/types.h>          /* See NOTES */
       #include <sys/socket.h>


    getsockopt用来获取套接字选项的值
       int getsockopt(int sockfd, int level, int optname,
                      void *optval, socklen_t *optlen);
            @sockfd : 套接字文件描述符
            @level : 你要获取的选项在哪个级别上
            @optname : 你要获取的选项的名字
            @optval : 指针。指向的内存空间用来保存获取到的选项的值
            @optlen : 指针。用来保存这些选项值的长度。
            	注意: 在调用前,应用保存的是optval指向的内存空间的长度,
            	      在函数返回后,被重写为optval实际值的大小。
        返回值:
        	成功返回0
        	失败返回-1,errno被设置
setsockopt
setsockopt用来设置套接字选项的值
    int setsockopt(int sockfd, int level, int optname,
                   const void *optval, socklen_t optlen);
			@sockfd : 套接字文件描述符
    		@level : 你要设置的选项在哪个级别上
        	@optname : 你要设置的选项的名字
            @optval : 指针。指向的内存空间用来保存要设置的选项的值
            @optlen : 选项值的长度。

         返回值:
			成功返回0
    		失败返回-1,errno被设置

4.例程

udp_client.c

#include <sys/types.h>         
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

char buf[256]={0};

int sockfd;



int Reciver_Init(const char *server_addr,int server_port)
{
	
	/*step1: 创建一个套接字(SOCK_DGRAM)*/
	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd == -1)
	{
		perror("socket failed");
		return -1;
	}

	/*设置套接字选项:允许端口重用*/
	int on=1;
	int r = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT,&on, 4);
	if(r == -1)
	{
		perror("setsockopt failed");
		return -1;
	}

	/*step2:使能广播功能*/
	on=1;
	r = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST,&on, 4);
	if(r == -1)
	{
		perror("setsockopt failed");
		return -1;
	}
	
	/*step3: 生成一个广播的地址,并bind*/
	struct sockaddr_in sAddr;
	memset(&sAddr,0,sizeof(sAddr));//把结构体清空
 	sAddr.sin_family = AF_INET;//IPV4协议
    sAddr.sin_port = htons(server_port);//指定端口号
    sAddr.sin_addr.s_addr = inet_addr(server_addr);//指定IP地址

    r = bind(sockfd, (struct sockaddr *)&sAddr,sizeof(sAddr));
    if(r == -1)
    {
		perror("bind failed");
		return -1;
    }

    
	/*step3: 等待接收数据*/
	while(1)
	{
	    struct sockaddr_in *cAddr = malloc(sizeof(*cAddr));
		memset(cAddr,0,sizeof(*cAddr));//把结构体清空
		socklen_t addrlen = sizeof(*cAddr);
		int r = recvfrom(sockfd, buf, 256, 0, (struct sockaddr *)cAddr, &addrlen);

		puts(buf);
    }
	
	return sockfd;  	
}


int main(int argc, char *argv[])
{
	int sockfd = Reciver_Init(argv[1], atoi(argv[2]));
	if(sockfd == -1)
	{
		printf("Server_Init failed\n");
		return -1;
	}

	
	close(sockfd);
}


udp_server.c

#include <sys/types.h>         
#include <sys/socket.h>
#include <arpa/inet.h>
#include <errno.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>


char buf[256]={0};

int Sender_Init(const char *server_addr,int server_port)
{
	int sockfd;
	/*step1: 创建一个套接字(SOCK_DGRAM)*/
	sockfd = socket(AF_INET, SOCK_DGRAM, 0);
	if(sockfd == -1)
	{
		perror("socket failed");
		return -1;
	}

	/*设置套接字选项:允许端口重用*/
	int on=1;
	int r = setsockopt(sockfd, SOL_SOCKET, SO_REUSEPORT,&on, 4);
	if(r == -1)
	{
		perror("setsockopt failed");
		return -1;
	}
	
	/*step2:使能广播功能*/
	on=1;
	r = setsockopt(sockfd, SOL_SOCKET, SO_BROADCAST,&on, 4);
	if(r == -1)
	{
		perror("setsockopt failed");
		return -1;
	}
	

    /*step3: 向网络上发数据*/
    
	struct sockaddr_in bAddr;
	memset(&bAddr,0,sizeof(bAddr));//把结构体清空
	bAddr.sin_family = AF_INET;//IPV4协议
	bAddr.sin_port = htons(server_port);//指定端口号
	bAddr.sin_addr.s_addr = inet_addr(server_addr);//指定IP地址

	int i=0;
	while(1)
	{
    	char sendbuf[64]={0};
    	sprintf(sendbuf,"%s :%d\n","帅气张大爷,在线吃瓜!",i++);
    	sendto(sockfd, sendbuf, strlen(sendbuf)+1,0,(struct sockaddr *)&bAddr, sizeof(bAddr));
		sleep(1);
	}

	return sockfd;  	
}


int main(int argc, char *argv[])
{
	pthread_t tid;
	int sockfd = Sender_Init(argv[1], atoi(argv[2]));
	if(sockfd == -1)
	{
		printf("client_Init failed\n");
		return -1;
	}

	close(sockfd);
}

四.多播通信笔记

1.多播(Multicast),组播

1,多播也只有传输层协议为UDP时,才支持
2, 多播地址,==>IPV4  D类地址
D: 1110多播组号   224.0.0.0  ~ 239.255.255.255
3. 广播方式发送,占用带宽,造成广播风暴,所以禁止在公网传播。
多播是一种折中的方式,只有加入某个多播组的主机才能收到数据。
4. 多播的编程思路(多播发送者,多播接收者)

5. 开发板要支持多播,需要设置好路由
route add -net 224.0.0.0  netmask 240.0.0.0 dev eth0
route add default gw 192.168.xx.1  dev eth0

vi /etc/init.d/rcS

2.图示

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-nrswTDbh-1648221147461)(F:\summer\net\UDP\多播通信\多播编程思路.png)]

3.例程

multicast_receiver.c

#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>

int main(char *argc, char *argv[])
{
    //1.打开套接字
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd == -1)
    {
        perror("socket error!");
        return -1;
    }

    //2.设置套接字选项,加入多播组
    struct ip_mreq value;
    value.imr_multiaddr.s_addr = inet_addr("224.10.10.1");
    value.imr_interface.s_addr = inet_addr("192.168.31.85");
    int r = setsockopt(sockfd,IPPROTO_IP,IP_ADD_MEMBERSHIP,(void *)&value,sizeof(value));
    if(r == -1)
    {
        perror("setsockopt error");
        return -1;
    }

    //3.绑定多播地址
    struct sockaddr_in info;
    info.sin_family = AF_INET;
    info.sin_port = htons(1234);
    info.sin_addr.s_addr = inet_addr("224.10.10.1");
    r = bind(sockfd,(struct sockaddr *)&info,sizeof(info));
    if(r == -1)
    {
        perror("bind error");
        return -1;
    }

    //4.开始接收数据
    while(1)
    {
        char recvbuf[32]={0};
        struct sockaddr_in from;
        socklen_t size = sizeof(from);
        recvfrom(sockfd,recvbuf,32,0,(struct sockaddr *)&from,&size);
        puts(recvbuf);
    }

    //5.关闭套接字
    close(sockfd);
}

multicast_sender.c

#include <stdio.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <unistd.h>
#include <string.h>

int main(char argc,char *argv[])
{
    //1.打开socket
    int sockfd = socket(AF_INET,SOCK_DGRAM,0);
    if(sockfd == -1)
    {
        perror("socket error!");
        return -1;
    }

    //向多播端口不断的发送数据
    struct sockaddr_in info;
    info.sin_family = AF_INET;
    info.sin_port = htons(1234);
    info.sin_addr.s_addr = inet_addr("224.10.10.1");
    int i = 0;
    while (1)
    {
        sleep(2);
        char sendbuf[30]={0};
        sprintf(sendbuf,"i : %d\n",i++);
        sendto(sockfd,sendbuf,strlen(sendbuf)+1,0,(struct sockaddr *)&info,sizeof(info));
    }
    
    //3.关闭套接字
    close(sockfd);
}

五.IO的四种方式

1.IO的四种方式

	IO: input output 读写一个文件/设备的四种不同的行为方式:

	1. 阻塞IO
		最常用、最简单的、效率最低的
		实时性高

	2. 非阻塞IO
		可防止进程阻塞在IO操作上

	3. IO多路复用(select/poll)
		监听多个文件,允许同时对多个IO进行控制

	4. 异步通知(信号驱动IO)
		当一个文件(或设备)就绪时(可读可写),发一个信号给应用程序。(实时性高,效率高)

2.相关的API

fcntl

https://blog.csdn.net/qq_28114615/article/details/94590598?utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7EBlogCommendFromMachineLearnPai2%7Edefault-3.control

img

img

	NAME
       fcntl - manipulate file descriptor

SYNOPSIS
       #include <unistd.h>
       #include <fcntl.h>

    int fcntl(int fd, int cmd, ... /* arg */ );

    fcntl用5种功能: cmd命令号不同,功能不一样,带的参数也可能不一样。

    =====>

1. 复制一个现有的文件描述符。 cmd == F_DUPFD
	F_DUPFD: duplicate file descriptor
		复制文件描述符fd,新的文件描述符作为函数的返回值返回。
		它是尚未打开的各描述符(“进程文件表项中下标没有用到的”)中的最小值(>=3)2. 获取/设置文件描述符标记。 cmd == F_GETFD/F_SETFD
	"文件描述符标记""文件状态标记"不是一回事。

	F_GETFD: 获取文件描述符的标记。把对应的fd的文件描述符标志作为函数的返回值返回。
			目前只定义了一个文件描述符的标志:
				FD_CLOEXEC:  close fd on exec
			当使用execl执行的程序里,此文件描述符会被自动关闭(释放其资源)
			int flags = fcntl(fd, F_GETFD);
			if(flags & FD_CLOEXEC)
			{
				//该文件描述符设置了 FD_CLOEXEC标志	
			}


	F_SETFD: 设置文件描述符的标志。新标志通过第三个参数指定。 
		eg:
			int flags = fcntl(fd, F_GETFD);
			flags |= FD_CLOEXEC;
			fcntl(fd, F_SETFD, flags);


3, 获取/设置文件状态标记。 cmd == F_GETFL/F_SETFL
	文件状态标志也是用bit位来表示的:
		O_RDONLY  只读
		O_WRONLY  只写
		O_RDWR    可读可写
		O_APPEND  追加
		O_NONBLOCK 非阻塞方式:设置了该标志为非阻塞,未设置则为阻塞方式
		O_SYNC  等待写完成(数据和属性)
				没设置: write写到内核缓冲区就返回
				设置:  write写到文件的硬件中去才返回
		....

	F_GETFL : 获取文件状态标志,作为返回的返回值返回
		eg: unsigned long flags;
		flags = fcntl(fd, F_GETFL);
		if(flags & O_NONBLOCK)
		{
			//非阻塞
		}
	F_SETFL: 设置文件状态标志。新的状态标志作为函数的第三个参数。
		eg: 
			unsigned long flags;
			flags = fcntl(fd, F_GETFL);
			flags |= O_NONBLOCK;
			fcntl(fd, F_SETFL, flags);

4, 获取/设置文件属主进程ID。  cmd == F_GETOWN/F_SETOWN
	F_GETOWN: 获取文件的属主进程ID。通过函数返回值返回进程ID
	F_SETOWN: 设置文件属主。第三个参数为新的属主进程ID
		fcntl(fd,F_SETOWN,getpid());

5, 获取/设置文件记录锁。  cmd == F_GETLK/F_SETLK/F_SETLKW
	记录锁(record locking):当一个进程正在读或修改一个文件的某个部分时,
	它可以阻止其他进程修改同一个文件区域。

	int fcntl(fd,cmd,struct flock *fptr);
		struct flock
		{
			short l_type;//锁的类型
				F_RDLCK: 加锁,加一把读锁
				F_WRLCK: 加锁,加一把写锁
				F_UNLCK: 解锁
			off_t l_start: //您这把锁从文件的哪个地方开始加

			short l_whence: 
				SEEK_SET 相对于文件开头
				SEEK_CUR 相对于当前光标位置
				SEEK_END 相对于文件末尾
			off_t l_len: 加锁区间的长度
			pid_t l_pid: 持有锁的进程PID,即自己的PID

		};

	F_GETLK : 判断由fptr所描述的锁是否被另外一个把锁排斥。
			 fptr所描述的锁包含了操作与区域,若该锁与另外的锁排斥,则fptr指向
			 的结构体会被重写,来描述该区域的状态,即返回后:
			 	l_type == F_UNLCK 则表示不排斥
			 	l_type == F_RDLCK 排斥,已经被上读锁
			 	l_type == F_WRLCK 排斥,已经被上写锁
	F_SETLK : 加锁、解锁操作。锁由fptr来描述。
	F_SETLKW: wait,阻塞方式去加锁、解锁,锁由fptr来描述。

3.多路复用

多路复用提供一种机制,可以同时监听多个文件是否就绪了(可读、可写、出错了)

select
NAME
       select,  pselect,  FD_CLR,  FD_ISSET, FD_SET, FD_ZERO - synchronous I/O
       multiplexing

SYNOPSIS
       /* According to POSIX.1-2001, POSIX.1-2008 */
       #include <sys/select.h>

       /* According to earlier standards */
       #include <sys/time.h>
       #include <sys/types.h>
       #include <unistd.h>

    在select中,用结构体fd_set来表示文件描述符结合,我们可以用以下函数来操作它:
       void FD_SET(int fd, fd_set *set);//用来把文件描述符fd加入到集合set中去
       void FD_CLR(int fd, fd_set *set);//用来把文件描述符fd从集合set中删除
       void FD_ZERO(fd_set *set);//用来把set指向的文件描述符集合清0
       int  FD_ISSET(int fd, fd_set *set);//用来判断文件描述符fd是否在set指向的集合中
       

       int select(int nfds, fd_set *readfds, fd_set *writefds,
                  fd_set *exceptfds, struct timeval *timeout);
            @nfds: 您感兴趣的文件集合中最大的那个文件描述符+1, 即会在[0,nfds)
                   范围去轮询。
            @readfds: 对读感兴趣的文件描述符集合
            @writefds: 对写感兴趣的文件描述符集合
            @execptfds: 对出错感兴趣的文件描述符集合
            @timeout: 超时时间

    select的实现原理:
    	select在实现时,每隔一段时间,就会去询问文件描述符集合[0,nfds)中的每一个
    	文件,看它是否就绪,如果就绪那么就在相应的文件描述符集合中置1。
    	。。。。
    	直到有文件就绪或超时或出错。

    注意:
    	在调用select之前,readfds是你感兴趣读的文件描述符集合。
    	在调用select返回后,readfds是你感兴趣并且可读的文件描述符集合。
    	writefds和exceptfds同理。

    	timeout在调用select之前,timeout是超时时间,
    	timeout在select返回之后,timeout是剩余时间。

    select返回值:
    	如果有文件数据就绪了,立马返回
    	超时了,返回
    	出错了,返回
    	返回值:
    		>0 : 有文件就绪了,并且返回值表示就绪文件的个数。
    		=0 : 没有文件就绪,表示超时了
    		-1 : 表示出错了,errno被设置。

select_client.c

#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <signal.h>
#include <stdlib.h>

int flag = 1;
void my_signal(int sig)
{
    switch (sig)
    {
        case SIGINT: flag = 0;
        break;
    }
}
int client_init(const char *ip,const int port)
{
    //1.打开sockfd
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        perror("socket error");
        return -1;
    }

    //2.开始连接
    struct sockaddr_in info;
    info.sin_family = AF_INET;
    info.sin_addr.s_addr = inet_addr(ip);
    info.sin_port = htons(port);
    int r = connect(sockfd,(struct sockaddr *)&info,sizeof(info));
    if(r == -1)
    {
        perror("connect error!");
        return -1;
    }

    return sockfd;
}

int main(char *argc,char *argv[])
{
    signal(SIGINT,my_signal);
    int sockfd = client_init(argv[1],atoi(argv[2]));
    if(sockfd == -1)
    {
        perror("client_init error!");
        return -1;
    }

    while(flag)
    {
        char sendbuf[32]={0};
        gets(sendbuf);
        write(sockfd,sendbuf,32);
    }

    close(sockfd);
}

select_server.c

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <pthread.h>
#include <stdlib.h>

int flag = 1;
void *handler(void * args)
{
    int connfd = *(int *)args;
    while(1)
    {
        char recvbuf[32]={0};
        read(connfd,recvbuf,32);
        puts(recvbuf);
    }
}

void my_signal(int sig)
{
    switch (sig)
    {
        case SIGINT: flag = 0;break;
    }
}

int server_init(const char *ip,const int port)
{
    /*step1: 创建一个套接字(SOCK_DGRAM)*/
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1)
	{
		perror("socket failed");
		return -1;
	}

    /*step2:绑定IP和端口*/
    struct sockaddr_in info;
    info.sin_family = AF_INET;
    info.sin_port = htons(port);
    printf("INADDR_ANY = %u,info.sin_addr.s_addr = %u\n",INADDR_ANY,inet_addr(ip));
    info.sin_addr.s_addr = inet_addr(ip);
    int r = bind(sockfd,(struct sockaddr *)&info,sizeof(info));
    if(r == -1)
    {
        perror("bind error!");
        return -1;
    }

    /*step3:设定同时监听的个数*/
    r = listen(sockfd,10);
    if(r == -1)
    {
        perror("listen error!");
        return -1;
    }

    /*step4:用select进行监听*/
    //1.创建一个文件描述符集合
    fd_set rfds;
    //2.将文件描述符集合清0,并设置最大的文件描述符并加1,设置超时时间
    int maxfd = 0;
    FD_ZERO(&rfds);
    struct timeval t;
    t.tv_sec = 3;
    t.tv_usec = 0;

    printf("sizeof(rfds) = %ld\n",sizeof(rfds));
    //4.select开始监听
    while (flag)
    {
        //3.将监听套接字加入
        FD_SET(sockfd,&rfds);
        if(maxfd < sockfd)
        {
            maxfd = sockfd + 1;
        }
       r = select(maxfd,&rfds,NULL,NULL,&t);
       if(r <= 0)
       {
           continue;
       }

       if(FD_ISSET(sockfd,&rfds))
       {
           struct sockaddr_in from;
           socklen_t size;
           int connfd = accept(sockfd,(struct sockaddr *)&from,&size);
           if(connfd > 0)
           {
               printf("%s:%d\n",inet_ntoa(from.sin_addr),ntohs(from.sin_port));
               pthread_t pid;
               r = pthread_create(&pid,NULL,handler,(void *)&connfd);
               if(r == -1)
               {
                   perror("pthread_create error!");
               }
           }
       }
    }
    
    return sockfd;
}

int main(char *argc,char *argv[])
{
    signal(SIGINT, my_signal);
    printf("服务器开始启动\n");
    int sockfd = server_init(argv[1],atoi(argv[2]));
    if(sockfd == -1)
    {
        perror("server: server_init error");
        return -1;
    }
    printf("服务器关闭\n");
    close(sockfd);
}

poll
  	select和poll都是轮询方式,复杂度为O(n),但是poll没有数量的限制,
  	因为是用链表存储的。
  	epoll 是用事件通知的方式,复杂度为O(1)。

NAME
       poll, ppoll - wait for some event on a file descriptor

SYNOPSIS
       #include <poll.h>

    在linux内核实现中,一个文件或设备是否可读、可写。。。这些都称为事件(event),
    用bit位来实现的。

       int poll(struct pollfd *fds, nfds_t nfds, int timeout);
       		@fds: struct pollfd 数组首地址
       		@nfds: 数组元素的个数
       		@timeout: 超时时间,单位为毫秒
       	返回值:
       		>0 表示就绪的文件个数
       		=0 表示超时
       		=-1 出错了,errno被设置

       struct pollfd这个结构体用来描述用户对某个文件的哪些事件感兴趣,
       并且返回时,已经发生的事件,也在该结构体中来体现。

       		struct pollfd {
               int   fd;     //你关心的文件描述符
               short events; //要关注的事件
               		POLLIN  该设备是否可被不阻塞的读,该bit位必须设置
               		POLLOUT 该设备或文件是否可以被不阻塞的写
               		POLLERR 该设备或文件是否发送某种错误
               			eg: POLLIN | POLLOUT
               short revents; //该文件更新了的事件状态
           };

例程

poll_client.c

#include <stdio.h>
#include <unistd.h>
#include <arpa/inet.h>
#include <sys/socket.h>
#include <signal.h>
#include <stdlib.h>

int flag = 1;
void my_signal(int sig)
{
    switch (sig)
    {
        case SIGINT: flag = 0;break;
    }
}
int client_init(const char *ip,const int port)
{
    //1.打开sockfd
    int sockfd = socket(AF_INET,SOCK_STREAM,0);
    if(sockfd == -1)
    {
        perror("socket error");
        return -1;
    }

    //2.开始连接
    struct sockaddr_in info;
    info.sin_family = AF_INET;
    info.sin_addr.s_addr = inet_addr(ip);
    info.sin_port = htons(port);
    printf("error :%s:%d\n",inet_ntoa(info.sin_addr),ntohs(info.sin_port));
    int r = connect(sockfd,(struct sockaddr *)&info,sizeof(info));
    if(r == -1)
    {
        perror("connect error!");
        return -1;
    }

    return sockfd;
}

int main(char *argc,char *argv[])
{
    signal(SIGINT,my_signal);
    int sockfd = client_init(argv[1],atoi(argv[2]));
    if(sockfd == -1)
    {
        perror("client_init error!");
        return -1;
    }

    printf("客户端初始完毕\n");
    while(flag)
    {
        char sendbuf[32]={0};
        gets(sendbuf);
        write(sockfd,sendbuf,32);
    }

    close(sockfd);
}

poll_server.c

#include <stdio.h>
#include <unistd.h>
#include <time.h>
#include <sys/socket.h>
#include <arpa/inet.h>
#include <signal.h>
#include <pthread.h>
#include <stdlib.h>
#include <poll.h>

int flag = 1;
void *handler(void * args)
{
    int connfd = *(int *)args;
    while(1)
    {
        char recvbuf[32]={0};
        read(connfd,recvbuf,32);
        puts(recvbuf);
    }
}

void my_signal(int sig)
{
    switch (sig)
    {
        case SIGINT: flag = 0;break;
    }
}

int server_init(const char *ip,const int port)
{
    /*step1: 创建一个套接字(SOCK_DGRAM)*/
	int sockfd = socket(AF_INET, SOCK_STREAM, 0);
	if(sockfd == -1)
	{
		perror("socket failed");
		return -1;
	}

    /*step2:绑定IP和端口*/
    struct sockaddr_in info;
    info.sin_family = AF_INET;
    info.sin_port = htons(port);
    printf("INADDR_ANY = %u,info.sin_addr.s_addr = %u\n",INADDR_ANY,inet_addr(ip));
    info.sin_addr.s_addr = inet_addr(ip);
    int r = bind(sockfd,(struct sockaddr *)&info,sizeof(info));
    if(r == -1)
    {
        perror("bind error!");
        return -1;
    }

    /*step3:设定同时监听的个数*/
    r = listen(sockfd,10);
    if(r == -1)
    {
        perror("listen error!");
        return -1;
    }
    
    printf("sockfd = %d\n",sockfd);
    //4.select开始监听
    while (flag)
    {
        //3.将监听套接字加入
        
      struct pollfd p[1];
      p[0].fd = sockfd;
      p[0].events = POLLIN;
       r = poll(p,1,10);
       if(r <= 0)
       {
           continue;
       }


       printf(" p[0].revents = %d, POLLIN = %d\n",p[0].revents,POLLIN);

       if(p[0].revents & POLLIN)
       {
           struct sockaddr_in from;
           socklen_t size;
           printf("size = %u\n",size);
           int connfd = accept(sockfd,(struct sockaddr *)&from,&size);
           printf("sockfd = %d\n",sockfd);
           printf("error :%s:%d,size = %u\n",inet_ntoa(from.sin_addr),ntohs(from.sin_port),size);
           printf("***************\n");
           printf("connfd = %d\n", connfd);
           if(connfd <= 0)
           {
               perror("accept error!");
           }
           
           if(connfd > 0)
           {
               printf("%s:%d\n",inet_ntoa(from.sin_addr),ntohs(from.sin_port));
               pthread_t pid;
               r = pthread_create(&pid,NULL,handler,(void *)&connfd);
               if(r == -1)
               {
                   perror("pthread_create error!");
               }
           }
       }
    }
    
    return sockfd;
}

int main(char *argc,char *argv[])
{
    signal(SIGINT, my_signal);
    printf("服务器开始启动\n");
    int sockfd = server_init(argv[1],atoi(argv[2]));
    if(sockfd == -1)
    {
        perror("server: server_init error");
        return -1;
    }
    printf("服务器关闭\n");
    close(sockfd);
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值