Windows命名管道

Windows命名管道

作者: yym439 时间: 2021-08-30

一、 命名管道

命名管道(NamedPipes)是在管道服务器和一台或多台管道客户机之间进行单向或双向通信的一种命名的管道。一个命名管道的所有实例共享同一个管道名,但是每一个实例均拥有独立的缓存与句柄,并且为客户——服务通信提供有一个分离的管道。实例的使用保证了多个管道客户能够在同一时间使用同一个命名管道。

二、服务端

2.1 CreateNamedPipe

  • 服务器进程调用CreateNamedPipe函数来创建一个有名称的命名管道在创建命名管道的时候必须指定一个本地的命名管道名称。
  • 该函数用来创建一个命名管道的实例,并返回这个命名管道的句柄。
HANDLE CreateNamedPipe(
  LPCTSTR lpName, // 命名管道的名称
  DWORD dwOpenMode, // 指定管道的访问方式
  DWORD dwPipeMode, // pipe-specific modes
  DWORD nMaxInstances, // maximum number of instances
  DWORD nOutBufferSize, // output buffer size
  DWORD nInBufferSize, // input buffer size
  DWORD nDefaultTimeOut, // time-out interval
  LPSECURITY_ATTRIBUTES lpSecurityAttributes  // SD
);

2.2 ConnectNamedPipe

  • 服务器进程就可以调用ConnectNamedPipe来等待客户的连接请求,这个ConnectNamedPipe既支持同步形式,又支持异步形式
  • hNamedPipe 所标识的命名管道是用 FILE_FLAG_OVERLAPPED, (也就是重叠模式或者说异步方式)标记打开的,则这个参数不能为 NULL ,必须是一个有效的指向一个 OVERLAPPED 结构的指针,否则该函数可能会错误的执行。
BOOL ConnectNamedPipe(
  HANDLE hNamedPipe, // handle to named pipe
  LPOVERLAPPED lpOverlapped   // overlapped structure
);

2.3 ReadFile/WriteFile

  • 服务器端向客户端读(ReadFile)/写(WriteFile)数据
BOOL ReadFile(
  HANDLE hFile, // handle to file
  LPVOID lpBuffer, // data buffer
  DWORD nNumberOfBytesToRead,  // number of bytes to read
  LPDWORD lpNumberOfBytesRead, // number of bytes read
  LPOVERLAPPED lpOverlapped // overlapped buffer
);

2.4 DisconnectNamedPipe

  • 将一个管道实例与当前建立连接的客户端进程断开,从而可以重新连接到新的客户端进程。当然,服务器也可以调用CloseHandle来关闭一个已经建立连接的命名管道实例
BOOL DisconnectNamedPipe(  HANDLE hNamedPipe   // handle to named pipe);

2.5 服务端代码

// pipelineServer.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
#include "pch.h"
#include <iostream>
#include <Windows.h>

/*
*进程间通信 命名管道 server
*/
int main()
{
	char buff[256];

	DWORD len = 0;
	HANDLE h_Pipe = CreateNamedPipe(
		TEXT("\\\\.\\Pipe\\mypipe"),//管道名字
		PIPE_ACCESS_DUPLEX,//管道类型
		PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,//管道参数
		PIPE_UNLIMITED_INSTANCES,//管道能创建的最大实例数量
		0,//输出缓冲区的长度 0表示默认
		0,//输入缓冲区的长度 0表示默认
		NMPWAIT_WAIT_FOREVER,//超时时间
		NULL);

	if (h_Pipe == INVALID_HANDLE_VALUE)
	{
		std::cout << "Failed to CreateNamedPipe!"<<std::endl;
		return -1;
	}

	std::cout << "CreateNamedPipe success!"<<std::endl;
	std::cout << "waiting client connect..."<<std::endl;
	
	if (ConnectNamedPipe(h_Pipe, NULL) == NULL)//阻塞客户端来连接
	{
		std::cout << "Failed to Connect!" << std::endl;
	}
	else
	{
		std::cout << "Connect success!" << std::endl;
	}

	while (true)
	{
		if (ReadFile(h_Pipe, buff, 256, &len, NULL) == FALSE)//接收客户端发送的内容
		{
			std::cout << "Failed to read data!" << std::endl;
			break;
		}
		else
		{
			std::cout << "read data:" << buff << ",data size:" << len <<std::endl;

			char d[256] = "i am server ,hello client";
			DWORD len_ = 0;
			WriteFile(h_Pipe, d, sizeof(d), &len_, 0);//向客户端发送内容
			
			std::cout << "send data:" << d << ",data size:" << len_ << std::endl;

			Sleep(1000);
		}
	}
	CloseHandle(h_Pipe);//关闭管道释放资源
	system("pause");
}

三、客户端

3.1 WaitNamedPipe

  • 判断是否有可用的命名管道。直到等待的时间间隔已过,或者指定的命名管道的实例可以用来连接了
BOOL WaitNamedPipe(
  LPCTSTR lpNamedPipeName,  // pipe name
  DWORD nTimeOut // time-out interval
);

3.2 CreateFile

  • 连接到一个正在等待连接的命名管道上
  • 返回一个指向已经建立连接的命名管道实例的句柄
HANDLE CreateFile(
  LPCTSTR lpFileName, // file name
  DWORD dwDesiredAccess, // access mode
  DWORD dwShareMode, // share mode
  LPSECURITY_ATTRIBUTES lpSecurityAttributes, // SD
  DWORD dwCreationDisposition, // how to create
  DWORD dwFlagsAndAttributes, // file attributes
  HANDLE hTemplateFile // handle to template file
);

3.3 ReadFile/WriteFile

BOOL WriteFile(
  HANDLE hFile, // handle to file
  LPCVOID lpBuffer, // data buffer
  DWORD nNumberOfBytesToWrite, // number of bytes to write
  LPDWORD lpNumberOfBytesWritten,  // number of bytes written
  LPOVERLAPPED lpOverlapped // overlapped buffer
);

3.4 CloseHandle

  • 关闭一个已经建立连接的命名管道实例
BOOL CloseHandle(  HANDLE hObject   // handle to object);

3.5 客户端程序

#include <windows.h>
#include <tchar.h>
#include <iostream>
using namespace std;

LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\mynamedpipe");
const int nBufferSize = 1024;

int main()
{
    HANDLE hPipe = INVALID_HANDLE_VALUE;
    while (true)
    {
        // 创建连接管道的句柄
        HANDLE hPipe = CreateFile(lpszPipename,
            GENERIC_READ | GENERIC_WRITE,// 管道可读可写
            0,// 共享模式,0表示不共享
            NULL,// 默认的访问控制权限    
            OPEN_EXISTING,// 打开已经存在的管道
            0,// 默认的文件属性 
            NULL);// 不设置临时模板文件,只有创建新文件时有用
        if (hPipe != INVALID_HANDLE_VALUE)
            break;

        // 如果返回错误不是ERROR_PIPE_BUSY
        if (GetLastError() != ERROR_PIPE_BUSY) 
        {
            cout << "open pipe faild:" << GetLastError() << endl;
            return -1;
        }
        WaitNamedPipe(lpszPipename, NMPWAIT_WAIT_FOREVER);
    }

    DWORD dwMode = PIPE_READMODE_MESSAGE;
    BOOL bRet = SetNamedPipeHandleState(hPipe, &dwMode,  NULL,  NULL);  
    if (!bRet)
    {
        cout << "SetNamedPipeHandleState faild:" << GetLastError() << endl;
        return -1;
    }

    // 写入管道
    DWORD dwBytesWirte = 0;
    TCHAR szWriteBuf[] = L"Hello";
    bRet = WriteFile(hPipe,
        szWriteBuf,// 写入数据缓冲区     
        sizeof(szWriteBuf),// 要写入的字节数 
        &dwBytesWirte,// 写入的数据
        NULL);// 不用重叠结构

    if (!bRet || dwBytesWirte == 0)
    {
        cout << "WriteFile failed:" << GetLastError() << endl;
        CloseHandle(hPipe);
        return -3;
    }

    // 读取管道
    DWORD dwBytesRead = 0;
    TCHAR *pszRecvBuf = new TCHAR[nBufferSize];
    bRet = ReadFile(hPipe,
        pszRecvBuf,// 接收缓存区
        nBufferSize * sizeof(TCHAR),// 接收缓冲区大小
        &dwBytesRead,// 读取到的字节数 
        NULL);// 不用重叠结构
    if (!bRet || dwBytesRead == 0)
    {
        cout << "ReadFile failed:" << GetLastError() << endl;
        CloseHandle(hPipe);
        return -4;
    }
    _tprintf(TEXT("%s\n"), pszRecvBuf);
    delete[] pszRecvBuf;

    CloseHandle(hPipe);
    return 0;
}