文章目录
1.管道小总结
linux-manualshouce
在Linux中,manual手册的编号用于区分手册的不同部分。这些编号通常用于man命令中,以便用户可以指定要查看的手册部分。man命令的编号对应关系如下:
用户命令(User Commands):通常包括可执行程序或shell命令。
系统调用(System Calls):由内核提供的函数。
库调用(Library Calls):程序库中的函数。
特殊文件(Special Files):通常位于/dev目录下的设备文件。
文件格式和约定(File Formats and Conventions):例如/etc/passwd等配置文件的格式。
游戏(Games)。
杂项(Miscellaneous):包括宏包和约定等其他内容。
系统管理命令(System Administration Commands and Daemons)。
此外,还有第9个部分,通常用于其他内容,比如内核例行程序(Kernel Routines)。
管道读写规则
- 当没有数据可读时
O_NONBLOCK disable:read调用阻塞,即进程暂停执行,一直等到有数据为止。
O_NONBLOCK enable:read调用返回-1,errno值为EAGAIN。 - 当管道满的时候
O_NONBLOCK disable: write调用阻塞,直到有进程读走数据
O_NONBLOCK enable:调用返回-1,errno值为EAGAIN - 如果所有管道写端对应的文件描述符被关闭,则read返回0
- 如果所有管道读端对应的文件描述符被关闭,则write操作会产生信号SIGPIPE,进而可能导致write进程退出
- 当要写入的数据量不大于PIPE_BUF时,linux将保证写入的原子性。
- 当要写入的数据量大于PIPE_BUF时,linux将不再保证写入的原子性。
原子性
要么做要么不做。无中间状态。
多执行流下数据出现并发访问时考虑。
2.命名管道
2.1认识命名管道
- 进程A打卡了文件file,进程B再去打开file时,OS识别到file已打开,则不再创建对应的file结构体,而是直接指向已有的file结构体。【OS中一般都会存在n多个进程,且存在像许多进程打开同一个文件的情况,如果对于已经打开的文件,另外的进程再去打开他时区创建一个新的file结构体,这无疑是巨大的时空浪费】
- 命名管道无疑是这样的一个存在,首先他是一个文件,其次他不用将数据刷新到磁盘。
- 该文件一定在系统路径中(路径具有唯一性)。该文件有名字,可以被打开,但是不会将内存数据刷新到磁盘。
- 至此,两个毫不相干的进程通过命名管道文件的路径看到同一份资源。【匿名管道通常应用于有血缘关系的进程,这也是匿名管道的一个缺点】
2.2命名管道的应用小场景
创建命名管道
场景1
场景2:输出重定向
条件编译
#ifndef _COMMON_H_
#define _COMMON_H_
#endif
这三句代码是C或C++编程语言中常用的预处理器指令,它们通常用于头文件中以防止头文件内容的多次包含(多重定义问题)。我来详细解释每句代码的含义:
#ifndef COMMON_H
这是一个条件编译指令,它检查是否定义了名为_COMMON_H_的宏(macro)。如果_COMMON_H_没有被定义,那么紧随其后的代码(直到遇到#endif或另一个条件编译指令)会被编译器包含(或编译)。
- #define COMMON_H
这行代码定义了一个名为_COMMON_H_的宏。一旦这个宏被定义,再次遇到#ifndef _COMMON_H_时,由于_COMMON_H_已经被定义,所以其后的代码不会被再次包含。
- #endif
这是一个结束标记,表示#ifndef条件编译指令的结束。
将这三行代码放在头文件common.h的开始处,可以确保无论这个头文件被包含多少次,其内部的内容都只会被编译器编译一次。这在处理循环包含或者复杂项目结构时非常有用,因为你可以避免函数或变量被多次定义,从而避免编译错误。
这样,当common.h首次被包含时,其内容会被编译;如果再次被包含,由于_COMMON_H_已经被定义,其内容不会被再次编译。
unlink()删除文件
rm和unlink的异同
在Linux中,rm和unlink都用于删除文件或目录,但它们在使用方式和功能上有一些重要的区别。
- 命令形式和使用方式:
rm 是一个更高级的、用户友好的命令,用于删除文件或目录。它可以处理多种复杂的情况,例如删除目录(使用 -r 或 -R 选项)或强制删除文件(使用 -f 选项)。
unlink 是一个较低级的系统调用,通常用于在编程中删除文件。在shell中,你可以使用 unlink 命令来删除文件,但它没有 rm 那么多的选项和功能。
- 功能和行为:
在功能上,rm 和 unlink 基本上是一样的:它们都删除了指定的文件或目录。然而,由于 rm 提供了更多的选项,它可以更灵活地处理各种情况。
在行为上,rm 和 unlink 都减少了文件的链接计数。如果一个文件只有一个链接(即它是唯一被引用的),那么当这个链接被 rm 或 unlink 删除后,该文件占用的磁盘空间也会被释放。但是,如果一个文件有多个链接,那么删除其中一个链接只会减少链接计数,而不会释放磁盘空间。
- 错误处理:
rm 在删除不存在的文件或目录时,会输出一个错误消息。然而,如果你使用 -f 选项,rm 会静默地忽略这些错误。
unlink 在遇到错误时,通常会返回一个错误代码,但具体的行为可能取决于你的shell和如何调用 unlink。
总的来说,rm 和 unlink 在功能上相似,但在使用方式和行为上有所不同。对于日常的文件删除任务,rm 通常是一个更好的选择,因为它提供了更多的选项和更友好的错误处理。而在编程中,你可能需要更直接地控制文件删除的过程,这时 unlink 可能更合适。
2.3模拟命名管道
1.Lod.hpp
cpp
#ifndef _LOG_H_
#define _LOG_H_
#include <iostream>
#include <ctime>
#define Debug 0
#define Notice 1
#define Warning 2
#define Error 3
const std::string tip[] ={
"Debug",
"Notice",
"Warning",
"Error"};
std::ostream &Log(std::string message, int option)
{
// 获取时间戳 time_t timestamp; time(timestamp);
time_t timestamp = time(nullptr);
if (timestamp == std::time_t(-1))
{
std::cerr << "获取时间失败" << std::endl;
exit(1);
}
// 获取格式化时间 tm *localtime(const time_t *__timer)
tm *timeinfo = std::localtime(×tamp);
std::cout << " | "
<< 1900 + timeinfo->tm_year << "-"
<< 1 + timeinfo->tm_mon << "-"
<< timeinfo->tm_mday << " "
<< timeinfo->tm_hour << ":"
<< timeinfo->tm_min << ":"
<< timeinfo->tm_sec
<< " | "
<< tip[option]
<< " | "
<< message;
return std::cout;
}
#endif
2.common.hpp
cpp
#ifndef _COMMON_H_
#define _COMMON_H_
#include <iostream>
#include <string>
#include <cstdio>
#include <cstring>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <iomanip>
#include "Log.hpp"
using namespace std;
#define MODE 0666
#define SIZE 128
string ipcPath = "./fifo.ipc";
#endif
3.server.cxx
cpp
#include "common.hpp"
#include <sys/wait.h>
static void IpcWithClient(int fd)
{
char buffer[SIZE];
while (true)
{
memset(buffer, '\0', sizeof(buffer));
// 【OS写的时候不写\0 这里读的时候自然没有\0 我们空一个以免有需要在末尾加0】
// 当然 上面的memset已经对所有单元都加了\0 这里只是通用的默认规范
ssize_t s = read(fd, buffer, sizeof(buffer) - 1);
if (s > 0) // read success
{
cout << "[" << getpid() << "] "
<< "client say:> " << buffer << endl;
}
else if (s == 0) // end of file
{
cerr << "[" << getpid() << "] "
<< "read end of file, clien quit, server quit too!" << endl;
break;
}
else // read error
{
perror("IpcWithClient::read");
break;
}
}
}
int main()
{
// 1. 创建管道文件 int mkfifo(const char *__path, mode_t __mode)
if (mkfifo(ipcPath.c_str(), MODE) < 0) //成功返回0 失败返回-1
{
perror("server::mkfifo");
exit(1);
}
Log("创建管道文件成功", Debug) << " step 1" << endl;
// 2. 打开管道文件
int fd = open(ipcPath.c_str(), O_RDONLY);
if (fd < 0)
{
perror("server::open");
exit(2);
}
Log("打开管道文件成功", Debug) << " step 2" << endl;
// 3. 开始进行IPC 服务端创建子进程去与客户端进行ipc
int serverChildNum = 3;
for (int i = 0; i < serverChildNum; i++)
{
pid_t id = fork();
if (id == 0)
{
IpcWithClient(fd);
exit(1);
}
}
//阻塞式等待子进程 获取子进程退出状态 回收子进程
for (int i = 0; i < serverChildNum; i++)
{
waitpid(-1, nullptr, 0);
}
// 4. 关闭文件
close(fd);
Log("关闭管道文件成功", Debug) << " step 3" << endl;
unlink(ipcPath.c_str()); // int unlink(const char *__name)
Log("删除管道文件成功", Debug) << " step 4" << endl;
return 0;
}
4.client.cxx
cpp
#include "common.hpp"
int main()
{
// 1. 获取管道文件 创建文件由server负责
// 这里不加O_TRUNC 文件不存在client也不创建 失败是server的事情
int fd = open(ipcPath.c_str(), O_WRONLY);
if (fd < 0)
{
perror("client::open");
exit(1);
}
// 2. 进行ipc
string buffer;
while (true)
{
cout << "Please input information:> ";
//istream& getline<char, char_traits<char>, allocator<char>>(istream& __is, string& __str)
std::getline(std::cin, buffer);
write(fd, buffer.c_str(), buffer.size());
}
// 3. 关闭
close(fd);
return 0;
}
3.管道代码总结
- 模拟匿名管道: 父进程调用pipe() 创建匿名管道 记录pipefd 父子进程各自关闭不用的pipefd 之后父进程写到写端 子进程从读端读
- 模拟进程池: 父进程创建N个子进程 对每一个子进程之开启读端 父进程只开启写端 父进程随机选择一个子进程去执行它写到写端的命令 子进程阻塞等待获取从读端的命令 直到获取成功 否则一直阻塞等待
- 模拟命名管道:服务端进程创建命名管道 让子进程从fd里读信息后输出到显示器 客户端打开彼管道文件 向该fd写信息