概述
管道是进程通信的一种方式,其原理是内核里的一个缓冲区,不同进程该内存读写字节流数据
创建管道的进程称为管道服务器,连接到一个管道的进程为管道客户机。一个进程在向管道写入数据后,另一
进程就可以从管道的另一端将其读取出来。
**匿名管道**是只存在于内核的一块缓存区,做成文件读写方式,磁盘并无实体文件,大小64kb
命名管道是一种特殊的系统(设备)文件wins下一般在/./pipe/pipename,而跨网络的时候需要将.替换成服务器的地址;在内核中是以环形队列形式存储的,且是FIFO的,大小64kb
匿(命)名管道读写一次需要经历四次数据拷贝:用户空间buff拷贝到内核->内核拷贝到内存->内存拷贝到内核->内核拷贝到用户空间buff
而共享内存只需要两次:用户空间buff拷贝到内存->内存拷贝到用户空间buff
匿名管道
匿名管道只能用于父子进程间通信 ,不能跨网络通信,是单工的,即一条管道,只能一端写,另一端读,**管道的读写句柄通过继承重定向来传递给子进程**
双向通信,需要两个匿名管道
重要结构体&API
//该结构体,决定了启动程序的一些属性设置
typedef struct _STARTUPINFO
{
DWORD cb; //包含STARTUPINFO结构中的字节数.如果Microsoft将来扩展该结构,它可用作版本控制手段.应用程序必须将cb初始化为sizeof(STARTUPINFO)
PSTR lpReserved; //保留。必须初始化为NULL
PSTR lpDesktop; //用于标识启动应用程序所在的桌面的名字。如果该桌面存在,新进程便与指定的桌面相关联。如果桌面不存在,便创建一个带有默认属性的桌面,并使用为新进程指定的名字。如果lpDesktop是NULL(这是最常见的情况 ),那么该进程将与当前桌面相关联
PSTR lpTitle; //用于设定控制台窗口的名称。如果lpTitle是NULL,则可执行文件的名字将用作窗口名.This parameter must be NULL for GUI or console processes that do not create a new console window.
DWORD dwX; //用于设定应用程序窗口相对屏幕左上角位置的x 坐标(以像素为单位)。
DWORD dwY; //对于GUI processes用CW_USEDEFAULT作为CreateWindow的x、y参数,创建它的第一个重叠窗口。若是创建控制台窗口的应用程序,这些成员用于指明相对控制台窗口的左上角的位置
DWORD dwXSize; //用于设定应用程序窗口的宽度(以像素为单位)
DWORD dwYSize; //子进程将CW_USEDEFAULT 用作CreateWindow 的nWidth、nHeight参数来创建它的第一个重叠窗口。若是创建控制台窗口的应用程序,这些成员将用于指明控制台窗口的宽度
DWORD dwXCountChars; //用于设定子应用程序的控制台窗口的宽度(屏幕显示的字节列)和高度(字节行)(以字符为单位)
DWORD dwYCountChars;
DWORD dwFillAttribute; //用于设定子应用程序的控制台窗口使用的文本和背景颜色
DWORD dwFlags; //请参见下一段和表4-7 的说明
WORD wShowWindow; //用于设定如果子应用程序初次调用的ShowWindow 将SW_*作为nCmdShow 参数传递时,该应用程序的第一个重叠窗口应该如何出现。本成员可以是通常用于ShowWindow 函数的任何一个SW_*标识符,除了SW_SHOWDEFAULT.
WORD cbReserved2; //保留。必须被初始化为0
PBYTE lpReserved2; //保留。必须被初始化为NULL
HANDLE hStdInput; //用于设定供控制台输入和输出用的缓存的句柄。按照默认设置,hStdInput 用于标识键盘缓存,hStdOutput 和hStdError用于标识控制台窗口的缓存
HANDLE hStdOutput; //默认控制台输出
HANDLE hStdError; //标准错误输出
} STARTUPINFO, *LPSTARTUPINFO
typedef struct _PROCESS_INFORMATION {
HANDLE hProcess; // 进程句柄
HANDLE hThread; // 主线程句柄
DWORD dwProcessId; // 进程ID号
DWORD dwThreadId; // 主线程ID号
} PROCESS_INFORMATION, *PPROCESS_INFORMATION, *LPPROCESS_INFORMATION;
BOOL CreateProcess(
LPCTSTR lpApplicationName, //可执行程序名,如果这里给了exe路径,lpCommandLine不能重复给
LPTSTR lpCommandLine, //命令行字符串,可以为NULL
LPSECURITY_ATTRIBUTES lpProcessAttributes, //新进程对象的安全属性
LPSECURITY_ATTRIBUTES lpThreadAttributes, //新进程对应的线程安全属性
BOOL blnheritHandles, //指定父进程的对象句柄能否被子进程继承,管道正是利用该属性,让子进程继承管道句柄重定向
DWORD dwCreationFlags, //指定创建进程的附加标记,即指定新进程的特性
LPVOID lpEnvironment, //指定新进程使用的环境,NULL表示同父进程的环境
LPCTSTR lpCurrentDirection, //指定子进程当前路径,NULL表示同父进程相同
LPSTARTUPINFO lpStartupInfo, //指定新进程主窗口如何显示
LPPROCESS_INFORMATION lpProcessInformation, //作为返回值使用,是一个指针
);
/* 创建管道 */
BOOL WINAPI CreatePipe(
PHANDLE hReadPipe, // 管道输出(读取)端句柄
PHANDLE hWritePipe, // 管道输入(写入)端句柄
LPSECURITY_ATTRIBUTES lpPipeAttributes, // 管道的安全属性
DWORDnSize // 管道缓冲区容量,设置0时使用默认大小
);
/* 从管道中读取数据 */
BOOL ReadFile(
HANDLE hFile, // 管道输出端(读取)句柄
LPVOID lpBuffer, // 数据缓冲区指针,读取的数据将写在该缓冲区中
DWORD nNumberOfBytesToRead, // 指定读取的字节数
LPDWORD lpNumberOfBytesRead, // 返回实际读取到的字节数
LPOVERLAPPED lpOverlapped // 用于异步操作,一般传入NULL即可
);
/* 向管道写入数据 */
BOOL WriteFile(
HANDLE hFile, // 管道输入端(写入)句柄,也可以是CreateFile()接口创建的文件句柄
LPVOID lpBuffer, // 待写入管道的数据缓冲区指针
DWORD nNumberOfBytesToWrite, // 指定写入的字节数
LPDWORD lpNumberOfBytesWritten, // 返回实际写入管道的字节数
LPOVERLAPPED lpOverlapped // 用于异步操作,一般传入NULL即可
);
匿名管道双向通信代码demo
父进程:
// parentExe.cpp : 定义控制台应用程序的入口点。
/*****双匿名管道,单管道单工通信*****
管道1:父进程读,子进程写
管道2:父进程写,子进程读
*************************************/
#include "stdafx.h"
#include "parentExe.h"
#include <windows.h>
#include <iostream>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 唯一的应用程序对象
CWinApp theApp;
using namespace std;
HANDLE hPrarentRead = NULL; //管道1父进程读
HANDLE hSubWrite = NULL; //管道1子进程写
HANDLE hSubRead = NULL; //管道2子进程读
HANDLE hPrarentWrite = NULL; //管道2父进程写
void ReadMsg()
{
char buf1[512];
memset(buf1, 0, 512);
DWORD dwRead;
if (!ReadFile(hPrarentRead, buf1, 512, &dwRead, NULL))
{
cout << "读取数据失败!";
return;
}
else
{
cout << "管道中读取数据为:" << buf1 << endl;
}
}
void WriteMsg()
{
char buf[100];
cout << "请输入你要发给子进程的消息:\n";
cin >> buf;
DWORD dwWrite;
if (!WriteFile(hPrarentWrite, buf, strlen(buf), &dwWrite, NULL))
{
cout << "写入数据失败!";
return;
}
else
cout << "成功写入数据!\n";
}
int main()
{
int nRetCode = 0;
HMODULE hModule = ::GetModuleHandle(nullptr);
if (hModule != nullptr)
{
// 初始化 MFC 并在失败时显示错误
if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0))
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: MFC 初始化失败\n");
nRetCode = 1;
}
else
{
SECURITY_ATTRIBUTES sa; //定义一个安全结构体类型的变量Sa
sa.bInheritHandle = TRUE;//让子进程可以继承父进程的句柄
sa.lpSecurityDescriptor = NULL;//让系统为创建的匿名管道赋予默认的安全描述符
sa.nLength = sizeof(SECURITY_ATTRIBUTES);//得到结构体(SECURITY_ATTRIBUTES)的长度
if (!CreatePipe(&hPrarentRead, &hSubWrite, &sa, 0))//创建匿名管道1
{
cout << "创建匿名管道1失败!\n";
return 0;
}
else
cout << "创建匿名管道1成功!\n";
if (!CreatePipe(&hSubRead, &hPrarentWrite, &sa, 0))//创建匿名管道2
{
cout << "创建匿名管道2失败!\n";
return 0;
}
else
cout << "创建匿名管道2成功!\n";
//如果创建匿名管道成功,就启动子进程,并将匿名管道的读写句柄传递给子进程
STARTUPINFO sui;//创建子进程的函数需要的一个结构体类型的值
PROCESS_INFORMATION pi;
ZeroMemory(&sui, sizeof(STARTUPINFO));//将其余的成员全部置零,避免造成不好的影响
sui.cb = sizeof(STARTUPINFO);//cb用来存放结构体变量STARTUPINFO的长度
sui.dwFlags = STARTF_USESTDHANDLES;//标志成员,表示当前STARTUPINFO结构体的标准输入,标准输出和标准错误句柄有用
sui.hStdInput = hSubRead; //子程序标准输入->从管道2读取
sui.hStdOutput = hSubWrite; //子程序标准输出->从管道1写入
sui.hStdError = GetStdHandle(STD_ERROR_HANDLE); //子程序标准错误->父进程标准错误
sui.wShowWindow = SW_SHOW; //显示窗口
sui.dwFlags = STARTF_USESHOWWINDOW | STARTF_USESTDHANDLES;
WriteMsg();
TCHAR cmdLine[] = TEXT("D:\\vs project\\ConsoleAppPipeClient\\Debug\\ConsoleAppPipeClient.exe");
//CreateProcess(NULL, TEXT("D:\\ConsoleAppPipeClient.exe"), NULL, NULL,\
TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &sui, &pi) 是错误的,lpCommandLine会被内部修改,所以不能是常量,得用临时变量
if (!CreateProcess(NULL, cmdLine, NULL, NULL,
TRUE, CREATE_NEW_CONSOLE, NULL, NULL, &sui, &pi))//创建子进程(十个参数)
{
//关闭句柄,管道内核对象,读写计数都为0,系统自动释放管道
CloseHandle(hPrarentRead);
CloseHandle(hPrarentWrite);
CloseHandle(hSubRead);
CloseHandle(hSubWrite);
hPrarentRead = NULL;
hPrarentWrite = NULL;
hSubRead = NULL;
hSubWrite = NULL;
cout << "创建子进程失败!";
return 0;
}
else
{
//关闭子进程句柄和其主线程句柄
CloseHandle(pi.hProcess);
CloseHandle(pi.hThread);
}
Sleep(500); //停5s让子进程读取再写入
ReadMsg();
system("pause");
}
}
else
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: GetModuleHandle 失败\n");
nRetCode = 1;
}
return nRetCode;
}
子进程:
// subExe.cpp : 定义控制台应用程序的入口点。
//
#include "stdafx.h"
#include "subExe.h"
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 唯一的应用程序对象
CWinApp theApp;
using namespace std;
int main()
{
int nRetCode = 0;
HMODULE hModule = ::GetModuleHandle(nullptr);
if (hModule != nullptr)
{
// 初始化 MFC 并在失败时显示错误
if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0))
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: MFC 初始化失败\n");
nRetCode = 1;
}
else
{
HANDLE hRead = GetStdHandle(STD_INPUT_HANDLE);//获取标准输入句柄,输入表示从鼠标键盘显示器等外设获取数据到内存
HANDLE hWrite = GetStdHandle(STD_OUTPUT_HANDLE);//获取标准输出句柄,输出表示从内存到鼠标键盘显示器等外设
char bufRead[512] = { 0 };
DWORD dwRead;
if (!ReadFile(hRead, bufRead, 512, &dwRead, NULL))
{
cout << "读取数据失败!";
}
char bufWrite[512] = "reply";
strcat_s(bufWrite, bufRead);
DWORD dwWrite;
if (!WriteFile(hWrite, bufWrite, strlen(bufWrite) + 1, &dwWrite, NULL))
{
cout << "写入数据失败!";
}
}
}
else
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: GetModuleHandle 失败\n");
nRetCode = 1;
}
return nRetCode;
}
命名管道
- 命名管道具有名称,也是根据该名称进行找到对应管道
- 一个管道,既可单工也可以双工通信
- 可支持1个或以上程序访问,无父子进程限定
- 命名管道是以FIFO的文件形式存储于硬盘文件系统中,是一个设备文件
- 命名管道的所有实例共享相同的管道名称,但每个实例都有自己的缓冲区和句柄,并为客户端/服务器通信提供单独的管道。实例的使用使多个管道客户端能够同时使用同一个命名管道。
- 命名管道可用于在同一台计算机上的进程之间或网络中不同计算机上的进程之间提供通信
重要结构体&API
创建命名管道
HANDLE CreateNamedPipeA(
[in] LPCSTR lpName,
[in] DWORD dwOpenMode,
[in] DWORD dwPipeMode,
[in] DWORD nMaxInstances,
[in] DWORD nOutBufferSize,
[in] DWORD nInBufferSize,
[in] DWORD nDefaultTimeOut,
[in, LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
lpName://指定管道名,采用的形式是:\.\管道\管道名。最多可达256个字符的长度,而且不用区分大小写。如果存在指定名字的一个管道,则创建那个管道的一个新实例
dwOpenMode:管道流向
-
PIPE_ACCESS_DUPLEX,管道是双向的;服务器和客户端进程都可以读取和写入管道。此模式为服务器提供了对管道的GENERIC_READ和GENERIC_WRITE访问权限。当客户端使用CreateFile函数连接到管道时,它可以指定GENERIC_READ或GENERIC_WRITE或两者
-
PIPE_ACCESS_INBOUND,管道中的数据流仅从客户端到服务器。此模式为服务器提供了对管道的GENERIC_READ访问权限。客户端在连接到管道时必须指定GENERIC_WRITE访问权限。如果客户端必须通过调用GetNamedPipeInfo或GetNamedPipeHandleState函数读取管道设置,则客户端在连接到管道时必须指定GENERIC_WRITE和FILE_READ_ATTRIBUTES访问权限
-
PIPE_ACCESS_OUTBOUND,管道中的数据流仅从服务器流向客户端。此模式为服务器提供了对管道的GENERIC_WRITE访问权限。客户端在连接到管道时必须指定GENERIC_READ访问权限。如果客户端必须通过调用SetNamedPipeHandleState函数更改管道设置,则客户端在连接到管道时必须指定GENERIC_READ和FILE_WRITE_ATTRIBUTES访问权限
-
FILE_FLAG_FIRST_PIPE_INSTANCE,如果您尝试使用此标志创建管道的多个实例,则第一个实例的创建成功,但下一个实例的创建失败并显示ERROR_ACCESS_DENIED
-
FILE_FLAG_WRITE_THROUGH,直写模式已启用。此模式仅影响字节类型管道上的写入操作,并且仅当客户端和服务器进程位于不同的计算机上时。如果启用此模式,则写入命名管道的函数不会返回,直到写入的数据通过网络传输并位于远程计算机上的管道缓冲区中。如果未启用此模式,系统会通过缓冲数据来提高网络操作的效率,直到累积的字节数达到最少或经过最长时间
-
FILE_FLAG_OVERLAPPED,重叠模式已启用。如果启用此模式,则执行可能需要很长时间才能完成的读取、写入和连接操作的函数可以立即返回。这种模式使启动操作的线程可以执行其他操作,而耗时的操作在后台执行。例如,在重叠模式下,线程可以同时处理管道的多个实例上的输入和输出 (I/O) 操作,或者在同一个管道句柄上同时执行读写操作。如果未启用重叠模式,则对管道句柄执行读取、写入和连接操作的函数在操作完成之前不会返回。该 ReadFileEx和 WriteFileEx函数只能在重叠模式下与管道句柄一起使用。的 ReadFile的, WriteFile的, ConnectNamedPipe和 TransactNamedPipe功能可以执行同步或作为重叠操作
-
WRITE_DAC,调用者将拥有对命名管道的自由访问控制列表 (ACL) 的写访问权限
-
WRITE_OWNER,调用者将拥有对命名管道所有者的写访问权限
-
ACCESS_SYSTEM_SECURITY,调用者将拥有对命名管道的 SACL 的写访问权限。有关详细信息,请参阅 访问控制列表(ACL)和 SACL 访问权限
dwPipeMode:
-
PIPE_TYPE_BYTE,数据以字节流的形式写入管道。此模式不能与PIPE_READMODE_MESSAGE一起使用。管道不区分在不同写操作期间写入的字节,读取端不用每次读取固定大小的数据,可以读取任意字节大小数据
-
PIPE_TYPE_MESSAGE,数据作为消息流写入管道。管道将每次写入操作期间写入的字节视为一个消息单元。当消息未完全读取时,GetLastError函数返回ERROR_MORE_DATA。此模式可与PIPE_READMODE_MESSAGE或PIPE_READMODE_BYTE一起使用,读取端用消息模式读取时必须全部读取完,不能只读取部分消息
-
PIPE_READMODE_BYTE,数据作为字节流从管道中读取。此模式可与PIPE_TYPE_MESSAGE或PIPE_TYPE_BYTE 一起使用
-
PIPE_READMODE_MESSAGE,数据作为消息流从管道中读取。仅当还指定了PIPE_TYPE_MESSAGE 时才能使用此模式
-
PIPE_WAIT,阻塞模式已启用。当在ReadFile、WriteFile或 ConnectNamedPipe函数中指定管道句柄时 , 直到有数据要读取、所有数据都已写入或客户端已连接时,操作才会完成。使用此模式可能意味着在某些情况下无限期地等待客户端进程执行操作
-
PIPE_NOWAIT,非阻塞模式已启用。在这种模式下,ReadFile、WriteFile和 ConnectNamedPipe总是立即返回。
请注意,为了与 Microsoft LAN Manager 2.0 版兼容,支持非阻塞模式,并且不应使用命名管道实现异步 I/O。有关异步管道 I/O 的更多信息,请参阅 同步和重叠输入和输出
nMaxInstances:
可以为此管道创建的最大实例数。管道的第一个实例可以指定这个值;必须为管道的其他实例指定相同的编号。可接受的值在 1 到PIPE_UNLIMITED_INSTANCES (255)的范围内
nOutBufferSize:
为输出缓冲区保留的字节数
nInBufferSize:
为输入缓冲区保留的字节数
nDefaultTimeOut:
默认超时值(以毫秒为单位),如果 WaitNamedPipe函数指定NMPWAIT_USE_DEFAULT_WAIT。命名管道的每个实例都必须指定相同的值。零值将导致默认超时为 50 毫秒
lpSecurityAttributes:
指向SECURITY_ATTRIBUTES结构的指针,该 结构为新命名管道指定安全描述符并确定子进程是否可以继承返回的句柄
返回值:
如果函数成功,则返回值是命名管道实例的服务器端的句柄。
如果函数失败,则返回值为INVALID_HANDLE_VALUE。要获取扩展错误信息,请调用 GetLastError
BOOL ConnectNamedPipe(
[in] HANDLE hNamedPipe,
[in, LPOVERLAPPED lpOverlapped
);
hNamedPipe:
命名管道实例的服务器端的句柄。此句柄由CreateNamedPipe函数返回
lpOverlapped:
指向OVERLAPPED结构的指针 。
如果hNamedPipe是用 FILE_FLAG_OVERLAPPED 打开的,则lpOverlapped参数不能为NULL。它必须指向一个有效的OVERLAPPED结构。如果hNamedPipe使用 FILE_FLAG_OVERLAPPED 打开并且lpOverlapped为NULL,则该函数可能会错误地报告连接操作已完成。
如果hNamedPipe是用 FILE_FLAG_OVERLAPPED 创建的并且lpOverlapped不是NULL,则OVERLAPPED结构应该包含一个手动重置事件对象的句柄(服务器可以使用CreateEvent函数创建它 )。【非阻塞重叠IO,异步等待客户端连接】
如果未使用 FILE_FLAG_OVERLAPPED 打开hNamedPipe,则在连接客户端或发生错误之前,该函数不会返回。如果客户端在调用函数后连接,则成功的同步操作会导致函数返回非零值。【阻塞同步等待客户端连接】
返回值:
如果操作是同步的,则ConnectNamedPipe在操作完成之前不会返回。如果函数成功,则返回值非零。如果函数失败,则返回值为零。要获取扩展错误信息,请调用 GetLastError。
如果操作是异步的,ConnectNamedPipe 会立即返回。如果操作仍处于挂起状态,则返回值为零且GetLastError返回 ERROR_IO_PENDING。(您可以使用HasOverlappedIoCompleted宏来确定操作何时完成。)如果函数失败,则返回值为零,并且 GetLastError返回 ERROR_IO_PENDING 或 ERROR_PIPE_CONNECTED 以外的值。
如果客户端在调用函数之前连接,则函数返回零并且GetLastError返回 ERROR_PIPE_CONNECTED。如果客户端在调用CreateNamedPipe和调用 ConnectNamedPipe之间的时间间隔内进行连接,则会发生这种情况【举例:比如服务器端执行CreateNamePipe后,不立即执行ConnectionNamePipe,客户端随即执行CreateFile,客户端此时可以往里面写数据、然后再执行ConnectionNamePipe时函数返回零并且GetLastError返回 ERROR_PIPE_CONNECTED)】
BOOL WaitNamedPipeA(
[in] LPCSTR lpNamedPipeName,//命名管道名称
[in] DWORD nTimeOut
);
nTimeOut:
函数等待命名管道实例可用的毫秒数,等待直到超时间隔结束或指定命名管道的实例可用于连接(即,管道的服务器进程在管道上有一个挂起的 ConnectNamedPipe操作),还可以用以下宏
- NMPWAIT_USE_DEFAULT_WAIT,超时间隔是服务器进程在CreateNamedPipe函数中指定的默认值
- NMPWAIT_WAIT_FOREVER,在命名管道的实例可用之前,该函数不会返回
服务端同步读,客户端异步写
// NamePipeAsycClient.cpp : 定义控制台应用程序的入口点。
// 服务器同步读
#include "stdafx.h"
#include "NamePipeAsycServer.h"
#include<Windows.h>
#include <string>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 唯一的应用程序对象
CWinApp theApp;
using namespace std;
DWORD WINAPI ThreadProc(LPVOID);
bool readMessage(HANDLE hTmpNamedPipe, std::string& message)
{
char recvBuffer[10001] = { 0 };
DWORD nBytesRead = 0;
int ret = ReadFile(
hTmpNamedPipe,
recvBuffer,
sizeof(recvBuffer),
&nBytesRead,
NULL
);
if (0 >= ret)
{
printf("Read from client error: %d.\n", errno);
return false;
}
else
{
message = static_cast<std::string>(recvBuffer);
return true;
}
}
DWORD WINAPI ThreadProc(LPVOID)
{
HANDLE hNamedPipe = CreateNamedPipe
(TEXT("\\\\.\\pipe\\pipeTest"),
PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE,
3,//pipeTest管道最大实例数
0,
0,
1000, // timeout in millseconds
NULL);
printf("\\\\.\\pipe\\pipeTest... \n");
if (NULL == hNamedPipe || INVALID_HANDLE_VALUE == hNamedPipe)
{
return NULL;
}
printf("waiting for signal\n");
while (true)
{
HANDLE hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);//初始状态为TRUE的人工重置对象
if (INVALID_HANDLE_VALUE == hEvent)
{
return NULL;
}
OVERLAPPED ovlap;
ZeroMemory(&ovlap, sizeof(OVERLAPPED));
ovlap.hEvent = hEvent;//将一个事件与重叠I/O绑定
std::string message;
BOOL fPendingIO = FALSE;
DWORD dwWait;
printf("strat to read from client...\n");
BOOL fConnected = ConnectNamedPipe(hNamedPipe, &ovlap); //ovlap.hEvent关联到管道,当有客户端连接,就会设为有信号;但是此例管道openMode是FILE_FLAG_OVERLAPPED,所以马上返回false(0)
if (fConnected)
{
printf("ConnectNamedPipe failed with %d.\n", GetLastError());
return 0;
}
switch (GetLastError())
{
// The overlapped connection in progress.
case ERROR_IO_PENDING:
printf("ConnectNamedPipe status fPendingIO.\n");
fPendingIO = true;
break;
// Client is already connected, so signal an event.
case ERROR_PIPE_CONNECTED:
if (SetEvent(hEvent))
break;
// If an error occurs during the connect operation...
default:
{
printf("ConnectNamedPipe failed with %d.\n", GetLastError());
return 0;
}
}
/* 阻塞状态下,等待管道另一端的连入,当一个连接到来的时候,ovlap.hEvent会立即变为有信号状态 */
dwWait = WaitForSingleObject(ovlap.hEvent, INFINITE);
DWORD dwTransBytes = -1;
switch (dwWait)
{
case 0:
if (fPendingIO)
{
//获取Overlapped结果
if (GetOverlappedResult(hNamedPipe, &ovlap, &dwTransBytes, TRUE) == FALSE)
{
printf("ConnectNamedPipe failed %d\n", GetLastError());
return -1;
}
}
// 读写完成
case WAIT_IO_COMPLETION:
{
while (readMessage(hNamedPipe, message))
{
while (std::string::npos != message.find("|"))
{
std::string tmpSourceStr = message.substr(0, message.find("|"));
if (tmpSourceStr.length() > 0)
{
printf("push message=%s in queue...\n", message.c_str());
}
message = message.substr(message.find("|") + 1);
}
}
break;
}
}
DisconnectNamedPipe(hNamedPipe);
}
}
int main()
{
int nRetCode = 0;
HMODULE hModule = ::GetModuleHandle(nullptr);
if (hModule != nullptr)
{
// 初始化 MFC 并在失败时显示错误
if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0))
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: MFC 初始化失败\n");
nRetCode = 1;
}
else
{
// TODO: 在此处为应用程序的行为编写代码。
HANDLE hThread1;
DWORD threadId1;
/*HANDLE hThread2;
DWORD threadId2;*/
printf("another server is running\r\n");
hThread1 = CreateThread(NULL, 0, ThreadProc, 0, 0, &threadId1);//创建线程
system("pause");
}
}
else
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: GetModuleHandle 失败\n");
nRetCode = 1;
}
return nRetCode;
}
// NamePipeAsycServer.cpp : 定义控制台应用程序的入口点。
// 客户端异步写
#include "stdafx.h"
#include "NamePipeAsycClient.h"
#include <iostream>
#include <WinSock2.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 唯一的应用程序对象
CWinApp theApp;
using namespace std;
DWORD WINAPI ThreadProc(LPVOID);
HANDLE hThread;
void Execute();
void ConnectANamePipe();
void ReconnectNamePipe();
void ReconnectNamePipe()
{
CloseHandle(hThread);
ConnectANamePipe();
}
void ConnectANamePipe()
{
DWORD len;
HANDLE hClientNamedPipe = CreateFile(TEXT("\\\\.\\pipe\\pipeTest"),
GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL);
if (hClientNamedPipe == NULL || hClientNamedPipe == INVALID_HANDLE_VALUE)
{
printf("Connect to WDS ERROR %d...\n", GetLastError());
}
/*HANDLE hClientNamedPipe=CreateFile("\\\\.\\Pipe\\pipeTest",GENERIC_WRITE|GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
if(hClientNamedPipe==INVALID_HANDLE_VALUE)
{
printf("error,getlasterror=%d\n",GetLastError());
}*/
//CreateFile连接管道成功后,服务器端ovlap.hEvent即可变为有信号
else
{
while (true)
{
OVERLAPPED olWrite;
memset(&olWrite, 0, sizeof(olWrite));
olWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);//创建了一个初始状态为FALSE的手动(人工)重置的事件对象,当手动重置的事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程。
DWORD nBytesWritten = 0;
printf("子线程,pid=%d...\n", GetCurrentThreadId());
char buff[256] = "i am cliect,hello server|";
/* if (WriteFile(hClientNamedPipe, buff, sizeof(buff), &nBytesWritten, &olWrite) == false)
{
printf("write error,getlasterror=%d...\n", GetLastError());
ReconnectNamePipe();
}
else
{
printf("write success\n");
Sleep(500);
}*/
int ret = (WriteFile(hClientNamedPipe, buff, sizeof(buff), &nBytesWritten, &olWrite));
printf(" Write to server return : %d.\n", ret);
if (0 == ret)
{
long int iError = GetLastError();
if (iError != ERROR_IO_PENDING /*997*/)//997表示这个请求是悬而未决的
{
printf("a Write to server Error : %d.\n", GetLastError());
ReconnectNamePipe();
return;
}
}
if (0 == GetOverlappedResult(hClientNamedPipe, &olWrite, &nBytesWritten, true))
{
printf("Write to server Error : %d.\n", GetLastError());
ReconnectNamePipe();
return;
}
else
{
printf("Write to server ok.\n");
Sleep(1000);
}
}
}
CloseHandle(hClientNamedPipe);
}
DWORD WINAPI ThreadProc(LPVOID)
{
ConnectANamePipe();
return 0;
}
int main()
{
int nRetCode = 0;
HMODULE hModule = ::GetModuleHandle(nullptr);
if (hModule != nullptr)
{
// 初始化 MFC 并在失败时显示错误
if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0))
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: MFC 初始化失败\n");
nRetCode = 1;
}
else
{
// TODO: 在此处为应用程序的行为编写代码。
DWORD threadId;
hThread = CreateThread(NULL, 0, ThreadProc, 0, 0, &threadId);//创建线程
std::cout << "Hello World!\n";
while (1)
{
Sleep(5000);
}
}
}
else
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: GetModuleHandle 失败\n");
nRetCode = 1;
}
return nRetCode;
}
服务器同步读,客户端同步写
// NamePipeServer.cpp : 定义控制台应用程序的入口点。
// 服务端同步读
#include "stdafx.h"
#include "NamePipeServer.h"
#include<Windows.h>
#include <string>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 唯一的应用程序对象
CWinApp theApp;
using namespace std;
DWORD WINAPI ThreadProc(LPVOID);
bool readMessage(HANDLE hTmpNamedPipe, std::string& message)
{
char recvBuffer[10001] = { 0 };
DWORD nBytesRead = 0;
int ret = ReadFile(
hTmpNamedPipe,
recvBuffer,
sizeof(recvBuffer),
&nBytesRead,
NULL
);
if (0 >= ret)
{
printf("Read from client error: %d.\n", errno);
return false;
}
else
{
message = static_cast<std::string>(recvBuffer);
return true;
}
}
DWORD WINAPI ThreadProc(LPVOID)
{
HANDLE hNamedPipe = CreateNamedPipe
(TEXT("\\\\.\\pipe\\pipeTest"),
PIPE_ACCESS_INBOUND, //服务器读
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE, //写入:消息流,读取:消息流
3, //最大实例3
0,
0,
1000, // timeout in millseconds
NULL);
printf("\\\\.\\pipe\\pipeTest... \n");
if (NULL == hNamedPipe || INVALID_HANDLE_VALUE == hNamedPipe)
{
return NULL;
}
std::string message;
while (true)
{
printf("strat to read from client...\n");
BOOL fConnected = ConnectNamedPipe(hNamedPipe, NULL); //阻塞,直到有客户端来连接管道
if (fConnected)
{
printf("connect success...\n");
while (readMessage(hNamedPipe, message)) //阻塞,直到管道有数据读取
{
while (std::string::npos != message.find("|"))
{
std::string tmpSourceStr = message.substr(0, message.find("|"));
if (tmpSourceStr.length() > 0)
{
printf("push message=%s in queue...\n", message.c_str());
}
message = message.substr(message.find("|") + 1);
}
}
}
DisconnectNamedPipe(hNamedPipe);
}
return NULL;
}
int main()
{
int nRetCode = 0;
HMODULE hModule = ::GetModuleHandle(nullptr);
if (hModule != nullptr)
{
// 初始化 MFC 并在失败时显示错误
if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0))
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: MFC 初始化失败\n");
nRetCode = 1;
}
else
{
// TODO: 在此处为应用程序的行为编写代码。
HANDLE hThread1;
DWORD threadId1;
printf("another server is running\r\n");
hThread1 = CreateThread(NULL, 0, ThreadProc, 0, 0, &threadId1);//创建线程
while (true)
{
Sleep(30000);
}
}
}
else
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: GetModuleHandle 失败\n");
nRetCode = 1;
}
return nRetCode;
}
// NamePipeClient.cpp : 定义控制台应用程序的入口点。
//客户端同步写
#include "stdafx.h"
#include "NamePipeClient.h"
#include <iostream>
#include <WinSock2.h>
#ifdef _DEBUG
#define new DEBUG_NEW
#endif
// 唯一的应用程序对象
CWinApp theApp;
using namespace std;
DWORD WINAPI ThreadProc(LPVOID);
HANDLE hThread;
void ConnectANamePipe()
{
if (!WaitNamedPipe(TEXT("\\\\.\\pipe\\pipeTest"), NMPWAIT_WAIT_FOREVER)) //阻塞,直到有管道实例可以连接,这里NMPWAIT_WAIT_FOREVER无限等待
{
cout << "conenct namepipe failed" << endl;
}
DWORD len;
HANDLE hClientNamedPipe = CreateFile(TEXT("\\\\.\\pipe\\pipeTest"),
GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (hClientNamedPipe == NULL || hClientNamedPipe == INVALID_HANDLE_VALUE)
{
printf("Connect to WDS ERROR %d...\n", GetLastError());
}
else
{
while (true)
{
DWORD nBytesWritten = 0;
printf("子线程,pid=%d...\n", GetCurrentThreadId());
char buff[256] = "i am cliect,hello server|";
/* if (WriteFile(hClientNamedPipe, buff, sizeof(buff), &nBytesWritten, &olWrite) == false)
{
printf("write error,getlasterror=%d...\n", GetLastError());
ReconnectNamePipe();
}
else
{
printf("write success\n");
Sleep(500);
}*/
int ret = (WriteFile(hClientNamedPipe, buff, sizeof(buff), &nBytesWritten, NULL));
if (0 == ret)
{
long int iError = GetLastError();
printf("write to server error=%d", iError);
}
printf("Write to server ok.\n");
Sleep(1000);
}
}
CloseHandle(hClientNamedPipe);
}
DWORD WINAPI ThreadProc(LPVOID)
{
ConnectANamePipe();
return 0;
}
void ReconnectNamePipe()
{
CloseHandle(hThread);
ConnectANamePipe();
}
int main()
{
int nRetCode = 0;
HMODULE hModule = ::GetModuleHandle(nullptr);
if (hModule != nullptr)
{
// 初始化 MFC 并在失败时显示错误
if (!AfxWinInit(hModule, nullptr, ::GetCommandLine(), 0))
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: MFC 初始化失败\n");
nRetCode = 1;
}
else
{
// TODO: 在此处为应用程序的行为编写代码。
DWORD threadId;
hThread = CreateThread(NULL, 0, ThreadProc, 0, 0, &threadId);//创建线程
std::cout << "Hello World!\n";
system("pause");
}
}
else
{
// TODO: 更改错误代码以符合您的需要
wprintf(L"错误: GetModuleHandle 失败\n");
nRetCode = 1;
}
return nRetCode;
}