【从浅学到熟知Linux】进程间通信之命名管道(含mkfifo命令及函数调用示例、读写阻塞及ONOBLOCK详解、匿名与命名管道区别、命名管道实现Client&Server)

本文详细介绍了Linux中的命名管道和匿名管道,包括它们的创建方法、区别以及在Client-Server通信中的应用。重点讲解了命名管道的内存级特性、同步机制以及O_NONBLOCK模式的使用。
摘要由CSDN通过智能技术生成

在这里插入图片描述

🏠关于专栏:Linux的浅学到熟知专栏用于记录Linux系统编程、网络编程等内容。
🎯每天努力一点点,技术变化看得见


创建命名管道的方法

进程间通信的本质是让不同的进程看到同一份资源。由于匿名管道只适用于具有血缘关系(具有共同祖先)的进程间进行通信,那如果是两个不相关的进程交换数据呢?该怎么实现呢?

我们可以使用FIFO文件实现不相关的进程的通信需求,这个文件常被称为命名管道。下面我们来看看如何创建命名管道↓↓↓

命令行创建命名管道

mkfifo [命名管道文件名]

下面示例中,在当前目录创建了一个名为connect-file的文件,通过ls -l可以看到,connect-file的类型为p(管道)。↓↓↓
在这里插入图片描述
现在我们打开两个终端,左侧终端向connect-file写入,右侧终端从connect-file读取,这样就可以实现跨终端通信了↓↓↓
在这里插入图片描述

函数接口创建命名管道

在这里插入图片描述
由于FIFO(命名管道)也是文件,第一个参数pathname表示要在哪个目录下创建命名管道,第二个参数表示文件创建时的权限。

下面程序演示了如何使用mkfifo函数创建命名管道↓↓↓

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

int main()
{
	mkfifo("connect-fifo", 0666);
	return 0;
}

在这里插入图片描述
★ps:创建命名管道本质也是创建文件,文件的最终权限=mode&~umask。注意,命名管道创建时必须指定文件权限,不能省略。

那为什么不直接创建一个文件,让通信进程访问该文件呢?由于文件每次写入和删除内容,它都会更新到磁盘上,磁盘是一个慢速外设,故速度较慢。而命名管道是一个内存级文件,该文件创建后,在该文件执行通信任务时,任何读、写、修改操作都不会使用到磁盘(即不会落盘、刷盘),这样可以大大提高通信效率。

★ps:命名管道通信方式如何保证两个进程看到同一份资源?由于文件名=路径+具体文件名,同一个路径下不可能存在两个同名文件,故可以保证两个进程看到同一份资源。

命名管道的打开方式

如果想实现两个进程的通信,则需要让双方打开同一个文件,一个读,一个写。在没有特别指定的情况下,当读进程读取命名管道中的内容时,如果内部为空(写进程还没有写),则会阻塞等待;写进程写入命名管道的内容时,如果读进程还没有读,则会阻塞等待。

下面程序中,让写进程向已经存在的管道文件中写入,待写进程写入完毕后,再启动读进程↓↓↓

写进程代码

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

int main()
{
	printf("write start:%u\n", time(NULL));
	int fp = open("connect-fifo", O_WRONLY);
	char* msg = "Jammingpro";
	write(fp, msg, strlen(msg));
	printf("write success!\n");
	printf("write:%u\n", time(NULL));
}

读进程代码

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

int main()
{
	printf("read start:%u\n", time(NULL));
	int fp = open("connect-fifo", O_RDONLY);
	char buffer[1024];
	ssize_t n = read(fp, buffer, sizeof(buffer));
	buffer[n] = '\0';
	printf("read success! -> %s\n", buffer);
	printf("read:%u\n", time(NULL));
	return 0;
}

在这里插入图片描述

从上面程序执行可以知道,执行写的进程在读进程启动前将阻塞等待,待读进程启动后,读进程将命名管道中的数据读取走后,才会停止阻塞。

★ps:在写进程执行open系统调用时,就会开始阻塞,而不是等到执行write系统调用才阻塞

那如果让读进程先执行,写进程再执行呢?↓↓↓
在这里插入图片描述
读进程会阻塞等待,直到写进程进行写入。

从上面的程序执行结果可知,命名管道提供了同步机制,即必须先写入再读取。违背这个条件的话,则会出现上面的读端或写端阻塞的情况。如果我们不希望阻塞,则可以在open时,以O_NONBLOCK模式打开↓↓↓

int fd = open("connect-fifo", O_WDOBLY | O_NONBLOCK);

下面我们编写以O_NONBLOCK方式打开的读写端程序↓↓↓
wblock.c

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <time.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <errno.h>
int main()
{
  int fd = open("connect-fifo", O_WRONLY | O_NONBLOCK);
  if(fd < 0)
  {
    perror("open error");
    exit(1);
  }

  while(1)
  {
    char* msg = "Jammingpro";
    int ret = write(fd, msg, strlen(msg));
    if(ret == -1)
    {
      if(errno == EAGAIN)
      {
        printf("EAGAIN\n");
      }
      else 
      {
        perror("write error");
        exit(2);
      }
    }
    else 
    {
      printf("write success\n");
    }
    usleep(10000);
  }
  return 0;
}

rblock.c

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

int main()
{
  int fd = open("connect-fifo", O_RDONLY | O_NONBLOCK);
  int cnt = 0;
  char buffer[1024];
  while(1)
  {
    int n = read(fd, buffer, sizeof(buffer) - 1);
    buffer[n] = '\0';
    printf("%d->%s\n",cnt, buffer);
    cnt++;
    sleep(1);
  }
  return 0;
}

即使对应的命名管道文件已经存在,但如果读进程未执行(即没有进程以读方式打开命名管道文件),此时执行写进程,则会出现如下错误(6号错误)↓↓↓
在这里插入图片描述
如果读进程打开,写进程再打开,此时若命名管道已经被写满,则写进程会得到一个EAGAIN的错误码,该错误码可以通过errno.h头文件中的errno变量获取↓↓↓
在这里插入图片描述
对于读端来说,如果没有写端(或写端没有写入)时,读端可以从命名管道中读数据,只是这个数据为空而已。一旦有写端写入,则读端可以正常读取。

从上面可以得出,在设置以O_NONBLOCK方式打开命名管道时,一定要保证读进程先打开,否则写端进程无法写入。若读写均打开时,读端中途关闭,则写端随之关闭,无法正常写入。

匿名管道与命名管道的区别

  1. 匿名管道由pipe函数创建并打开;而命名管道使用mkfifo创建,用open打开(创建和打开接口分离);
  2. 匿名管道只能实现具有血缘关系的进程间进行通信;命名管道可以实现任意进程之间的通信。

命名管道的应用——使用Client&Server通信

由于命名管道可以让不同的进程进行通信,我们可以创建客户端Client.cc和服务器端Server.cc,让客户端发送消息给Server端,Server端从命名管道中读取消息并显式↓↓↓

Com.hpp

#include <iostream>
#include <cstdio>
#include <cstdlib>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>

#define FILE_NAME "sc-fifo"
#define MODE 0666

enum 
{
  FIFO_CREATE_ERR = 1,
  FIFO_DELETE_ERR,
  FIFO_OPEN_ERR
};

class Init
{
public:
  Init()
  {
    int n = mkfifo(FILE_NAME, MODE);
    if(n != 0)
    {
      perror("create error");
      exit(FIFO_CREATE_ERR);
    }
  }
  ~Init()
  {
    int n = unlink("sc-fifo");
    if(n != 0)
    {
      perror("delete error");
      exit(FIFO_DELETE_ERR);
    }
  }
};

Server.cc

#include "Com.hpp"

int main()
{
  Init init;
  char buffer[1024];

  int fd = open(FILE_NAME, O_RDONLY);
  if(fd < 0)
  {
    perror("open error");
    exit(FIFO_OPEN_ERR);
  }

  while(true)
  {
    read(fd, buffer, sizeof(buffer));
    std::cout << "Client say # " << buffer << std::endl;
  }
  return 0;
}

Client.cc

#include "Com.hpp"

int main()
{
  int fd = open(FILE_NAME, O_WRONLY);
  if(fd < 0)
  {
    perror("create error");
    exit(FIFO_OPEN_ERR);
  }
  std::string s;
  while(true)
  {
    std::cout << "send # ";
    std::cin >> s;
    write(fd, s.c_str(), s.size());
  }
  return 0;
}

在这里插入图片描述

🎈欢迎进入从浅学到熟知Linux专栏,查看更多文章。
如果上述内容有任何问题,欢迎在下方留言区指正b( ̄▽ ̄)d

  • 29
    点赞
  • 17
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在静态方法中使用JdbcTemplate需要注意以下几点: 1. 静态方法中无法直接使用Spring容器中的Bean,因为静态方法是类级别的,而Bean是实例级别的。因此需要手动获取JdbcTemplate实例,可以通过ApplicationContext获取JdbcTemplate实例,或者通过静态变量保存JdbcTemplate实例。 2. 在使用JdbcTemplate时,需要先创建一个JdbcTemplate实例,并设置数据源。数据源可以通过Spring容器注入,或者手动创建。在静态方法中,可以通过静态变量保存JdbcTemplate实例,避免重复创建。 3. 在使用JdbcTemplate操作数据库时,需要注意线程安全问题。JdbcTemplate是线程安全的,但是需要保证JdbcTemplate实例的线程安全,即在多线程环境中需要保证同一JdbcTemplate实例不会被并发访问。 下面是一个示例代码: ``` public class JdbcUtils { private static JdbcTemplate jdbcTemplate; public static void setDataSource(DataSource dataSource) { jdbcTemplate = new JdbcTemplate(dataSource); } public static void executeSql(String sql) { jdbcTemplate.execute(sql); } } ``` 在上面的代码中,我们通过静态变量保存了JdbcTemplate实例,并提供了一个静态方法setDataSource用于设置数据源。在使用JdbcTemplate时,我们可以直接调用静态方法executeSql执行SQL语句。需要注意的是,这里的executeSql方法是线程安全的,因为JdbcTemplate实例是共享的,并且JdbcTemplate本身是线程安全的。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值