1.判断远程服务器端口是否开放
/*
socket创建套接字设置connect为非阻塞连接后,connect之后如果端口是不通的,那么阻塞默认是会过几十秒才会重新connect。,所以这时候我们要设置connect为非阻塞。非阻塞的情况下:connect不管怎么样立即返回-1,所以不能当作判断的依据。所以要通过select判断是否可对这个服务器写消息,来判断是否该服务器的端口开放。
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <fcntl.h>
#include <unistd.h>
#include<iostream>
using namespace std;
int main()
{
int sockClient = socket(AF_INET, SOCK_STREAM, 0);
struct sockaddr_in addrSrv;
addrSrv.sin_addr.s_addr = inet_addr("47.107.153.238");
addrSrv.sin_family = AF_INET;
addrSrv.sin_port = htons(22);
struct timeval tv = { 1,0 };//2s 超时
setsockopt(sockClient, SOL_SOCKET, SO_SNDTIMEO, (char *)&tv, sizeof(struct timeval));
int flag = fcntl(sockClient,3, 0);//获取文件打开方式的标志
flag |= O_NONBLOCK;//修改非阻塞标志位
fcntl(sockClient, F_SETFL, flag);
int ret = 0;
while (1)
{
ret = connect(sockClient, (const struct sockaddr *)&addrSrv, sizeof(struct sockaddr_in));
printf("connect ret:%d\n", ret);
fd_set rfd, wfd;
FD_ZERO(&wfd);
FD_SET(sockClient, &wfd);
ret = select(sockClient + 1, &rfd, &wfd, NULL, &tv);//检查套节字是否写,注意必须是套接字最大值+1.超时返回0;失败返回-1;成功返回大于0的整数
printf("now ret:%d\n", ret);
if (FD_ISSET(sockClient, &wfd))//elect将更新这个集合,把其中不可写的套节字去掉
{
printf("open!\n");
break;
}
else
{
printf("connect failed...\n");
}
sleep(1);
}
//又重新设置成阻塞
flag = fcntl(sockClient, 3, 0);
flag &= ~04000;
fcntl(sockClient, 4, flag);
//可以发送数据测试一下,如果单纯为了测试是否连通,直接关闭套接字就行了
/* char buf[10] = { 0 };
for (unsigned int i = 0; i < 5; i++)
{
buf[i] = 'd';
}
ret = send(sockClient, buf, sizeof(buf), 0);
std::cout << "ret:" << ret <<" send success!"<< std::endl; */
close(sockClient);
return 0;
}
2.TCP多进程并发服务器
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
int main(int argc, char *argv[])
{
printf("Start Multiprocess server!\n");
unsigned short port = 8080; // 本地端口
// 创建tcp套接字
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("socket");
exit(-1);
}
// 配置本地网络信息
struct sockaddr_in my_addr;
bzero(&my_addr, sizeof(my_addr)); // 清空
my_addr.sin_family = AF_INET; // IPv4
my_addr.sin_port = htons(port); // 端口
my_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip
// 绑定
int err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
if( err_log != 0)
{
perror("binding");
close(sockfd);
exit(-1);
}
// 监听,套接字变被动
err_log = listen(sockfd, 10);
if(err_log != 0)
{
perror("listen");
close(sockfd);
exit(-1);
}
while(1) //主进程 循环等待客户端的连接
{
char cli_ip[INET_ADDRSTRLEN] = {0};
struct sockaddr_in client_addr;
socklen_t cliaddr_len = sizeof(client_addr);
// 取出客户端已完成的连接
int connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
if(connfd < 0)
{
perror("accept");
close(sockfd);
exit(-1);
}
printf("****************fork():%d*****************************\n",getpid());
pid_t pid = fork();
if(pid < 0){
perror("fork");
_exit(-1);
}else if(0 == pid){ //子进程 接收客户端的信息,并发还给客户端
/*关闭不需要的套接字可节省系统资源,
同时可避免父子进程共享这些套接字
可能带来的不可预计的后果
*/
close(sockfd); // 关闭监听套接字,这个套接字是从父进程继承过来
char recv_buf[1024] = {0};
int recv_len = 0;
// 打印客户端的 ip 和端口
memset(cli_ip, 0, sizeof(cli_ip)); // 清空
inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
printf("----------------------------------------------\n");
printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
// 接收数据
while( (recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0 )
{
printf("recv_buf: %s\n", recv_buf); // 打印数据
send(connfd, recv_buf, recv_len, 0); // 给客户端回数据
}
printf("client closed!\n");
close(connfd); //关闭已连接套接字
exit(0);
}else if(pid > 0){ // 父进程
close(connfd); //关闭已连接套接字
}
}
close(sockfd);
return 0;
}
3.TCP多线程并发服务器
/*由于多线程间共享数据段,而线程处理函数传递的是某个变量的地址,因此当新的请求出现的时候原来地址中的值会被覆盖,因此为了会产生同步和互斥问题,因此需要加锁保护临界变量,等临界变量的副本保存完了之后再把锁解开*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <pthread.h>
pthread_mutex_t mutex; // 定义互斥锁,全局变量
/************************************************************************
函数名称: void *client_process(void *arg)
函数功能: 线程函数,处理客户信息
函数参数: 已连接套接字
函数返回: 无
************************************************************************/
void *client_process(void *arg)
{
int recv_len = 0;
char recv_buf[1024] = ""; // 接收缓冲区
int connfd = *(int *)arg; // 传过来的已连接套接字
// 解锁,pthread_mutex_lock()唤醒,不阻塞
pthread_mutex_unlock(&mutex);
// 接收数据
while((recv_len = recv(connfd, recv_buf, sizeof(recv_buf), 0)) > 0)
{
printf("recv_buf: %s\n", recv_buf); // 打印数据
send(connfd, recv_buf, recv_len, 0); // 给客户端回数据
}
printf("client closed!\n");
close(connfd); //关闭已连接套接字
return NULL;
}
//===============================================================
// 语法格式: void main(void)
// 实现功能: 主函数,建立一个TCP并发服务器
// 入口参数: 无
// 出口参数: 无
//===============================================================
int main(int argc, char *argv[])
{
int sockfd = 0; // 套接字
int connfd = 0;
int err_log = 0;
struct sockaddr_in my_addr; // 服务器地址结构体
unsigned short port = 8080; // 监听端口
pthread_t thread_id;
pthread_mutex_init(&mutex, NULL); // 初始化互斥锁,互斥锁默认是打开的
printf("TCP Server Started at port %d!\n", port);
sockfd = socket(AF_INET, SOCK_STREAM, 0); // 创建TCP套接字
if(sockfd < 0)
{
perror("socket error");
exit(-1);
}
bzero(&my_addr, sizeof(my_addr)); // 初始化服务器地址
my_addr.sin_family = AF_INET;
my_addr.sin_port = htons(port);
my_addr.sin_addr.s_addr = htonl(INADDR_ANY);
printf("Binding server to port %d\n", port);
// 绑定
err_log = bind(sockfd, (struct sockaddr*)&my_addr, sizeof(my_addr));
if(err_log != 0)
{
perror("bind");
close(sockfd);
exit(-1);
}
// 监听,套接字变被动
err_log = listen(sockfd, 10);
if( err_log != 0)
{
perror("listen");
close(sockfd);
exit(-1);
}
printf("Waiting client...\n");
while(1)
{
char cli_ip[INET_ADDRSTRLEN] = ""; // 用于保存客户端IP地址
struct sockaddr_in client_addr; // 用于保存客户端地址
socklen_t cliaddr_len = sizeof(client_addr); // 必须初始化!!!
// 上锁,在没有解锁之前,pthread_mutex_lock()会阻塞
pthread_mutex_lock(&mutex);
//获得一个已经建立的连接
connfd = accept(sockfd, (struct sockaddr*)&client_addr, &cliaddr_len);
if(connfd < 0)
{
perror("accept this time");
continue;
}
// 打印客户端的 ip 和端口
inet_ntop(AF_INET, &client_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
printf("----------------------------------------------\n");
printf("client ip=%s,port=%d\n", cli_ip,ntohs(client_addr.sin_port));
if(connfd > 0)
{
//给回调函数传的参数,&connfd,地址传递
pthread_create(&thread_id, NULL, (void *)client_process, (void *)&connfd); //创建线程
pthread_detach(thread_id); // 线程分离,结束时自动回收资源
}
}
close(sockfd);
return 0;
}
4.IO复用方式实现
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/select.h>
#define SERV_PORT 8080
#define LIST 20 //服务器最大接受连接
#define MAX_FD 10 //FD_SET支持描述符数量
int main(int argc, char *argv[])
{
int sockfd;
int err;
int i;
int connfd;
int fd_all[MAX_FD]; //保存所有描述符,用于select调用后,判断哪个可读
//下面两个备份原因是select调用后,会发生变化,再次调用select前,需要重新赋值
fd_set fd_read; //FD_SET数据备份
fd_set fd_select; //用于select
struct timeval timeout; //超时时间备份
struct timeval timeout_select; //用于select
struct sockaddr_in serv_addr; //服务器地址
struct sockaddr_in cli_addr; //客户端地址
socklen_t serv_len;
socklen_t cli_len;
//超时时间设置
timeout.tv_sec = 10;
timeout.tv_usec = 0;
//创建TCP套接字
sockfd = socket(AF_INET, SOCK_STREAM, 0);
if(sockfd < 0)
{
perror("fail to socket");
exit(1);
}
// 配置本地地址
memset(&serv_addr, 0, sizeof(serv_addr));
serv_addr.sin_family = AF_INET; // ipv4
serv_addr.sin_port = htons(SERV_PORT); // 端口, 8080
serv_addr.sin_addr.s_addr = htonl(INADDR_ANY); // ip
serv_len = sizeof(serv_addr);
// 绑定
err = bind(sockfd, (struct sockaddr *)&serv_addr, serv_len);
if(err < 0)
{
perror("fail to bind");
exit(1);
}
// 监听
err = listen(sockfd, LIST);
if(err < 0)
{
perror("fail to listen");
exit(1);
}
//初始化fd_all数组
memset(&fd_all, -1, sizeof(fd_all));
fd_all[0] = sockfd; //第一个为监听套接字
FD_ZERO(&fd_read); // 清空
FD_SET(sockfd, &fd_read); //将监听套接字加入fd_read
int maxfd;
maxfd = fd_all[0]; //监听的最大套接字
while(1){
// 每次都需要重新赋值,fd_select,timeout_select每次都会变
fd_select = fd_read;
timeout_select = timeout;
// 检测监听套接字是否可读,没有可读,此函数会阻塞
// 只要有客户连接,或断开连接,select()都会往下执行
err = select(maxfd+1, &fd_select, NULL, NULL, NULL);
//err = select(maxfd+1, &fd_select, NULL, NULL, (struct timeval *)&timeout_select);
if(err < 0)
{
perror("fail to select");
exit(1);
}
if(err == 0){
printf("timeout\n");
}
// 检测监听套接字是否可读
if( FD_ISSET(sockfd, &fd_select) ){//可读,证明有新客户端连接服务器
cli_len = sizeof(cli_addr);
// 取出已经完成的连接
connfd = accept(sockfd, (struct sockaddr *)&cli_addr, &cli_len);
if(connfd < 0)
{
perror("fail to accept");
exit(1);
}
// 打印客户端的 ip 和端口
char cli_ip[INET_ADDRSTRLEN] = {0};
inet_ntop(AF_INET, &cli_addr.sin_addr, cli_ip, INET_ADDRSTRLEN);
printf("----------------------------------------------\n");
printf("client ip=%s,port=%d\n", cli_ip,ntohs(cli_addr.sin_port));
// 将新连接套接字加入 fd_all 及 fd_read
for(i=0; i < MAX_FD; i++){
if(fd_all[i] != -1){
continue;
}else{
fd_all[i] = connfd;
printf("client fd_all[%d] join\n", i);
break;
}
}
FD_SET(connfd, &fd_read);
if(maxfd < connfd)
{
maxfd = connfd; //更新maxfd
}
}
//从1开始查看连接套接字是否可读,因为上面已经处理过0(sockfd)
for(i=1; i < maxfd; i++){
if(FD_ISSET(fd_all[i], &fd_select)){
printf("fd_all[%d] is ok\n", i);
char buf[1024]={0}; //读写缓冲区
int num = read(fd_all[i], buf, 1024);
if(num > 0){
//收到 客户端数据并打印
printf("receive buf from client fd_all[%d] is: %s\n", i, buf);
//回复客户端
num = write(fd_all[i], buf, num);
if(num < 0){
perror("fail to write ");
exit(1);
}else{
//printf("send reply\n");
}
}else if(0 == num){ // 客户端断开时
//客户端退出,关闭套接字,并从监听集合清除
printf("client:fd_all[%d] exit\n", i);
FD_CLR(fd_all[i], &fd_read);
close(fd_all[i]);
fd_all[i] = -1;
continue;
}
}else {
//printf("no data\n");
}
}
}
return 0;
}
5.dup() 和 dup2()
dup() 和 dup2() 是两个非常有用的系统调用,都是用来复制一个文件的描述符,使新的文件描述符也标识旧的文件描述符所标识的文件。
这个过程类似于现实生活中的配钥匙,钥匙相当于文件描述符,锁相当于文件,本来一个钥匙开一把锁,相当于,一个文件描述符对应一个文件,现在,我们去配钥匙,通过旧的钥匙复制了一把新的钥匙,这样的话,旧的钥匙和新的钥匙都能开启这把锁。对比于 dup(), dup2() 也一样,通过原来的文件描述符复制出一个新的文件描述符,这样的话,原来的文件描述符和新的文件描述符都指向同一个文件,我们操作这两个文件描述符的任何一个,都能操作它所对应的文件。
//打开一个文件,复制文件描述符,通过 2 个描述符分别对文件进行写操作:
//dup() 后复制的新文件描述符是系统自动分配的最小可用的文件描述符
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char *argv[])
{
int fd1;
int fd2;
// 打开文件
fd1 = open("1.txt", O_CREAT|O_WRONLY, 0666);
if (fd1 < 0)
{
perror("open");
exit(-1);
}
printf("fd1 ============== %d\n", fd1);
// 通过 fd1 复制出 fd2, 最终 fd1, fd2 都指向 1.txt
fd2 = dup(fd1);
printf("fd2 ============== %d\n", fd2);
char *buf1 = "this is a test for fd1\n";
// 操作 fd1 文件描述符
write(fd1, buf1, strlen(buf1));
char *buf2 = "this is a test for fd2\n";
// 操作 fd2 文件描述符
write(fd2, buf2, strlen(buf2));
// 关闭文件描述符,两个都得关
close(fd1);
close(fd2);
return 0;
}
/*
dup2() 的用法,功能和 dup() 完全一样,但是 dup2() 复制出来的新文件描述符可以指定任意一个合法的数字
*/
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd1;
int fd2;
// 打开文件
fd1 = open("1.txt", O_CREAT|O_WRONLY, 0666);
if (fd1 < 0){
perror("open");
exit(-1);
}
printf("fd1 ============== %d\n", fd1);
// 通过 fd1 复制出 fd2, 最终 fd1, fd2 都指向 “1.txt”
// 指定 fd2 的值为 1,1 原来指向标准输出设备,先 close(),再复制
fd2 = dup2(fd1, 1);
// 下面这句话的内容不会打印到屏幕上,而会写到文件 “1.txt” 里
// printf()是标准库函数,最终还是会调用系统调用函数write()
// 相当于这样,write(1,),往 1 文件描述符写内容,
// 默认的情况下, 1 文件描述符指向标准输出设备(如,显示屏)
// 所以, printf()的内容先显示到屏幕上
// 但是现在 1 文件描述符指向文件 “1.txt”
// 所以,printf()的内容会写入文件 “1.txt”
printf("fd2 ============== %d\n", fd2);
close(fd1);
close(fd2);
return 0;
}
6.读写文件
在 Linux 的世界里,一切设备皆文件。我们可以系统调用中 I/O 的函数(I:input,输入;O:output,输出),对文件进行相应的操作( open()、close()、write() 、read() 等)。
打开现存文件或新建文件时,系统(内核)会返回一个文件描述符,文件描述符用来指定已打开的文件。这个文件描述符相当于这个已打开文件的标号,文件描述符是非负整数,是文件的标识,操作这个文件描述符相当于操作这个描述符所指定的文件。
/*
程序运行起来后这三个文件描述符是默认打开的
#define STDIN_FILENO 0 //标准输入的文件描述符
#define STDOUT_FILENO 1 //标准输出的文件描述符
#define STDERR_FILENO 2 //标准错误的文件描述符
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
int main(int argc, char *argv[])
{
if((argc == 3) && (strcmp(argv[1], argv[2]) != 0))
{// 保证有 3 个参数,而且源文件和目的文件名字不能一样
int fd_src, fd_dest, ret;
//只读方式打开源文件
fd_src = open(argv[1], O_RDONLY);
if(fd_src < 0)
{
perror("open argv[1]");
return -1;
}
// 新建目的文件
fd_dest = open(argv[2], O_WRONLY|O_CREAT, 0755);
if(fd_dest < 0)
{
close(fd_src);
perror("open argv[2]");
return -1;
}
do
{
char buf[1024] ="\0";
// 从源文件读取数据
ret = read(fd_src, buf, sizeof(buf));
// 把数据写到目的文件,注意最后一个参数,有多少写多少
write(fd_dest, buf, ret);
}while(ret > 0);
// 关闭已打开的文件
close(fd_src);
close(fd_dest);
}
return 0;
}
7.有名管道
命名管道(FIFO)不同于无名管道之处在于它提供了一个路径名与之关联,以 FIFO 的文件形式存在于文件系统中,这样,即使与 FIFO 的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过 FIFO 相互通信,因此,通过 FIFO 不相关的进程也能交换数据。其特点如下:
1、FIFO 在文件系统中作为一个特殊的文件而存在,但 FIFO 中的内容却存放在内存中。
2、当使用 FIFO 的进程退出后,FIFO 文件将继续保存在文件系统中以便以后使用。
3、FIFO 有名字,不相关的进程可以通过打开命名管道进行通信。
//写端
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd;
int ret;
ret = mkfifo("my_fifo", 0666);//创建命名管道
if(ret != 0)
{
perror("mkfifo");
}
printf("before open\n");
fd = open("my_fifo", O_WRONLY); //等着只读
if(fd < 0)
{
perror("open fifo");
}
printf("after open\n");
printf("before write\n");
// 5s后才往命名管道写数据,没数据前,读端read()阻塞
sleep(5);
char send[100] = "Hello Mike";
write(fd, send, strlen(send));
printf("write to my_fifo buf=%s\n", send);
return 0;
}
//读端
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int main(int argc, char *argv[])
{
int fd;
int ret;
ret = mkfifo("my_fifo", 0666); //创建命名管道
if(ret != 0)
{
perror("mkfifo");
}
printf("before open\n");
fd = open("my_fifo", O_RDONLY);//等着只写
if(fd < 0)
{
perror("open fifo");
}
printf("after open\n");
printf("before read\n");
char recv[100] = {0};
//读数据,命名管道没数据时会阻塞,有数据时就取出来
read(fd, recv, sizeof(recv));
printf("read from my_fifo buf=[%s]\n", recv);
return 0;
}
8.管道(PIPE)
每个管道只有一个页面作为缓冲区,该页面是按照环形缓冲区的方式来使用的。这种访问方式是典型的“生产者——消费者”模型。当“生产者”进程有大量的数据需要写时,而且每当写满一个页面就需要进行睡眠等待,等待“消费者”从管道中读走一些数据,为其腾出一些空间。相应的,如果管道中没有可读数据,“消费者” 进程就要睡眠等待.默认的情况下,从管道中读写数据,最主要的特点就是阻塞问题(这一特点应该记住),当管道里没有数据,另一个进程默认用 read() 函数从管道中读数据是阻塞的。
#include <stdio.h>
#include <string.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/wait.h>
int main(int argc, char *argv[])
{
int fd_pipe[2] = {0};
pid_t pid;
if( pipe(fd_pipe) < 0 ){// 创建无名管道
perror("pipe");
}
pid = fork(); // 创建进程
if( pid < 0 ){ // 出错
perror("fork");
exit(-1);
}
if( pid == 0 ){ // 子进程
_exit(0);
}else if( pid > 0){// 父进程
wait(NULL); // 等待子进程结束,回收其资源
char str[50] = {0};
printf("before read\n");
// 从管道里读数据,如果管道没有数据, read()会阻塞
read(fd_pipe[0], str, sizeof(str));
printf("after read\n");
printf("str=[%s]\n", str); // 打印数据
}
return 0;
}
9.进程间通信——消息队列
有一个队列,队列中存放各种消息。每个进程都可以把数据封存在消息中,再放入队列。每个进程都可以拿到消息队列,再从中取出/放入消息。消息队列不会产生覆盖问题,但需要考虑队列长度。
消息队列的编程步骤:
- 生成key,使用ftok()或用头文件定义
- 创建/获取消息队列 msgget()
- 发送/接收消息 msgsnd() / msgrcv()
- 如果不会再被使用(所有进程都不用)可以删除msgctl()
键(key)值:System V 提供的进程间通信机制需要一个 key 值,通过 key 值就可在系统内获得一个唯一的消息队列标识符。key 值可以是人为指定的,也可以通过 ftok() 函数获得。 //1.获取键(key)值 key_t ftok(const char *pathname, int proj_id);//把从pathname导出的信息与id的低序8位组合成一个整数IPC键 参数:pathname: 路径名 proj_id: 项目ID,非 0 整数(只有低 8 位有效) //2.消息队列的创建 int msgget(key_t key, int msgflg);//得到消息队列标识符或创建一个消息队列对象 参数: key: ftok() 返回的 key 值 msgflg: 标识函数的行为及消息队列的权限 //3.消息队列的读写操作 int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg); //将消息送入消息队列 参数: msqid:消息队列的标识符。 msgp:指向消息缓冲区的指针,此位置用来暂时存储发送和接收的消息,是一个用户可定义的通用结构 ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);//从消息队列读取消息 int msgctl(int msqid, int cmd, struct msqid_ds *buf);//获取和设置消息队列的属性
9.1无类型消息
无类型消息可以是任意类型,比如:整数、字符串、浮点…
无类型消息的取出遵循队列的基本准则: FIFO。
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include<string.h>
#include <unistd.h>
int main()
{
key_t key = ftok(".",100);
if(key==-1)
{
perror("ftok");
exit(-1);
}
int msgid = msgget(key,
0666|IPC_CREAT);
if(msgid==-1)
{
perror("msgget");
exit(-1);
}
int i=-1;
while(1)
{
i++;
char numStr[8];
snprintf(numStr,5,"%d",i%10);
strcat(numStr,"hellow");
int res = msgsnd(msgid,numStr,8,0);
if(res==-1)
{
perror("msgsnd");
exit(-1);
}
sleep(10); //延迟10秒产生一个新消息
}
printf("send ok\n");
}
//练习:写msgb.c,从消息队列中取出hello并打印
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int main()
{
key_t key = ftok(".",100);
if(key==-1)
{
perror("ftok");
exit(-1);
}
int msgid = msgget(key,0);
if(msgid==-1)
{
perror("msgget");
exit(-1);
}
char buf[100] = {};
int res = msgrcv(msgid,buf,sizeof(buf),0,0);
if(res==-1)
{
perror("msgrcv");
exit(-1);
}
printf("buf=%s,rcv=%d\n",buf,res);
}
9.2有消息类型
有类型消息,类型必须是一个结构体:
struct 自定义名字{
long mtype;//消息类型,必须有,格式固定,取值时,必须 大于0
char msg[];//数据
};
msgtypea.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct mymsg
{
long mtype; //类型
char buf[256];// 数据
};
int main(){
key_t key = ftok(".",100);
int msgid = msgget(key,0666|IPC_CREAT);
if(msgid==-1)
{
perror("msgget");
exit(-1);
}
struct mymsg msg1 = {1,"hello1"};
struct mymsg msg2 = {2,"hello2"};
int res1 = msgsnd(msgid,&msg1,
sizeof(msg1)-4,0);
int res2 = msgsnd(msgid,&msg2,
sizeof(msg2)-4,0);
if((res1==0)&&(res2==0))printf("send ok\n");
}
----------------------------------------------------------
msgtypeb.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
struct mymsg
{
long mtype; //类型
char buf[256];// 数据
};
int main()
{
key_t key = ftok(".",100);
int msgid = msgget(key,0);
if(msgid==-1)
{
perror("msgget");
exit(-1);
}
struct mymsg msg;
int res = msgrcv(msgid,&msg,sizeof(msg)-4,
-2,0);
if(res==-1)
{
perror("msgrcv");
exit(-1);
}
printf("mtype=%ld,msg=%s\n",msg.mtype,
msg.buf);
}
10.进程间通信——信号量
信号量广泛用于进程或线程间的同步和互斥,信号量本质上是一个非负的整数计数器,它被用来控制对公共资源的访问。编程时可根据操作信号量值的结果判断是否对公共资源具有访问的权限,当信号量值大于 0 时,则可以访问,否则将阻塞。PV原语是对信号量的操作,一次 P 操作使信号量减1,一次 V 操作使信号量加1。信号量主要用于进程或线程间的同步和互斥这两种典型情况。
在 POSIX 标准中,信号量分两种,一种是无名信号量,一种是有名信号量。无名信号量一般用于线程间同步或互斥,而有名信号量一般用于进程间同步或互斥。它们的区别和管道及命名管道的区别类似,无名信号量则直接保存在内存中,而有名信号量要求创建一个文件。
10.1有名信号量
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:信号量文件名。注意,不能指定路径名。因为有名信号量,默认放在/dev/shm 里。
flags:sem_open() 函数的行为标志。
mode:文件权限(可读、可写、可执行)的设置。
value:信号量初始值。
int sem_close(sem_t *sem);//关闭有名信号量。
参数:
sem:指向信号量的指针。
int sem_unlink(const char *name);//删除有名信号量的文件。
参数:
name:有名信号量文件名。
//有名信号量实现进程间互斥功能:
#include<stdio.h>
#include<semaphore.h>
#include<fcntl.h>
#include<unistd.h>
#include<sys/stat.h>
#include<sys/types.h>
#include <sys/wait.h>
void printer(sem_t *sem, char *str)
{
sem_wait(sem); //信号量减一
while(*str!='\0')
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
printf("\n");
sem_post(sem); //信号量加一
}
int main(int argc, char *argv[])
{
pid_t pid;
sem_t *sem = NULL;
pid = fork(); //创建进程
if(pid<0){ //出错
perror("fork error");
}else if(pid == 0){ //子进程
//跟open()打开方式很相似,不同进程只要名字一样,那么打开的就是同一个有名信号量
sem = sem_open("name_sem", O_CREAT|O_RDWR, 0666, 1); //信号量值为 1
if(sem == SEM_FAILED){ //有名信号量创建失败
perror("sem_open");
return -1;
}
char *str1 = "hello";
printer(sem, str1); //打印
sem_close(sem); //关闭有名信号量
_exit(1);
}else if(pid > 0){ //父进程
//跟open()打开方式很相似,不同进程只要名字一样,那么打开的就是同一个有名信号量
sem = sem_open("name_sem", O_CREAT|O_RDWR, 0666, 1); //信号量值为 1
if(sem == SEM_FAILED){//有名信号量创建失败
perror("sem_open");
return -1;
}
char *str2 = "world";
printer(sem, str2); //打印
sem_close(sem); //关闭有名信号量
wait(NULL); //等待子进程结束
}
sem_unlink("name_sem");//删除有名信号量
return 0;
}
//有名信号量实现进程间同步功能
//print1如下
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#include <stdio.h>
void print(sem_t *print1, sem_t *print2)
{
int i = 0;
while(1)
{
sem_wait(print1);
i++;
printf("int print1 i = %d\n", i);
sem_post(print2);
}
}
int main(int argc, char **argv)
{
sem_t *print1, *print2;
print1 = sem_open("sem_print1", O_CREAT, 0777, 0);
if(SEM_FAILED == print1)
{
perror("sem_open");
}
print2 = sem_open("sem_print2", O_CREAT, 0777, 1);
if(SEM_FAILED == print2)
{
perror("sem_open");
}
print(print1, print2);
return 0;
}
//print2如下
#include <fcntl.h> /* For O_* constants */
#include <sys/stat.h> /* For mode constants */
#include <semaphore.h>
#include <stdio.h>
void print(sem_t *print1, sem_t *print2)
{
int i = 0;
while(1)
{
sem_wait(print2);
i++;
printf("in print2 i = %d\n", i);
sleep(1);
sem_post(print1);
}
}
int main(int argc, char **argv)
{
sem_t *print1, *print2;
print1 = sem_open("sem_print1", O_CREAT, 0777, 0);
if(SEM_FAILED == print1)
{
perror("sem_open");
}
print2 = sem_open("sem_print2", O_CREAT, 0777, 1);
if(SEM_FAILED == print2)
{
perror("sem_open");
}
print(print1, print2);
return 0;
}
//删除有名信号量
#include <semaphore.h>
#include <stdio.h>
void sem_del(char *name)
{
int ret;
ret = sem_unlink(name);
if(ret < 0)
{
perror("sem_unlink");
}
}
int main(int argc, char **argv)
{
sem_del("sem_print1"); //删除信号量文件sem_print1
sem_del("sem_print2"); //删除信号量文件sem_print2
return 0;
}
10.2无名信号量
//1.初始化
int sem_init(sem_t *sem, int pshared, unsigned int value);//创建一个信号量并初始化它的值。一个无名信号量在被使用前必须先初始化。
参数:
sem:信号量的地址。
pshared:等于 0,信号量在线程间共享(常用);不等于0,信号量在进程间共享。
value:信号量的初始值。
//2.P操作
int sem_wait(sem_t *sem);//将信号量的值减 1。操作前,先检查信号量(sem)的值是否为 0,若信号量为 0,此函数会阻塞,直到信号量大于 0 时才进行减 1 操作
int sem_trywait(sem_t *sem);//非阻塞的方式来对信号量进行减 1 操作。若操作前,信号量的值等于 0,则对信号量的操作失败,函数立即返回。
//3.V操作
int sem_post(sem_t *sem);//将信号量的值加 1 并发出信号唤醒等待线程(sem_wait())。
//4.获取信号量的值
int sem_getvalue(sem_t *sem, int *sval);//获取 sem 标识的信号量的值,保存在 sval 中。
//5.销毁信号量
int sem_destroy(sem_t *sem);
具体实例
//1.用于互斥
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
#include <semaphore.h>
sem_t sem; //信号量
void printer(char *str)
{
sem_wait(&sem);//减一
while(*str)
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
printf("\n");
sem_post(&sem);//加一
}
void *thread_fun1(void *arg)
{
char *str1 = "hello";
printer(str1);
}
void *thread_fun2(void *arg)
{
char *str2 = "world";
printer(str2);
}
int main(void)
{
pthread_t tid1, tid2;
sem_init(&sem, 0, 1); //初始化信号量,初始值为 1
//创建 2 个线程
pthread_create(&tid1, NULL, thread_fun1, NULL);
pthread_create(&tid2, NULL, thread_fun2, NULL);
//等待线程结束,回收其资源
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
sem_destroy(&sem); //销毁信号量
return 0;
}
//2.用于同步
#include <stdio.h>
#include <unistd.h>
#include <pthread.h>
#include <semaphore.h>
sem_t sem_g,sem_p; //定义两个信号量
char ch = 'a';
void *pthread_g(void *arg) //此线程改变字符ch的值
{
while(1)
{
sem_wait(&sem_g);
ch++;
sleep(1);
sem_post(&sem_p);
}
}
void *pthread_p(void *arg) //此线程打印ch的值
{
while(1)
{
sem_wait(&sem_p);
printf("%c",ch);
fflush(stdout);
sem_post(&sem_g);
}
}
int main(int argc, char *argv[])
{
pthread_t tid1,tid2;
sem_init(&sem_g, 0, 0); //初始化信号量
sem_init(&sem_p, 0, 1);
pthread_create(&tid1, NULL, pthread_g, NULL);
pthread_create(&tid2, NULL, pthread_p, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
return 0;
}
11.线程互斥锁(mutex)
线程里也有这么一把锁——互斥锁(mutex),互斥锁是一种简单的加锁的方法来控制对共享资源的访问,互斥锁只有两种状态,即上锁( lock )和解锁( unlock )。流程如下:
1)在访问共享资源后临界区域前,对互斥锁进行加锁。
2)在访问完成后释放互斥锁导上的锁。
3)对互斥锁进行加锁后,任何其他试图再次对互斥锁加锁的线程将会被阻塞,直到锁被释放。
//1.初始化互斥锁
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_mutexattr_t *attr);
参数:
mutex:互斥锁地址。类型是 pthread_mutex_t 。attr:设置互斥量的属性,通常可采用默认属性,即可将 attr 设为 NULL。
可以使用宏 PTHREAD_MUTEX_INITIALIZER 静态初始化互斥锁,比如:pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
//2.上锁
int pthread_mutex_lock(pthread_mutex_t *mutex);//对互斥锁上锁,若互斥锁已经上锁,则调用者一直阻塞,直到互斥锁解锁后再上锁。
int pthread_mutex_trylock(pthread_mutex_t *mutex);//不会堵塞直接返回,调用该函数时,若互斥锁未加锁,则上锁,返回 0;若互斥锁已加锁,则函数直接返回失败,即 EBUSY
//3.解锁
int pthread_mutex_unlock(pthread_mutex_t * mutex);
//4.销毁互斥锁
int pthread_mutex_destroy(pthread_mutex_t *mutex);
互斥锁应用举例:
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_mutex_t mutex; //互斥锁
// 打印机
void printer(char *str)
{
pthread_mutex_lock(&mutex); //上锁
while(*str!='\0')
{
putchar(*str);
fflush(stdout);
str++;
sleep(1);
}
printf("\n");
pthread_mutex_unlock(&mutex); //解锁
}
// 线程一
void *thread_fun_1(void *arg)
{
char *str = "hello";
printer(str); //打印
}
// 线程二
void *thread_fun_2(void *arg)
{
char *str = "world";
printer(str); //打印
}
int main(void)
{
pthread_t tid1, tid2;
pthread_mutex_init(&mutex, NULL); //初始化互斥锁
// 创建 2 个线程
pthread_create(&tid1, NULL, thread_fun_1, NULL);
pthread_create(&tid2, NULL, thread_fun_2, NULL);
// 等待线程结束,回收其资源
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_mutex_destroy(&mutex); //销毁互斥锁
return 0;
}
12.读写锁
在对数据的读写操作中,更多的是读操作,写操作较少,例如对数据库数据的读写应用。为了满足当前能够允许多个:
读写锁特点:
1)如果有其它线程读数据,则允许其它线程执行读操作,但不允许写操作。
2)如果有其它线程写数据,则其它线程都不允许读、写操作。
读写锁分为读锁和写锁,规则如下:
1)如果某线程申请了读锁,其它线程可以再申请读锁,但不能申请写锁。
2)如果某线程申请了写锁,其它线程不能申请读锁,也不能申请写锁。
//1.初始化读写锁
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr); //用来初始化 rwlock 所指向的读写锁。
参数:
rwlock:指向要初始化的读写锁指针。
attr:读写锁的属性指针。如果 attr 为 NULL 则会使用默认的属性初始化读写锁,否则使用指定的 attr 初始化读写锁。
可以使用宏 PTHREAD_RWLOCK_INITIALIZER 静态初始化读写锁,比如:pthread_rwlock_t my_rwlock = PTHREAD_RWLOCK_INITIALIZER;这种方法等价于使用 NULL 指定的 attr 参数调用 pthread_rwlock_init() 来完成动态初始化,不同之处在于PTHREAD_RWLOCK_INITIALIZER 宏不进行错误检查。
//2.申请读锁
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock ); //以阻塞方式在读写锁上获取读锁(读锁定)。如果没有写者持有该锁,并且没有写者阻塞在该锁上,则调用线程会获取读锁。如果调用线程未获取读锁,则它将阻塞直到它获取了该锁。一个线程可以在一个读写锁上多次执行读锁定。线程可以成功调用 pthread_rwlock_rdlock() 函数 n 次,但是之后该线程必须调用 pthread_rwlock_unlock() 函数 n 次才能解除锁定。
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock); //用于尝试以非阻塞的方式来在读写锁上获取读锁。如果有任何的写者持有该锁或有写者阻塞在该读写锁上,则立即失败返回。
//3.申请写锁
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock );//在读写锁上获取写锁(写锁定)。如果没有写者持有该锁,并且没有写者读者持有该锁,则调用线程会获取写锁。如果调用线程未获取写锁,则它将阻塞直到它获取了该锁。
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock); //用于尝试以非阻塞的方式来在读写锁上获取写锁。如果有任何的读者或写者持有该锁,则立即失败返回。
//4.解锁
int pthread_rwlock_unlock (pthread_rwlock_t *rwlock); //无论是读锁或写锁,都可以通过此函数解锁。
//5.销毁读写锁
int pthread_rwlock_destroy(pthread_rwlock_t *rwlock);//用于销毁一个读写锁,并释放所有相关联的资源(所谓的所有指的是由 pthread_rwlock_init() 自动申请的资源) 。
读写锁应用实例:
下面是一个使用读写锁来实现 4 个线程读写一段数据是实例。在此示例程序中,共创建了 4 个线程,其中两个线程用来写入数据,两个线程用来读取数据。当某个线程读操作时,其他线程允许读操作,却不允许写操作;当某个线程写操作时,其它线程都不允许读或写操作。
#include<stdio.h>
#include<unistd.h>
#include<pthread.h>
pthread_rwlock_t rwlock; //读写锁
int num = 1;
//读操作,其他线程允许读操作,却不允许写操作
void *fun1(void *arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
printf("read num first===%d\n",num);
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
}
//读操作,其他线程允许读操作,却不允许写操作
void *fun2(void *arg)
{
while(1)
{
pthread_rwlock_rdlock(&rwlock);
printf("read num second===%d\n",num);
pthread_rwlock_unlock(&rwlock);
sleep(2);
}
}
//写操作,其它线程都不允许读或写操作
void *fun3(void *arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);
num++;
printf("write thread first\n");
pthread_rwlock_unlock(&rwlock);
sleep(2);
}
}
//写操作,其它线程都不允许读或写操作
void *fun4(void *arg)
{
while(1)
{
pthread_rwlock_wrlock(&rwlock);
num++;
printf("write thread second\n");
pthread_rwlock_unlock(&rwlock);
sleep(1);
}
}
int main()
{
pthread_t ptd1, ptd2, ptd3, ptd4;
pthread_rwlock_init(&rwlock, NULL);//初始化一个读写锁
//创建线程
pthread_create(&ptd1, NULL, fun1, NULL);
pthread_create(&ptd2, NULL, fun2, NULL);
pthread_create(&ptd3, NULL, fun3, NULL);
pthread_create(&ptd4, NULL, fun4, NULL);
//等待线程结束,回收其资源
pthread_join(ptd1,NULL);
pthread_join(ptd2,NULL);
pthread_join(ptd3,NULL);
pthread_join(ptd4,NULL);
pthread_rwlock_destroy(&rwlock);//销毁读写锁
return 0;
}
13.线程私有数据
在多线程程序中,经常要用全局变量来实现多个函数间的数据共享。由于数据空间是共享的,因此全局变量也为所有线程共有。在多线程程序中,经常要用全局变量来实现多个函数间的数据共享。由于数据空间是共享的,因此全局变量也为所有线程共有。
//1.创建线程私有数据
int pthread_key_create(pthread_key_t *key, void (*destructor)(void*));//创建一个类型为 pthread_key_t 类型的私有数据变量( key )。
参数:
key:在分配( malloc )线程私有数据之前,需要创建和线程私有数据相关联的键( key ),这个键的功能是获得对线程私有数据的访问权。
destructor:清理函数名字( 如:fun )。当线程退出时,如果线程私有数据地址不是非 NULL,此函数会自动被调用。该函数指针可以设成 NULL ,这样系统将调用默认的清理函数。
//2.注销线程私有数据
int pthread_key_delete(pthread_key_t key);//注销线程私有数据。这个函数并不会检查当前是否有线程正使用线程私有数据( key ),也不会调用清理函数 destructor() ,而只是将线程私有数据( key )释放以供下一次调用 pthread_key_create() 使用。
//3.设置线程私有数据的关联
int pthread_setspecific(pthread_key_t key, const void *value);//设置线程私有数据( key ) 和 value 关联,注意,是 value 的值(不是所指的内容)和 key 相关联。
参数:
key:线程私有数据。
value:和 key 相关联的指针。
//4.读取线程私有数据所关联的值
void *pthread_getspecific(pthread_key_t key);
示例代码如下:
// this is the test code for pthread_key
#include <stdio.h>
#include <pthread.h>
#include <unistd.h>
pthread_key_t key; // 私有数据,全局变量
void echomsg(void *t)
{
printf("[destructor] thread_id = %lu, param = %p\n", pthread_self(), t);
}
void *child1(void *arg)
{
int i = 10;
pthread_t tid = pthread_self(); //线程号
printf("\nset key value %d in thread %lu\n", i, tid);
pthread_setspecific(key, &i); // 设置私有数据
printf("thread one sleep 2 until thread two finish\n\n");
sleep(2);
printf("\nthread %lu returns %d, add is %p\n",
tid, *((int *)pthread_getspecific(key)), pthread_getspecific(key) );
}
void *child2(void *arg)
{
int temp = 20;
pthread_t tid = pthread_self(); //线程号
printf("\nset key value %d in thread %lu\n", temp, tid);
pthread_setspecific(key, &temp); //设置私有数据
sleep(1);
printf("thread %lu returns %d, add is %p\n",
tid, *((int *)pthread_getspecific(key)), pthread_getspecific(key));
}
int main(void)
{
pthread_t tid1,tid2;
pthread_key_create(&key, echomsg); // 创建
prhread_create(&tid1, NULL, child1, NULL);
pthread_create(&tid2, NULL, child2, NULL);
pthread_join(tid1, NULL);
pthread_join(tid2, NULL);
pthread_key_delete(key); // 注销
return 0;
}
14.信号
信号是 Linux 进程间通信的最古老的方式。信号是软件中断,它是在软件层次上对中断机制的一种模拟,是一种异步通信的方式 。信号可以导致一个正在运行的进程被另一个正在运行的异步进程中断,转而处理某一个突发事件。信号可以直接进行用户空间进程和内核空间进程的交互,内核进程可以利用它来通知用户空间进程发生了哪些系统事件。
一个完整的信号周期包括三个部分:信号的产生,信号在进程中的注册,信号在进程中的注销,执行信号处理函数
产生一个信号,我们可以让其执行自定义信号处理函数。假如有函数 A, B, C,我们如何确定信号产生后只调用函数 A,而不是函数 B 或 C。这时候,我们需要一种规则规定,信号产生后就调用函数 A,就像交通规则一样,红灯停绿灯行,信号注册函数 signal() 就是做这样的事情。
//1.发送信号
int kill(pid_t pid, int signum);//给指定进程发送信号。使用 kill() 函数发送信号,接收信号进程和发送信号进程的所有者必须相同,或者发送信号进程的所有者是超级用户。
参数:
pid: 取值有 4 种情况:
pid > 0: 将信号传送给进程 ID 为pid的进程。
pid = 0: 将信号传送给当前进程所在进程组中的所有进程。
pid = -1: 将信号传送给系统内所有的进程。
pid < -1: 将信号传给指定进程组的所有进程。这个进程组号等于 pid 的绝对值。
signum: 信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l (“l” 为字母)进行相应查看。
//2.等待信号
int pause(void);//等待信号的到来(此函数会阻塞)。将调用进程挂起直至捕捉到信号为止,此函数通常用于判断信号是否已到。
//3.处理信号
一个进程收到一个信号的时候,可以用如下方法进行处理:
1)执行系统默认动作 对大多数信号来说,系统默认动作是用来终止该进程。 2)忽略此信号
3)执行自定义信号处理函数,用用户定义的信号处理函数处理该信号。
//4.信号注册函数
typedef void (*sighandler_t)(int);// 回调函数的声明
sighandler_t signal(int signum,sighandler_t handler);//注册信号处理函数(不可用于 SIGKILL、SIGSTOP 信号),即确定收到信号后处理函数的入口地址。此函数不会阻塞。
参数:
signum:信号的编号,这里可以填数字编号,也可以填信号的宏定义,可以通过命令 kill -l (“l” 为字母)进行相应查看。
handler: 取值有 3 种情况:
SIG_IGN:忽略该信号
SIG_DFL:执行系统默认动作
信号处理函数名:自定义信号处理函数。
实例:
父子进程各自每隔一秒打印一句话,3 秒后,父进程通过 kill() 函数给子进程发送一个中断信号 SIGINT( 2 号信号),最终,子进程结束,剩下父进程在打印信息:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <signal.h>
int main(int argc, char *argv[])
{
pid_t pid;
int i = 0;
pid = fork(); // 创建进程
if( pid < 0 ){ // 出错
perror("fork");
}
if(pid == 0){ // 子进程
while(1){
printf("I am son\n");
sleep(1);
}
}else if(pid > 0){ // 父进程
while(1){
printf("I am father\n");
sleep(1);
i++;
if(3 == i){// 3秒后
kill(pid, SIGINT); // 给子进程 pid ,发送中断信号 SIGINT
// kill(pid, 2); // 等级于kill(pid, SIGINT);
}
}
}
return 0;
}
信号处理函数示例1:
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
// 信号处理函数
void signal_handler(int signo)
{
if(signo == SIGINT){
printf("recv SIGINT\n");
}else if(signo == SIGQUIT){
printf("recv SIGQUIT\n");
}
}
int main(int argc, char *argv[])
{
printf("wait for SIGINT OR SIGQUIT\n");
/* SIGINT: Ctrl+c ; SIGQUIT: Ctrl+\ */
// 信号注册函数
signal(SIGINT, signal_handler);
signal(SIGQUIT, signal_handler);
// 等待信号
pause();
pause();
return 0;
}
信号集
为了方便对多个信号进行处理,一个用户进程常常需要对多个信号做出处理,在 Linux 系统中引入了信号集(信号的集合)。这个信号集有点类似于我们的 QQ 群,一个个的信号相当于 QQ 群里的一个个好友。
信号集是用来表示多个信号的数据类型(sigset_t),其定义路径为:/usr/include/i386-linux-gnu/bits/sigset.h。
信号集相关的操作主要有如下几个函数
int sigemptyset(sigset_t *set);
int sigfillset(sigset_t *set);
int sigismember(const sigset_t *set, int signum);
int sigaddset(sigset_t *set, int signum);
int sigdelset(sigset_t *set, int signum);
示例2:
#include <signal.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
sigset_t set; // 定义一个信号集变量
int ret = 0;
sigemptyset(&set); // 清空信号集的内容
// 判断 SIGINT 是否在信号集 set 里
// 在返回 1, 不在返回 0
ret = sigismember(&set, SIGINT);
if(ret == 0){
printf("SIGINT is not a member of set \nret = %d\n", ret);
}
sigaddset(&set, SIGINT); // 把 SIGINT 添加到信号集 set
sigaddset(&set, SIGQUIT);// 把 SIGQUIT 添加到信号集 set
// 判断 SIGINT 是否在信号集 set 里
// 在返回 1, 不在返回 0
ret = sigismember(&set, SIGINT);
if(ret == 1){
printf("SIGINT is a member of set \nret = %d\n", ret);
}
sigdelset(&set, SIGQUIT); // 把 SIGQUIT 从信号集 set 移除
// 判断 SIGQUIT 是否在信号集 set 里
// 在返回 1, 不在返回 0
ret = sigismember(&set, SIGQUIT);
if(ret == 0){
printf("SIGQUIT is not a member of set \nret = %d\n", ret);
}
return 0;
}
15.共享内存
共享内存是进程间通信中最简单的方式之一。共享内存允许两个或更多进程访问同一块内存,就如同 malloc() 函数向不同进程返回了指向同一个物理内存区域的指针。当一个进程改变了这块地址中的内容的时候,其它进程都会察觉到这个更改。
共享内存的特点:
1)共享内存是进程间共享数据的一种最快的方法。
一个进程向共享的内存区域写入了数据,共享这个内存区域的所有进程就可以立刻看到其中的内容。
2)使用共享内存要注意的是多个进程之间对一个给定存储区访问的互斥。
若一个进程正在向共享内存区写数据,则在它做完这一步操作前,别的进程不应当去读、写这些数据。
//1.创建共享内存
int shmget(key_t key, size_t size,int shmflg);//创建或打开一块共享内存区。
参数:
key:进程间通信键值,ftok() 的返回值。
size:该共享存储段的长度(字节)。
shmflg:标识函数的行为及共享内存的权限,其取值如下:
IPC_CREAT:如果不存在就创建
IPC_EXCL: 如果已经存在则返回失败
位或权限位:共享内存位或权限位后可以设置共享内存的访问权限,格式和 open() 函数的 mode_t 一样(open() 的使用请点此链接),但可执行权限未使用。
//2.内存映射
void *shmat(int shmid, const void *shmaddr, int shmflg);//将一个共享内存段映射到调用进程的数据段中。简单来理解,让进程和共享内存建立一种联系,让进程某个指针指向此共享内存。
参数:
shmid:共享内存标识符,shmget() 的返回值。
shmaddr:共享内存映射地址(若为 NULL 则由系统自动指定),推荐使用 NULL。
shmflg:共享内存段的访问权限和映射条件( 通常为 0 ),具体取值如下:
0:共享内存具有可读可写权限。
SHM_RDONLY:只读。
SHM_RND:(shmaddr 非空时才有效)
//3.解除共享内内存映射
int shmdt(const void *shmaddr);//将共享内存和当前进程分离( 仅仅是断开联系并不删除共享内存,相当于让之前的指向此共享内存的指针,不再指向)。
4.共享内存控制
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
shmid:共享内存标识符。
cmd:函数功能的控制,其取值如下:
IPC_RMID:删除。(常用
)IPC_SET:设置 shmid_ds 参数,相当于把共享内存原来的属性值替换为 buf 里的属性值。IPC_STAT:保存 shmid_ds 参数,把共享内存原来的属性值备份到 buf 里。SHM_LOCK:锁定共享内存段( 超级用户 )。SHM_UNLOCK:解锁共享内存段。
SHM_LOCK 用于锁定内存,禁止内存交换。并不代表共享内存被锁定后禁止其它进程访问。其真正的意义是:被锁定的内存不允许被交换到虚拟内存中。这样做的优势在于让共享内存一直处于内存中,从而提高程序性能
buf:shmid_ds 数据类型的地址,用来存放或修改共享内存的属性。
示例1:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define BUFSZ 1024
int main(int argc, char *argv[])
{
int shmid;
key_t key;
key = ftok("./", 2015);
if(key == -1)
{
perror("ftok");
}
//创建共享内存
shmid = shmget(key, BUFSZ, IPC_CREAT|0666);
if(shmid < 0)
{
perror("shmget");
exit(-1);
}
return 0;
}
示例2,两个进程进行读写数据共享
//1.写端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define BUFSZ 512
int main(int argc, char *argv[])
{
int shmid;
int ret;
key_t key;
char *shmadd;
//创建key值
key = ftok("./", 2015);
if(key == -1)
{
perror("ftok");
}
//创建共享内存
shmid = shmget(key, BUFSZ, IPC_CREAT|0666);
if(shmid < 0)
{
perror("shmget");
exit(-1);
}
//映射
shmadd = shmat(shmid, NULL, 0);
if(shmadd < 0)
{
perror("shmat");
_exit(-1);
}
//拷贝数据至共享内存区
printf("copy data to shared-memory\n");
bzero(shmadd, BUFSZ); // 共享内存清空
strcpy(shmadd, "how are you, mike\n");
return 0;
}
//2.读端代码
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#define BUFSZ 512
int main(int argc, char *argv[])
{
int shmid;
int ret;
key_t key;
char *shmadd;
//创建key值
key = ftok("./", 2015);
if(key == -1)
{
perror("ftok");
}
system("ipcs -m"); //查看共享内存
//打开共享内存
shmid = shmget(key, BUFSZ, IPC_CREAT|0666);
if(shmid < 0)
{
perror("shmget");
exit(-1);
}
//映射
shmadd = shmat(shmid, NULL, 0);
if(shmadd < 0)
{
perror("shmat");
exit(-1);
}
//读共享内存区数据
printf("data = [%s]\n", shmadd);
//分离共享内存和当前进程
ret = shmdt(shmadd);
if(ret < 0)
{
perror("shmdt");
exit(1);
}
else
{
printf("deleted shared-memory\n");
}
//删除共享内存
shmctl(shmid, IPC_RMID, NULL);
system("ipcs -m"); //查看共享内存
return 0;
}
16.临界区(Windows下概念)
#include"bits/stdc++.h"
#include <windows.h>
using namespace std;
class CMyCriticalSection
{
public:
CMyCriticalSection()
{
InitializeCriticalSection(&m_cSection);
}
void Lock()
{
EnterCriticalSection(&m_cSection);
}
void UnLock()
{
LeaveCriticalSection(&m_cSection);
}
//利用析构函数删除临界区对象
virtual ~CMyCriticalSection()
{
DeleteCriticalSection(&m_cSection);
}
private:
CRITICAL_SECTION m_cSection;
};
class CCriticalSectionAutoLock
{
public:
//利用构造函数上锁,即进去临界区
CCriticalSectionAutoLock(CMyCriticalSection* mySection)
:pCMySection(mySection)
{
pCMySection->Lock();
}
//利用析构函数解锁,即离开临界区
virtual ~CCriticalSectionAutoLock()
{
pCMySection->UnLock();
}
private:
CMyCriticalSection* pCMySection;
};
CMyCriticalSection MyCriticalSection; //临界区
DWORD WINAPI Fun(LPVOID lpParamter)
{
string strPrint((const char*)lpParamter);
int iRunTime = 0;
//执行100次跳出
while (++iRunTime < 10)
{
{
CCriticalSectionAutoLock cLock(&MyCriticalSection); //进入临界区
cout << "[" << iRunTime << "]:" << strPrint.c_str() << endl;
}
Sleep(1); //若去掉此句 可能导致其他线程无法进入临界区,因为 cLock在这之前析构,离开临界区
}
return 0;
}
int main()
{
//创建五个子线程
string str1 = "A";
string str2 = "B";
string str3 = "C";
string str4 = "D";
string str5 = "E";
HANDLE hThread1 = CreateThread(NULL, 0, Fun, (void*)str1.c_str(), 0, NULL);
HANDLE hThread2 = CreateThread(NULL, 0, Fun, (void*)str2.c_str(), 0, NULL);
HANDLE hThread3 = CreateThread(NULL, 0, Fun, (void*)str3.c_str(), 0, NULL);
HANDLE hThread4 = CreateThread(NULL, 0, Fun, (void*)str4.c_str(), 0, NULL);
HANDLE hThread5 = CreateThread(NULL, 0, Fun, (void*)str5.c_str(), 0, NULL);
//关闭线程
CloseHandle(hThread1);
CloseHandle(hThread2);
CloseHandle(hThread3);
CloseHandle(hThread4);
CloseHandle(hThread5);
getchar();
// system("pause");
return 0;
}
17.生产者和消费者问题(Window)
#include <iostream>
#include<queue>
#include <windows.h>
using namespace std;
#define N 100
#define up(mutex) ReleaseSemaphore(mutex, 1, NULL);//释放信号量
#define down(mutex) WaitForSingleObject(mutex, INFINITE);//等待信号量
typedef HANDLE semaphore;
semaphore mutex;
semaphore myempty;//初始N最大为N
semaphore myfull;//初始0最大为N
const int g_Number = 2;
DWORD WINAPI Productor(__in LPVOID lpParameter);
DWORD WINAPI Consumer(__in LPVOID lpParameter);
queue<int> itemQue;
int main()
{
mutex = CreateSemaphore(NULL, 1, 1, NULL);//第一个参数为安全属性;第二个参数为信号量初始值,第三个参数为信号量最大值
myempty = CreateSemaphore(NULL, N, N, NULL);
myfull = CreateSemaphore(NULL, 0, N, NULL);
HANDLE hThread[g_Number] = { 0 };
int first = 1, second = 2, third = 2;
hThread[0] = CreateThread(NULL, 0, Productor, (LPVOID)first, 0, NULL);
hThread[1] = CreateThread(NULL, 0, Consumer, (LPVOID)second, 0, NULL);
WaitForMultipleObjects(g_Number, hThread, TRUE, INFINITE);
CloseHandle(hThread[0]);
CloseHandle(hThread[1]);
CloseHandle(mutex);
return 0;
}
int produce_item() {
static int productorNumber = 0;
productorNumber++;
return productorNumber;
}
void remove_item()
{
int item = itemQue.front();
itemQue.pop();
cout << "消费:【" << item <<","<<itemQue.size()<< "】" << endl;
Sleep(1000);
}
void insert_item(int item)
{
itemQue.push(item);
cout << "生产:【" << item << "," << itemQue.size() << "】" << endl;
Sleep(1000);
}
DWORD WINAPI Productor(__in LPVOID lpParameter)//生产者
{
while (1)
{
int item = produce_item();
down(myempty);
down(mutex);
insert_item(item);
up(mutex);
up(myfull);
}
return 0;
}
DWORD WINAPI Consumer(__in LPVOID lpParameter)
{
while (1)
{
down(myfull);
down(mutex);
remove_item();
up(mutex);
up(myempty);
}
return 0;
}