管道
概念
概念:可跨进程的队列。实现进程之间的数据传输。种类:命名管道,匿名管道。命名管道:主要用于服务器。【双向传输】匿名管道:主要用于父子进程之间的数据传输。【单向传输】
![](https://img-blog.csdnimg.cn/img_convert/8da35bbcfb54f557202e766bec018289.jpeg)
命名管道
命名管道是一种有名字的管道,它可以被多个进程同时使用。命名管道在创建时必须指定一个唯一的名称,其他进程可以通过该名称来访问管道。命名管道使用CreateNamedPipe()函数创建,使用CreateFile()函数打开。命名管道适用于本地进程之间的通信和远程进程之间的通信。
匿名管道(父子进程之间的交互)
匿名管道是一种无名的管道,它只能被创建它的进程和它的子进程使用。匿名管道使用CreatePipe()函数创建,它返回两个句柄,一个是读句柄,一个是写句柄。这两个句柄只能被创建它们的进程和它的子进程使用。在Windows平台上,使用管道的过程与使用文件和套接字类似。进程可以使用WriteFile()函数将数据写入管道,然后使用ReadFile()函数从管道中读取数据。管道也可以使用ReadFile()函数异步读取数据,并使用WriteFile()函数异步写入数据。
创建匿名管道需要注意的事项
创建匿名管道,首先你要明白什么是管道. 管道你可以想象成一个管子.我们通过这个管子发送数据.如下图所示: 通过上图,我们就知道其实创建了两个管道,分别是父进程读取的管道以及子进程读取的管道,相应的子进程也可以对父进程读取的管道进行传输数据,父进程就可以读取了这段话可能难以理解,你可以这样想,我父进程读取子进程使用第一个管道,那么反过来正子进程的话也是使用第一个管道,因为子进程写,我们父进程才能读。
相关函数详解
BOOL CreatePipe(
PHANDLE hReadPipe,管道读取句柄
PHANDLE hWritePipe,管道写入矩形
LPSECURITY_ATTRIBUTES lpPipeAttributes, 安全属性结构体,决定是否能够继承管道
DWORD nSize,填NULL,使用默认缓冲区大小
);
hReadPipe:一个指向用于保存管道的读端句柄的指针。
hWritePipe:一个指向用于保存管道的写端句柄的指针。
lpPipeAttributes:用于设置管道的安全属性,一般为 NULL,表示使用默认安全属性。
nSize:用于设置管道的缓冲区大小,一般为 0,表示使用系统默认值。
功能:为各种函数创建对象提供安全设置。
例如CreateFile、CreatePipe、CreateProcess、RegCreateKeyEx或RegSaveKeyEx。
typedef struct _SECURITY_ATTRIBUTES {
DWORD nLength; //此结构体大小,必填
LPVOID lpSecurityDescriptor; //
BOOL bInheritHandle; //决定是否能够被继承
} SECURITY_ATTRIBUTES, *PSECURITY_ATTRIBUTES, *LPSECURITY_ATTRIBUTES;
读:`ReadFile`
写:`WriteFile`
获取句柄:`GetStdHandle`
HANDLE GetStdHandle(
DWORD nStdHandle // input, output, or error device
);
STD_INPUT_HANDLE 标准输入
STD_OUTPUT_HANDLE 标准输出
STD_ERROR_HANDLE 标准错误
查看管道是否有数据可读:`PeekNamedPipe`
BOOL PeekNamedPipe(
HANDLE hNamedPipe, // handle to pipe
LPVOID lpBuffer, // data buffer
DWORD nBufferSize, // size of data buffer
LPDWORD lpBytesRead, // number of bytes read
LPDWORD lpTotalBytesAvail, // number of bytes available
LPDWORD lpBytesLeftThisMessage // unread bytes
);
hNamedPipe:一个命名管道或匿名管道的句柄。
lpBuffer:一个指向缓冲区的指针,用于存储从管道中读取的数据。如果为 NULL,则不读取数据。
nBufferSize:缓冲区的大小。
lpBytesRead:一个指向变量的指针,用于存储已读取的字节数。
lpTotalBytesAvail:一个指向变量的指针,用于存储当前管道中可用的字节数。
lpBytesLeftThisMessage:一个指向变量的指针,用于存储当前消息中剩余的字节数。该参数只对消息式管道有效,对字节流式管道没有意义。
CreatePipe函数创建一个匿名管道并返回两个句柄:管道的读取句柄和管道的写入句柄。读取句柄对管道具有只读访问权限,并且写入句柄对管道具有只写访问权限。** 若要使用管道进行通信**,管道服务器必须将管道句柄传递到另一个进程。 通常,这是通过继承完成的;也就是说,进程允许子进程继承句柄。 该过程还可以使用 DuplicateHandle 函数复制管道句柄,并使用某种形式的进程间通信(例如 DDE 或共享内存)将其发送到无关的进程。管道服务器可以将读取句柄或写入句柄发送到管道客户端,具体情况取决于客户端是否应使用匿名管道来发送信息或接收信息。 若要从管道读取,请在调用 ReadFile 函数时使用管道的读取句柄。 当另一个进程写入管道时, ReadFile 调用返回。 如果管道的所有写入句柄已关闭,或在完成读取操作之前发生错误,则 ReadFile 调用也可以返回。若要写入管道,请在调用 WriteFile 函数时使用管道的写入句柄。 WriteFile 调用在向管道写入指定的字节数或发生错误之前,不会返回。 如果管道缓冲区已满,并且有更多的字节需要写入,则 WriteFile 不会返回,直到另一个进程从管道中读取内容,从而提供更多的缓冲区空间。 管道服务器在调用 CreatePipe时指定管道的缓冲区大小。匿名管道不支持异步 (重叠) 读和写操作。 这意味着不能将 ReadFileEx 和 WriteFileEx 函数与匿名管道一起使用。 此外,当将这些函数与匿名管道一起使用时,将忽略 ReadFile和 WriteFile的 lpOverlapped 参数。匿名管道存在,直到所有管道句柄都已关闭。 进程可以使用 CloseHandle 函数关闭其管道句柄。** 进程终止时,所有管道句柄也会关闭。**使用具有唯一名称的命名管道实现匿名管道。 因此,通常可以将句柄传递给需要命名管道的句柄的函数。
界面效果
实例代码
//创建管道
void CMyTestPipDlg::OnClickedBtnCreate()
{
SECURITY_ATTRIBUTES sa = {};
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
if (!CreatePipe(&m_hRead, &m_hWrite, &sa, 0))
{
AfxMessageBox("管道创建失败");
}
}
//写按钮
void CMyTestPipDlg::OnClickedBtnWrite()
{
CString str;
GetDlgItemText(EDT_WRITE, str);
DWORD dwBytesWrited = 0;
if (!WriteFile(m_hWrite, str.GetBuffer(), str.GetLength(), &dwBytesWrited, NULL))
{
AfxMessageBox("管道写入失败");
}
}
//读按钮
void CMyTestPipDlg::OnClickedBtnRead()
{
//检查管道中是否有数据可读
DWORD dwBytesAvail = 0;
if (!PeekNamedPipe(m_hRead, NULL, 0, NULL, &dwBytesAvail, NULL))
{
return;
}
//如果管道中有数据,则读出
if (dwBytesAvail > 0)
{
CString str;
DWORD dwBytesRead = 0;
if (!ReadFile(
m_hRead,
str.GetBufferSetLength(dwBytesAvail),
dwBytesAvail,
&dwBytesRead,
NULL))
{
AfxMessageBox("管道读取失败");
}
str.ReleaseBuffer(dwBytesRead);
SetDlgItemText(EDT_READ, str);
}
}
管道读取被阻塞的问题
在父进程读取管道的操作之前,先往管道中写入一些数据,这样可以确保在读取操作执行时,管道中已经有数据可以读取。使用非阻塞读取操作,这样可以避免读取操作被阻塞。可以使用 Windows API 函数 PeekNamedPipe() 来查询管道中是否有数据可以读取。如果返回值为 0,表示管道中没有数据可以读取;如果返回值不为 0,则可以调用 ReadFile() 函数来读取管道中的数据。在父进程中创建一个线程,让这个线程负责读取管道中的数据。这样可以避免读取操作阻塞主线程,从而避免程序无响应的问题。可以使用 Windows API 函数 CreateThread() 来创建线程,然后在线程中调用 ReadFile() 函数来读取管道中的数据。同时,主线程可以继续执行其他操作,不必等待管道中的数据读取完成。
父子进程之间的匿名管道通信
获取标准输入输出的句柄继承句柄创建管道的时候;创建子窗口的时候;创建进程时需要STARTUPINFO指定句柄
1、GetStdHandle
GetStdHandle函数获取标准输入、标准输出或标准错误设备的句柄。 参数:指定要为其返回句柄的标准设备。该参数可以是以下值之一STD_INPUT_HANDLE——标准输入处理STD_OUTPUT_HANDLE——标准输出处理STD_ERROR_HANDLE——标准错误处理
2、设置安全属性(子进程继承父进程)
两个地方的继承属性设置为TRUE:创建管道的时候;创建子窗口的时候;
//创建管道
void CMFCTestDlg::OnBnClickedCreate()
{
SECURITY_ATTRIBUTES sa = {};
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
if (!CreatePipe(&m_hRead, &m_hWrite, &sa, 0))
{
AfxMessageBox("管道创建失败");
}
}
3、si 属性
![](https://img-blog.csdnimg.cn/img_convert/4d516f0d970c5990a22810f289f03d85.png#crop=0&crop=0&crop=1&crop=1&id=uz9My&originHeight=744&originWidth=1599&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=#averageHue=#e2e0df&id=XRRhP&originHeight=744&originWidth=1599&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
结构体中下图中三个参数:![](https://img-blog.csdnimg.cn/img_convert/c5b66f5117f26dba9ff8a38b94fae460.png#crop=0&crop=0&crop=1&crop=1&id=vbUnK&originHeight=902&originWidth=1631&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=#averageHue=#e3e1e0&id=H6ore&originHeight=902&originWidth=1631&originalType=binary&ratio=1&rotation=0&showTitle=false&status=done&style=none&title=)
创建使用
A:创建子进程时,将管道句柄传入子进程B:
1. 获取管道句柄(从结构体中获取)
2. 判断管道内是否有数据
PS:当没有管道句柄的时候,默认拿取的值对应IO的句柄,stdin,stdout,stderr。
界面效果:
父进程
子进程
源码:
父进程源码![image.png](https://img-blog.csdnimg.cn/img_convert/9ac87730c905f7e826b9bb66974f210e.png#averageHue=#c6eccb&clientId=u3d1658bb-d4c4-4&from=paste&height=551&id=ucbb19e66&name=image.png&originHeight=826&originWidth=1511&originalType=binary&ratio=1.5&rotation=0&showTitle=false&size=189562&status=done&style=none&taskId=ue8fc5a7b-7e63-41e5-9b7d-906c7f518c0&title=&width=1007.3333333333334)
void CMFCTestDlg::OnBnClickedCreate()
{
SECURITY_ATTRIBUTES sa = {};
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
if (CreatePipe(&m_hChildRead, &m_hParentWrite, &sa, 0))
{
AfxMessageBox("创建管道成功");
}
else
{
AfxMessageBox("创建失败");
}
if (CreatePipe(&m_hParentRead, &m_hChildWrite, &sa, 0))
{
AfxMessageBox("创建管道成功");
}
else
{
AfxMessageBox("创建失败");
}
//创建子进程
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES; //使用句柄
si.hStdInput = m_hChildRead;
si.hStdOutput = m_hChildWrite;
si.hStdError = m_hChildWrite;
ZeroMemory(&pi, sizeof(pi));
// Start the child process.
if (!CreateProcess(NULL, // No module name (use command line).
"MyChildProcess", // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
TRUE, // Set handle inheritance to FALSE.
0, // No creation flags.
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi) // Pointer to PROCESS_INFORMATION structure.
)
{
AfxMessageBox("CreateProcess failed.");
}
else
{
AfxMessageBox("CreateProcess sucess.");
}
// Close process and thread handles.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
void CMFCTestDlg::OnBnClickedRead()
{
//判断管道中是否有数据可读
DWORD dwBytesAvail = 0;
if (PeekNamedPipe(m_hParentRead, NULL, 0, NULL, &dwBytesAvail, NULL))
{
if (dwBytesAvail > 0)
{
//从管道读取数据
char szBuff[MAXBYTE] = {};
DWORD dwBytesReaded = 0;
BOOL bRet = ReadFile(m_hParentRead, szBuff, dwBytesAvail, &dwBytesReaded, NULL);
if (!bRet)
{
AfxMessageBox("读取文件失败");
}
//显示到界面
SetDlgItemText(EDT_SHOW, szBuff);
}
}
}
void CMFCTestDlg::OnBnClickedWrite()
{
//获取数据
CString strData;
this->GetDlgItemText(EDT_DATA, strData);
//从管道写入数据
DWORD dwBytesReaded = 0;
BOOL bRet = WriteFile(m_hParentWrite, strData.GetBuffer(), strData.GetLength(), &dwBytesReaded, NULL);
if (!bRet)
{
AfxMessageBox("写入文件失败");
}
}
子进程源码
void CMFCTestDlg::OnBnClickedRead()
{
//获取父进程句柄 GetstdHandle
HANDLE hRead = GetStdHandle(STD_INPUT_HANDLE);
//判断管道中是否有数据可读
DWORD dwBytesAvail = 0;
if (PeekNamedPipe(hRead, NULL, 0, NULL, &dwBytesAvail, NULL))
{
if (dwBytesAvail > 0)
{
//从管道读取数据
char szBuff[MAXBYTE] = {};
DWORD dwBytesReaded = 0;
BOOL bRet = ReadFile(hRead, szBuff, dwBytesAvail, &dwBytesReaded, NULL);
if (!bRet)
{
AfxMessageBox("读取文件失败");
}
//显示到界面
SetDlgItemText(EDT_SHOW, szBuff);
}
}
}
void CMFCTestDlg::OnBnClickedWrite()
{
//获取父进程句柄 GetstdHandle
HANDLE hWrite = GetStdHandle(STD_OUTPUT_HANDLE);
//获取数据
CString strData;
this->GetDlgItemText(EDT_DATA, strData);
//从管道写入数据
DWORD dwBytesReaded = 0;
BOOL bRet = WriteFile(hWrite, strData.GetBuffer(), strData.GetLength(), &dwBytesReaded, NULL);
if (!bRet)
{
AfxMessageBox("写入文件失败");
}
}
附录
管道的创建与使用
1. 创建管道,并传给子进程
void CParentDlg::OnBnClickedButton1()
{
//管道的读写句柄,一般作为成员变量
HANDLE m_hRead = NULL;
HANDLE m_hWrite = NULL;
//创建管道
SECURITY_ATTRIBUTES sa = {}; //管道安全属性结构体
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE; //指定该管道句柄可以继承
BOOL bRet = ::CreatePipe(&m_hRead, &m_hWrite, &sa, 0); //使用默认缓冲区大小
if (!bRet)
{
AfxMessageBox("创建管道失败");
return;
}
//创建子进程
STARTUPINFO si = {};
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES; //指明标准输入输出句柄
si.hStdInput = m_hRead; //给将继承的输入句柄赋值
PROCESS_INFORMATION pi; //
ZeroMemory(&pi, sizeof(pi)); //等同于 PROCESS_INFORMATION pi = {};
// Start the child process.
if (!CreateProcess(NULL, // No module name (use command line).
"Child.exe", // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
TRUE, // Set handle inheritance to FALSE.
0, // No creation flags.
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi) // Pointer to PROCESS_INFORMATION structure.
)
{
AfxMessageBox("CreateProcess failed.");
return;
}
CloseHandle(pi.hProcess); //释放进程句柄
CloseHandle(pi.hThread); //释放线程句柄
}
====================== 向管道写入数据【WriteFile】 =====================
3. 通过写入句柄向管道写入数据
void CParentDlg::OnBnClickedButton2()
{
CString strBuf;
GetDlgItemText(EDT_WRITE, strBuf);
BOOL bRet = WriteFile(m_hWrite, strBuf.GetBuffer(), strBuf.GetLength(), NULL, NULL);
if (!bRet)
{
AfxMessageBox("写入管道失败");
}
}
=======================子进程的读取和写入 ======================
void CChildDlg::OnBnClickedButton1()
{
//1.获取管道读取或泽写入句柄
HANDLE hInput = ::GetStdHandle(STD_INPUT_HANDLE);
DWORD dwBytesAvail = 0;
//2. 检查管道内是否有数据。
BOOL bRet = ::PeekNamedPipe(hInput, NULL, 0, NULL, &dwBytesAvail, NULL);
if (!bRet)
{
AfxMessageBox("无法查看管道剩余数据");
return;
}
//3. 管道中有数据再读
if (dwBytesAvail > 0)
{
CString strBuf;
DWORD dwBytesToRead = 0;
BOOL bRet = ::ReadFile(
hInput,
strBuf.GetBufferSetLength(MAXBYTE),
MAXBYTE,
&dwBytesToRead,
NULL);
if (!bRet)
{
AfxMessageBox("读取数据失败");
return;
}
strBuf.ReleaseBuffer(dwBytesToRead);
SetDlgItemText(EDT_READ, strBuf);
}
}
模拟cmd
界面效果 源码:![image.png](https://img-blog.csdnimg.cn/img_convert/4b7a09fc85b855964b2bf2f4223f7e7c.png#averageHue=#c6eccb&clientId=u3d1658bb-d4c4-4&from=paste&height=541&id=ucf6ac1d8&name=image.png&originHeight=812&originWidth=1622&originalType=binary&ratio=1.5&rotation=0&showTitle=false&size=183086&status=done&style=none&taskId=u08a8d07c-0980-4448-9469-ff64a785482&title=&width=1081.3333333333333)
void CMFCTestDlg::OnBnClickedCreate()
{
SECURITY_ATTRIBUTES sa = {};
sa.nLength = sizeof(sa);
sa.bInheritHandle = TRUE;
if (!CreatePipe(&m_hChildRead, &m_hParentWrite, &sa, 0))
{
AfxMessageBox("创建失败");
}
if (!CreatePipe(&m_hParentRead, &m_hChildWrite, &sa, 0))
{
AfxMessageBox("创建失败");
}
//创建子进程
STARTUPINFO si;
PROCESS_INFORMATION pi;
ZeroMemory(&si, sizeof(si));
si.cb = sizeof(si);
si.dwFlags = STARTF_USESTDHANDLES; //使用句柄
si.hStdInput = m_hChildRead;
si.hStdOutput = m_hChildWrite;
si.hStdError = m_hChildWrite;
ZeroMemory(&pi, sizeof(pi));
// Start the child process.
if (!CreateProcess(NULL, // No module name (use command line).
"cmd.exe", // Command line.
NULL, // Process handle not inheritable.
NULL, // Thread handle not inheritable.
TRUE, // Set handle inheritance to FALSE.
CREATE_NO_WINDOW, // No creation flags.
NULL, // Use parent's environment block.
NULL, // Use parent's starting directory.
&si, // Pointer to STARTUPINFO structure.
&pi) // Pointer to PROCESS_INFORMATION structure.
)
{
AfxMessageBox("CreateProcess failed.");
}
// Close process and thread handles.
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
void CMFCTestDlg::OnBnClickedRead()
{
//判断管道中是否有数据可读
DWORD dwBytesAvail = 0;
if (PeekNamedPipe(m_hParentRead, NULL, 0, NULL, &dwBytesAvail, NULL))
{
if (dwBytesAvail > 0)
{
//从管道读取数据
char szBuff[MAXBYTE] = {};
DWORD dwBytesReaded = 0;
BOOL bRet = ReadFile(m_hParentRead, szBuff, sizeof(szBuff)-1, &dwBytesReaded, NULL);
if (!bRet)
{
AfxMessageBox("读取文件失败");
}
//显示到界面
m_strOutData += szBuff;
SetDlgItemText(EDT_SHOW, m_strOutData);
}
}
}
void CMFCTestDlg::OnBnClickedWrite()
{
//获取数据
CString strData;
this->GetDlgItemText(EDT_DATA, strData);
strData += "\r\n";
//从管道写入数据
DWORD dwBytesReaded = 0;
BOOL bRet = WriteFile(m_hParentWrite, strData.GetBuffer(), strData.GetLength(), &dwBytesReaded, NULL);
if (!bRet)
{
AfxMessageBox("写入文件失败");
}
}
效果:
作业:
1、cmd 窗口版,自动读取,(高亮,提示)
|