09

您所在的位置:网站首页 readfile阻塞 09

09

2023-03-13 19:10| 来源: 网络整理| 查看: 265

管道

概念

概念:可跨进程的队列。实现进程之间的数据传输。种类:命名管道,匿名管道。命名管道:主要用于服务器。【双向传输】匿名管道:主要用于父子进程之间的数据传输。【单向传输】

命名管道

命名管道是一种有名字的管道,它可以被多个进程同时使用。命名管道在创建时必须指定一个唯一的名称,其他进程可以通过该名称来访问管道。命名管道使用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 函数关闭其管道句柄。** 进程终止时,所有管道句柄也会关闭。**使用具有唯一名称的命名管道实现匿名管道。 因此,通常可以将句柄传递给需要命名管道的句柄的函数。

界面效果

image.png

实例代码 //创建管道 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("管道创建失败"); } }

image.png

3、si 属性

结构体中下图中三个参数:image.png

创建使用

A:创建子进程时,将管道句柄传入子进程B:

1. 获取管道句柄(从结构体中获取) 2. 判断管道内是否有数据

PS:当没有管道句柄的时候,默认拿取的值对应IO的句柄,stdin,stdout,stderr。

界面效果:

父进程

image.png

子进程

image.png

源码:

父进程源码image.png

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源码:image.png

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("写入文件失败"); } }

效果:image.png

作业:

1、cmd 窗口版,自动读取,(高亮,提示)



【本文地址】


今日新闻


推荐新闻


CopyRight 2018-2019 办公设备维修网 版权所有 豫ICP备15022753号-3