进程的通信 - 命名管道

命名管道概述

命名管道(Named Pipes),顾名思义,一个有名字的管道。命名管道的名字主要是用于确保多个进程访问同一个对象。命名管道不仅可以在同一台计算机之间传输数据,甚至能在跨越一个网络的不同计算机的不同进程之间,支持可靠的、单向或双向的数据通信。

命名管道常用的API

创建命名管道实例—CreateNamedPipe

函数原型

HANDLE CreateNamedPipeW(
  [in]           LPCWSTR               lpName,
  [in]           DWORD                 dwOpenMode,
  [in]           DWORD                 dwPipeMode,
  [in]           DWORD                 nMaxInstances,
  [in]           DWORD                 nOutBufferSize,
  [in]           DWORD                 nInBufferSize,
  [in]           DWORD                 nDefaultTimeOut,
  [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes
);
  • 参数lpName

唯一的管道名称。 必须是"\\.\pipe\管道名称“的格式。最多为256个字符长度,且不区分大小。如果已经有相同的命名管道,则会创建那个管道的一个实例

  • 参数dwOpenMode

表示管道的打开方式,同一管道的每个实例必须指定相同的打开方式

模式意义

PIPE_ACCESS_DUPLEX

0x00000003

管道是双向的;服务器和客户端进程都可以读取和写入管道。

PIPE_ACCESS_INBOUND

0x00000001

管道中的数据流仅从客户端传输到服务器。

PIPE_ACCESS_OUTBOUND

0x00000002

管道中的数据流仅从服务器传输到客户端。

同时可以包含以下一个或多个标记,对于同一管道的不同实例,这些模式可以不同

模式意义

FILE_FLAG_FIRST_PIPE_INSTANCE

0x00080000

如果尝试使用此标志创建管道的多个实例,则第一个实例的创建成功,但下一个实例的创建失败,并ERROR_ACCESS_DENIED

FILE_FLAG_WRITE_THROUGH

0x80000000

直写模式已启用。

FILE_FLAG_OVERLAPPED

0x40000000

重叠模式已启用。

还可以包含以下安全访问模式的组合,对于同一管道的不同实例,这些模式可以是不同。

模式意义

WRITE_DAC

0x00040000L

调用方将具有对命名管道的自由访问控制列表 (ACL) 的写入访问权限。

WRITE_OWNER

0x00080000L

调用方将具有对命名管道所有者的写入访问权限。

ACCESS_SYSTEM_SECURITY

0x01000000L

调用方将具有对命名管道的 SACL 的写入访问权限。有关详细信息,请参阅访问控制列表 (ACL) 和SACL 访问权限
  • 参数dwPipeMode

管道模式,可以指定以下类型模式之一。必须为管道的每个实例指定为相同的类型模式 

模式意义

PIPE_TYPE_BYTE

0x00000000

数据以字节流的形式写入管道。

PIPE_TYPE_MESSAGE

0x00000004

数据作为消息流写入管道。

也可以指定以下读取模式之一。同一管道的不同实例可以指定不同的读取模式。

模式意义

PIPE_READMODE_BYTE

0x00000000

数据以字节流的形式从管道读取。此模式可用于PIPE_TYPE_MESSAGEPIPE_TYPE_BYTE

PIPE_READMODE_MESSAGE

0x00000002

数据作为消息流从管道读取。仅当还指定了PIPE_TYPE_MESSAGE时,才能使用此模式。

 还可以指定以下等待模式之一。同一管道的不同实例可以指定不同的等待模式。

PIPE_WAIT

0x00000000

阻止模式已启用。

PIPE_NOWAIT

0x00000001

非阻塞模式已启用。在此模式下,ReadFileWriteFileConnectNamedPipe始终立即返回。

还可以指定以下远程客户端模式之一。同一管道的不同实例可以指定不同的远程客户端模式。

模式意义

PIPE_ACCEPT_REMOTE_CLIENTS

0x00000000

可以接受来自远程客户端的连接,并根据管道的安全描述符进行检查。

PIPE_REJECT_REMOTE_CLIENTS

0x00000008

来自远程客户端的连接将自动被拒绝。
  • 参数nMaxInstances

指定管道可以创建的最大实例数,必须是1到常数255之间的一个值。管道的第一个实例可以指定这个值,管道的其他实例的这个值必须和第一个实例指定的这个值相同。

  • 参数nOutBufferSize

表示管道的输出缓冲区的容量,0表示使用默认大小。

  • 参数nInBufferSize

表示管道的输入缓冲区的容量,0表示使用默认大小

  • 参数nDefaultTimeOut

表示管道的默认等待超时(ms单位),零表示默认超时为50毫秒

  • 参数lpSecurityAttributes

指向SECURITY_ATTRIBUTES结构的指针,该结构体指定命名管道的安全描述符,并确定子进程是否继承返回的句柄。NULL表示使用默认安全描述符,并且无法继承句柄

函数返回值
函数执行成功返回命名管道的句柄,否则返回INVALID_HANDLE_VALUE

等待客户端连接—ConnectNamdePipe

函数原型

BOOL ConnectNamedPipe(
  [in]                HANDLE       hNamedPipe,
  [in, out, optional] LPOVERLAPPED lpOverlapped
);
  • 参数hNamedPipe

表示管道的实例的服务端句柄。也就是CreateNamedPipe函数返回的句柄

  • 参数lpOverlapped

如果CreateNamedPipe第一个参数指定了FILE_FLAG_OVERLAPPED,则此参数不能为NULL,参数就必须是一个手动重置事件对象的句柄。 

例如下面这样

//创建命名管道
hNamedPipe = CreateNamedPipe(.., PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,..);

//手动重置事件对象的句柄
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
//创建一个OVERLAPPED结构体
OVERLAPPED ovlap;
ZeroMemory(&ovlap, sizeof(OVERLAPPED));//底层调用的memset
ovlap.hEvent = hEvent;

//等待客户端连接
ConnectNamedPipe(hNamedPipe,&ovlap);

 客户端连接命名管道—WaitNamedpipe

BOOL WaitNamedPipeW(
  [in] LPCWSTR lpNamedPipeName,
  [in] DWORD   nTimeOut
);
  • 参数lpNamedPipeName

表示命名管道的名称

  • 参数nTimeOut

表示等待命名管道的实例可用的毫秒数。可以使用以下值之一,而不是指定毫秒数。

取值意义

NMPWAIT_USE_DEFAULT_WAIT

0x00000000

超时间隔是服务器进程在CreateNamedPipe函数中指定的默认值。

NMPWAIT_WAIT_FOREVER

0xffffffff

在命名管道的实例可用之前,该函数不会返回。

函数返回值

如果管道的实例在超时之前可用,则返回值非零。否则返回零 。

如果指定的命名管道不存在实例,则无论超时值设定如何,WaitNamedPipe函数会立即返回。

如果函数执行成功,则进程可以使用CreateFile函数打开命名管道句柄。返回TRUE表示至少有一个管道实例可用。但也可能打开失败,例如当服务器关闭或者有其他客户端打开了管道。

打开文件或I/O设备—CreateFile

HANDLE CreateFileA(
  [in]           LPCSTR                lpFileName,
  [in]           DWORD                 dwDesiredAccess,
  [in]           DWORD                 dwShareMode,
  [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes,
  [in]           DWORD                 dwCreationDisposition,
  [in]           DWORD                 dwFlagsAndAttributes,
  [in, optional] HANDLE                hTemplateFile
);
  • 参数lpFileName

表示创建或打开的文件或设备名称

  • 参数dwDesiredAccess

请求文件或设备的访问权限,有读、写、读写或无任何操作。GENERIC_READGENERIC_WRITE或两者(GENERIC_READ | GENERIC_WRITE

  • 参数dwShareMode

文件或设备的请求共享模式,有读、写、读写、删除、读写删或者无任何模式

价值意义

0

防止其他进程在请求删除、读取或写入访问权限时打开文件或设备。

FILE_SHARE_DELETE

对文件或设备启用后续打开操作以请求删除访问权限。

FILE_SHARE_READ

启用对文件或设备的后续打开操作以请求读取访问权限。

FILE_SHARE_WRITE

允许对文件或设备执行后续打开操作以请求写入访问权限。
  • 参数 lpSecurityAttributes

指向SECUTITY_ATTRIBUTES结构的指针,参数为NULL表示任何子进程都不能继承CreateFile返回的句柄

  • 参数dwCreationDisposition

表示对存在会不存在的文件或设备执行的操作,对存在的文件或设备通常设置为OPEN_EXISTING

取值意义

CREATE_ALWAYS

始终创建新文件。

CREATE_NEW

仅当文件尚不存在时才创建新文件。

OPEN_ALWAYS

始终打开文件。

OPEN_EXISTING

仅打开文件或设备(如果存在)。

TRUNCATE_EXISTING

打开文件并将其截断,使其大小为零字节(仅当它存在时)。
  • 参数dwFlagsAndAttributes

表示文件或设备属性和标志,FILE_ATTRIBUTE_NORMAL是文件的通用默认属性 

  • 参数hTemplateFile

此参数可为NULL,打开现有文件时,创建文件时可用忽略这个参数

函数返回值

函数执行成功,返回值是指定文件、设备、命名管道或邮槽

函数失败返回INVALID_HANDLE_VALUE

Demo示例:

 

两个MFC应用,给第一个应用添加三个菜单分别为”创建管道“,”读取数据“,”写入数据“作为服务端。点击”创建管道“服务端会创建一个管道,然后等待连接;点击“读取数据”服务端会读取管道中的数据,然后通过消息提示框显示出来; 点击“写入数据”服务端会向命名管道写入数据。

第二个应用做客户端,为其添加三个菜单分别为“连接管道”、“读取数据”、“写入数据”。点击“连接管道”客户端会去连接命名管道;点击“读取数据”客户端会读取管道中的数据,然后通过消息提示框显示出来;点击“写入数据”客户端会向管道中写入数据。

服务端

创建管道:

服务端调ConnectNamedPipe等待客户端连接,这里我将参数lpOverlapped设定为一个手动重置的事件对象,当成功连接后,系统会将这个事件对象设为已通知状态,我们可以监听这个对象来判断客户端是否成功连接。

hNamedPipe是一个HANDLE类型的类属性,在类的构造函数里初始化,析构函数里销毁。

  

  

void CChildView::OnCreatNamePipe()
{
	//1.创建一个命名管道
	LPCTSTR szPipeName = TEXT("\\\\.\\pipe\\mypipe");
	hNamedPipe = CreateNamedPipe(szPipeName,
		PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
		PIPE_TYPE_BYTE,
		1, 1024, 1024, 0, NULL
	);
	if (hNamedPipe == INVALID_HANDLE_VALUE) {
		TRACE("Create NamedPipe failed witch %d\n", GetLastError());
		MessageBox(_T("创建命名管道失败"));
		return;
	}

	//连接完成后,系统会将OVERLAPPED的hEvent设置为已通知状态事件
	//这里创建一个事件赋值给hEvent,用来监控其改变
	HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
	if (hEvent == NULL) {
		MessageBox(_T("创建事件失败"));
		CloseHandle(hNamedPipe);
		hNamedPipe = NULL;
		return;
	}
	//2.等待客户端的连接
	OVERLAPPED ovlap;
	ZeroMemory(&ovlap, sizeof(OVERLAPPED));//底层调用的memset
	ovlap.hEvent = hEvent;

	if (!ConnectNamedPipe(hNamedPipe,&ovlap)) {
		//标准判断操作
		if (ERROR_IO_PENDING != GetLastError()) {
			MessageBox(_T("等待客户端连接失败"));
			CloseHandle(hNamedPipe);
			hNamedPipe = NULL;
			return;

		}
	}
	if (WaitForSingleObject(hEvent, INFINITE) == WAIT_FAILED) {
		//
		MessageBox(_T("等待对象失败"));
		CloseHandle(hNamedPipe);
		CloseHandle(hEvent);
		hNamedPipe = NULL;
		hEvent = NULL;
		return;
	}
	//否则就连接成功
}

读数据:

void CChildView::OnSreadNamePipe()
{
	char szBuf[100] = { 0 };
	DWORD dwRead;
	if (!ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL)) {
		MessageBox(_T("读取数据失败"));
		return;
	}
	MessageBox((CString)szBuf);
}

写数据:

void CChildView::OnSwriteNamePipe()
{
	char szBuf[] = "霸道小明超秀";
	DWORD dwWrite;
	if (!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL)) {
		MessageBox(_T("写入数据失败"));
		return;
	}
}

客户端 

 连接管道:

hNamedPipe是一个HANDLE类型是类属性,在构造函数里初始化,在析构函数里销毁。

   

  

void CChildView::OnConnectNamePipe()
{ 
	//连接命名管道
	LPCTSTR szNamedPipe = TEXT("\\\\.\\pipe\\mypipe");
	if (0 == WaitNamedPipe(szNamedPipe, NMPWAIT_WAIT_FOREVER)) {
		MessageBox(_T("当前没有可以利用的管道"));
		return;
	}
	hNamedPipe = CreateFile(szNamedPipe,
		GENERIC_READ | GENERIC_WRITE,
		0,
		NULL,
		OPEN_EXISTING,
		FILE_ATTRIBUTE_NORMAL,
		NULL);
	if (hNamedPipe == INVALID_HANDLE_VALUE) {
		TRACE("Create File failed with %d\n", GetLastError());
		MessageBox(_T("打开命名管道失败!"));
		hNamedPipe = NULL;
		return;
	}
	//连接成功
}

读数据:

void CChildView::OnReadNamePipe()
{
	char szBuf[100] = { 0 };
	DWORD dwRead;
	if (!ReadFile(hNamedPipe, szBuf, 100, &dwRead, NULL)) {
		MessageBox(_T("读取数据失败"));
		return;
	}
	MessageBox((CStringW)szBuf);
}

写数据:

void CChildView::OnWriteNamePipe()
{
	char szBuf[] = "霸道小明超秀";
	DWORD dwWrite;
	if(!WriteFile(hNamedPipe, szBuf, strlen(szBuf) + 1, &dwWrite, NULL)) {
		MessageBox(_T("写入数据失败"));
		return;
	}
}

执行结果:

 

好的,你的问题是关于 C# 管道通信中的命名管道,我会为你进行解答。 首先,命名管道是一种在本地计算机或网络上进行进程通信的机制。它允许一个进程向另一个进程发送数据或接收数据。在 C# 中,我们可以使用命名管道来实现进程通信。 在使用命名管道之前,我们需要先创建一个命名管道。可以使用 .NET Framework 中的 `NamedPipeServerStream` 类来创建一个命名管道。以下是创建命名管道的代码示例: ```csharp // 创建命名管道 NamedPipeServerStream pipeServer = new NamedPipeServerStream("testpipe", PipeDirection.InOut); ``` 上面的代码将创建一个名为 "testpipe" 的命名管道,并指定了管道的方向为双向。 接下来,我们可以使用 `NamedPipeServerStream` 类的 `WaitForConnection` 方法等待客户端连接到管道。一旦有客户端连接,我们就可以使用 `NamedPipeServerStream` 类的 `Read` 和 `Write` 方法来进行数据的读写。 以下是一个简单的命名管道通信示例: ```csharp // 创建命名管道 NamedPipeServerStream pipeServer = new NamedPipeServerStream("testpipe", PipeDirection.InOut); // 等待客户端连接 pipeServer.WaitForConnection(); // 从客户端读取数据 byte[] buffer = new byte[1024]; int bytesRead = pipeServer.Read(buffer, 0, buffer.Length); string data = Encoding.UTF8.GetString(buffer, 0, bytesRead); Console.WriteLine("Received data: " + data); // 向客户端发送数据 string response = "Hello, client!"; byte[] responseBuffer = Encoding.UTF8.GetBytes(response); pipeServer.Write(responseBuffer, 0, responseBuffer.Length); // 关闭管道 pipeServer.Close(); ``` 在上面的代码中,我们首先创建了一个名为 "testpipe" 的命名管道。然后,我们使用 `WaitForConnection` 方法等待客户端连接,并读取客户端发送的数据。接着,我们向客户端发送响应数据,并关闭管道。 总的来说,使用命名管道可以很方便地实现进程通信。在实际应用中,我们可以根据需要对命名管道进行配置,以实现更复杂的通信逻辑。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值