1. 进程间通信概述
进程是一个独立的资源分配单元,不同进程之间的资源是独立的,没有关联,不能在一个进程中直接访问另一个进程的资源。进程不是孤立的,不同的进程需要进行信息的交互和状态的传递等,因此需要进程间通信。
1.1 进程间通信功能
(1)数据传输:一个进程需要将它的数据发送给另一个进程。
(2)资源共享:多个进程之间共享同样的资源。
(3)通知事件:一个进程需要向另一个或一组进程发送消息,通知它们发生了某种事件。
(4)进程控制:有些进程希望完全控制另一个进程的执行,此时控制进程希望能够拦截另一个进程的所有操作,并能够及时知道它的状态改变
1.2 主要进程间通信的通信机制
2. 管道
2.1 管道特点
管道(pipe)又称无名管道,是一种特殊类型的文件,在应用层体现为两个打开的文件描述符
(1)半双工,数据在同一时刻只能在一个方向上流动
(2)管道不是普通的文件,不属于某个文件系统,其只存在于内存中
(3)管道没有名字,只能在具有公共祖先的进程之间使用
(4)管道的缓冲区大小是有限的,在linux中,该缓冲区的大小固定为4k
2.2 管道数据传输
2.3 函数
#include <unistd.h> int pipe(int filedes[2]); 功能:经由参数filedes返回两个文件描述符 参数: filedes为int型数组的首地址,其存放了管道的文件描述符fd[0]、fd[1]。 filedes[0]为读而打开,filedes[1]为写而打开 管道,filedes[0]的输出是filedes[1]的输入。 返回值:成功:返回 0 失败:返回-1
2.4 例子
#include "intf.h" #include <iostream> #include <string.h> using namespace std; #define BUFFSIZE 1024 int main(int argc, char *argv[]) { int ParentFd[2]; cout << "程序开始" << endl; if(pipe(ParentFd) < 0) { perror("创建管道失败"); } printf("创建管道成功 "); int pid = fork(); if(pid < 0) { perror("创建进程失败"); } else if(pid == 0) { printf("子进程: "); close(ParentFd[0]); cout << "请输入要写入的字符" << endl; string s; while(getline(cin, s)) { write(ParentFd[1], s.c_str(), s.length()); if(s == "quit") { exit(0); } } } else { cout << "父进程:" << endl; close(ParentFd[1]); while(1) { char msg[BUFFSIZE] = {0}; read(ParentFd[0], msg, BUFFSIZE); cout << "父进程显示:" << msg << endl; if(!strcmp(msg, "quit")) { exit(0); } } } cout << "程序结束" << endl; return 0; }
3. 命名管道
命名管道(FIFO)和管道(PIPE)基本相同,FOFO有名字,不同的进程可以通过该命名管道进行通信
3.1 函数介绍
(1)access
access():判断是否具有存取文件的权限
相关函数
stat,open,chmod,chown,setuid,setgid
表头文件
#include<unistd.h>
定义函数
int access(const char * pathname, int mode);
函数说明
access()会检查是否可以读/写某一已存在的文件。参数mode有几种情况组合, R_OK,W_OK,X_OK 和F_OK。R_OK,W_OK与X_OK用来检查文件是否具有读取、写入和执行的权限。F_OK则是用来判断该文件是否存在。由于access()只作权限的核查,并不理会文件形态或文件内容,因此,如果一目录表示为“可写入”,表示可以在该目录中建立新文件等操作,而非意味此目录可以被当做文件处理。例如,你会发现DOS的文件都具有“可执行”权限,但用execve()执行时则会失败。
返回值
若所有欲查核的权限都通过了检查则返回0值,表示成功,只要有一权限被禁止则返回-1。
错误代码
EACCESS 参数pathname 所指定的文件不符合所要求测试的权限。
EROFS 欲测试写入权限的文件存在于只读文件系统内。
EFAULT 参数pathname指针超出可存取内存空间。
EINVAL 参数mode 不正确。
ENAMETOOLONG 参数pathname太长。
ENOTDIR 参数pathname为一目录。
ENOMEM 核心内存不足
ELOOP 参数pathname有过多符号连接问题。
EIO I/O 存取错误。
附加说明
使用access()作用户认证方面的判断要特别小心,例如在access()后再做open()的空文件可能会造成系统安全上的问题。
范例
#include<unistd.h> int main() { if (access(“/etc/passwd”,R_OK) = =0) printf(“/etc/passwd can be read ”); } 执行 /etc/passwd can be read
(2)mkfifo
mkfifo(建立实名管道)
相关函数
pipe,popen,open,umask
表头文件
#include<sys/types.h>
#include<sys/stat.h>
定义函数
int mkfifo(const char * pathname,mode_t mode);
函数说明
mkfifo()会依参数pathname建立特殊的FIFO文件,该文件必须不存在,而参数mode为该文件的权限(mode%~umask),因此 umask值也会影响到FIFO文件的权限。Mkfifo()建立的FIFO文件其他进程都可以用读写一般文件的方式存取。当使用open()来打开 FIFO文件时,O_NONBLOCK旗标会有影响
1、当使用O_NONBLOCK 旗标时,打开FIFO 文件来读取的操作会立刻返回,但是若还没有其他进程打开FIFO 文件来读取,则写入的操作会返回ENXIO 错误代码。
2、没有使用O_NONBLOCK 旗标时,打开FIFO 来读取的操作会等到其他进程打开FIFO文件来写入才正常返回。同样地,打开FIFO文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回。
返回值
若成功则返回0,否则返回-1,错误原因存于errno中。
错误代码
EACCESS 参数pathname所指定的目录路径无可执行的权限
EEXIST 参数pathname所指定的文件已存在。
ENAMETOOLONG 参数pathname的路径名称太长。
ENOENT 参数pathname包含的目录不存在
ENOSPC 文件系统的剩余空间不足
ENOTDIR 参数pathname路径中的目录存在但却非真正的目录。
EROFS 参数pathname指定的文件存在于只读文件系统内。
4. 命名管道实现服务器和客户端双向通信
4.1 封装命令管道类NamedPipe
enum OpenMode { ReadOnly = 1, WriteOnly, READWRITE }; class NamedPipe { public: // 每次从管道最多读取的字节数 static const int PIPE_BUFF = 1024; NamedPipe(); NamedPipe(const string& strPath, OpenMode mode); ~NamedPipe(); string read(int nSize); string read(); void write(const string& content); private: int m_fd; int m_mode; };
NamedPipe实现
NamedPipe::NamedPipe() { } NamedPipe::NamedPipe(const string& strPath, OpenMode mode) :m_fd(-1), m_mode(mode) { int nOpenMode = 0; if(mode == ReadOnly) { nOpenMode = O_RDONLY; } else if(mode == WriteOnly) { nOpenMode = O_WRONLY; } else if(mode == READWRITE) { nOpenMode = O_RDWR; } cout << "检查管道:" << endl; if(access(strPath.c_str(), F_OK) < 0) { cout << "管道不存在创建管道" << endl; int ret = mkfifo(strPath.c_str(), 0777 ); if(ret < 0) { cout << "无法创建管道" << endl; } else{ cout << "创建管道成功" << endl; } } cout << "管道存在打开管道" << endl; m_fd = open(strPath.c_str(), nOpenMode); } NamedPipe::~NamedPipe() { if(m_fd && m_fd != -1) { close(m_fd); } } string NamedPipe::read(int nSize) { if(m_fd == -1) { cout << "打开文件失败!" << endl; } char buff[PIPE_BUFF] = {0}; int nReadSize = 0; string strContent = ""; do{ int nBytesToRead = 0; if(nReadSize + PIPE_BUFF < nSize) { nBytesToRead = PIPE_BUFF; } else { nBytesToRead = nSize - nReadSize; } nBytesToRead = ::read(m_fd, buff, nBytesToRead); if(nBytesToRead == -1) { cout << "读取失败" << endl; } nReadSize += nBytesToRead; strContent += string(buff, nBytesToRead); }while(nReadSize < nSize); return strContent; } string NamedPipe::read() { if(m_fd == -1) { cout << "打开文件失败!" << endl; } char buff[PIPE_BUFF] = {0}; int nBytesToRead = ::read(m_fd, buff, PIPE_BUFF); if(nBytesToRead == -1) { cout << "PipeReadException" << endl; } return string(buff); } void NamedPipe::write(const string& content) { if(m_fd == -1) { cout << "打开文件失败!" << endl; } int nWriteBytes = ::write(m_fd, content.c_str(), content.length()); if(nWriteBytes == -1) { cout << "PipeWriteException" << endl; } }
4.2 服务器和客户端实现
#include "namepipe.h" #define SERVER_W "serverWrite" #define SERVER_R "serverRead" #define RED_SIZE 1024 int main() { NamedPipe ReadPipe(SERVER_R, READWRITE); NamedPipe WritePipe(SERVER_W, READWRITE); int pid = fork(); if(pid < 0) { cout << "创建服务器子进程失败!" << endl; } else if(pid == 0) { cout << "服务器子进程创建成功,用于读客户端信息" << endl; string msg = ""; while(1) { msg = ReadPipe.read(); cout << msg.length() << endl; if(msg.length() > 0) { cout << "服务器接收到信息:" << msg << endl; if(msg == "EOF") { cout << "客户端请求断开连接" << endl; break; } } } cout << "服务器子进程没有资源可读,断开连接" << endl; exit(0); } else{ cout << "服务器父进程用于发送内容给客户端" << endl; string msg = ""; while(getline(cin, msg)) { WritePipe.write(msg); if(msg == "EOF") { cout << "服务器请求断开连接"<< endl; break; } } wait(NULL); } return 0; }
#include "namepipe.h" #define SERVER_W "serverWrite" #define SERVER_R "serverRead" #define RED_SIZE 64 int main() { NamedPipe ReadPipe(SERVER_W, READWRITE); NamedPipe WritePipe(SERVER_R, READWRITE); int pid = fork(); if(pid < 0) { cout << "创建客户端子进程失败!" << endl; } else if(pid == 0) { cout << "客户端子进程创建成功,用于写客户端信息" << endl; string msg = ""; while(getline(cin, msg)) { WritePipe.write(msg); if(msg == "EOF") { cout << "客户端子进程请求断开连接"<< endl; break; } } cout << "服客户端子进程断开连接" << endl; exit(0); } else{ cout << "客户端父进程用于读取服务器内容" << endl; string msg = ""; while(1) { msg = ReadPipe.read(); if(msg.length() > 0) { cout << "客户端父进程接收到信息:" << msg << endl; if(msg == "EOF") { cout << "服务器请求断开连接" << endl; break; } } } wait(NULL); } return 0; }