命名管道简述
命名管道是进程间通信的另一种方式,它屏蔽了底层的网络协议细节,在我们不了解网络协议时,可以依靠命名管道实现网络通信。命名管道作为一个种网络编程方案时,它实际上是建立了一个客户机/服务器通信体系,并在其中可靠的传输数据,传输方式包括双工通信和单工通信。
此外,一个命名管道的所有实例共享同一个管道名,但是每一个实例均拥有独立的缓存和句柄,并且为客户机-服务端提供一个分离的管道,保证多个管道能够在同一时间使用同一个命名管道。
命名管道是围绕Windows文件系统设计的一种机制,采用“命名管道文件系统(Name Pipe File System,NPFS)”接口;因此客户机和服务端可利用标准的Win32文件系统函数(ReadFile和WriteFile)来进行数据收发。
命名管道的提供两种通信模式:字节模式和消息模式
- 在字节模式下,数据以一个连续的字节流的形式在客户机和服务器之间流动
- 在消息模式下,客户机和服务器则通过一系列的不连续的数据单位,进行数据的收发,每次在管道上发出一个消息后,它必须作为一个完整的消息读入
命名管道特点
和匿名管道相比,命名管道有以下特点:
- 支持同一台机器上通信,也可以跨网络间通信
- 支持双工通信、客户机->服务端、服务端->客户机三种通信方式,双工通信不需要创建两个管道
- 一个命名管道的所有实例共享同一个管道名
- 管道是FIFO结构
客户机和服务端区别
- 服务器是唯一一个有权创建命名管道的进程,也只有它能接受客户机的连接请求
- 客户机只能同一个现成的命名管道服务器建立连接
管道命名规范
命名管道的名字格式必须是\\ServerName\pipe\PipeName
形式,名称pipe是固定的字符串,不能修改;命名管道有以下两种形式:
- 若在本机通信,则ServerName为 圆点
.
,代表本地机器 - 若在不同计算机之通信,ServerName则为远程服务器的名称或者地址
编码流程
服务端流程:
-
创建命名管道:CreateNamedPipe
-
等待客户端连接:ConnectNamedPipe
-
数据读写:ReadFile / WriteFile
-
关闭连接:DisconnectNamedPipe
-
关闭管道:CloseHandle
代码如下:
#include "stdafx.h"
#include <Windows.h>
const int BUFSIZE = 1024;
const TCHAR szPipeName[128] = {_T("\\\\.\\pipe\\mypipename")} ;
int _tmain(int argc, _TCHAR* argv[])
{
int nRet = -1;
//创建一个命名管道实例
//若需要同时支持多个客户端连接,需要多次调用CreateNamedPipe
HANDLE hPipe = CreateNamedPipe(
szPipeName, // pipe name
PIPE_ACCESS_DUPLEX | // read/write access
FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE,
PIPE_UNLIMITED_INSTANCES, // max. instances
BUFSIZE, // output buffer size
BUFSIZE, // input buffer size
0, // client time-out
NULL); // default security attribute
if (hPipe == INVALID_HANDLE_VALUE)
{
_tprintf(TEXT("CreateNamedPipe failed, error code=%d.\n"), GetLastError());
return -1;
}
else
{
_tprintf(_T("create namepipe success...\n"));
}
HANDLE hEvent = CreateEvent(NULL,TRUE,FALSE,NULL);
if (NULL == hEvent)
{
CloseHandle(hPipe);
return -1;
}
//等待客户端连接请求
do
{
OVERLAPPED ovlap;
ZeroMemory(&ovlap, sizeof(ovlap));
ovlap.hEvent = hEvent;
if (!ConnectNamedPipe(hPipe,&ovlap))
{
//ERROR_IO_PENDING代表正则进行中
if (ERROR_IO_PENDING != GetLastError())
{
_tprintf(_T("等待连接客户端失败...\n"));
break;
}
}
_tprintf(_T("waiting client connected...\n"));
if (WAIT_FAILED == WaitForSingleObject(hEvent, INFINITE))
{
break;
}
nRet = 0;
} while (0);
if (nRet != 0)
{
CloseHandle(hPipe);
CloseHandle(hEvent);
return -1;
}
else
{
_tprintf(_T("a client has connected server...\n"));
}
TCHAR szMsgBuf[BUFSIZE] = {_T("123456789")};
DWORD dwWrite = 0;
//客户端和服务端完成连接
//向管道写入数据
if (!WriteFile(hPipe,szMsgBuf,wcslen(szMsgBuf)*2,&dwWrite,NULL))
{
_tprintf(_T("write file error...%d\n"),::GetLastError());
}
else
{
_tprintf(_T("write file success %s...\n"),szMsgBuf);
}
//Flush the pipe to allow the client to read the pipe's contents
// before disconnecting
FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);
CloseHandle(hEvent);
return 0;
}
客户端流程
-
打开命名管道:CreateFile
-
等待服务端响应:WaitNamedPipe
-
管道数据读写:WriteFile / ReadFile
-
关闭管道:CloseHandle
#include "stdafx.h"
#include <Windows.h>
const TCHAR szPipeName[128] = {_T("\\\\.\\pipe\\mypipename")} ;
const int BUF_SIZE = 1024;
int _tmain(int argc, _TCHAR* argv[])
{
TCHAR szBuf[BUF_SIZE] = {};
DWORD dwRead = 0;
//等待服务端有可以利用的命名管道
if (!WaitNamedPipe(szPipeName,NMPWAIT_WAIT_FOREVER))
{
_tprintf(_T("wait namepipe error...\n"));
return -1;
}
//打开可以用的命名管道
HANDLE hPipe = CreateFile(szPipeName,
GENERIC_READ| GENERIC_WRITE,
0,
NULL,
OPEN_EXISTING,
FILE_ATTRIBUTE_NORMAL,
NULL);
if (INVALID_HANDLE_VALUE == hPipe)
{
return -1;
}
//从管道中读取数据
if (!ReadFile(hPipe,szBuf,sizeof(szBuf),&dwRead,NULL))
{
_tprintf(_T("read file error...\n"));
}
else
{
_tprintf(_T("read file success %s...\n"),szBuf);
}
//关闭句柄
CloseHandle(hPipe);
return 0;
}
运行结果:
更多资料可以参考:
https://docs.microsoft.com/zh-cn/windows/win32/ipc/named-pipes
https://blog.csdn.net/j6915819/article/details/8437274