说明
命名管道可以用在本地的进程之间,也可以用在跨网络的主机进程之间,孙鑫老师在视频里讲的如果跨网络的话管道名字中要有对方主机名,那么到底是局域网内还是可用于广域网呢? 暂时不知道
我在练习命名管道时容易犯的错误
服务器进程创建命名管道并等待客户进程连接,在服务器进程中,读写管道时的文件句柄为管道句柄,而不是创建文件,即不能调用 CreateFile。相反在客户进程中,客户进程连接上服务进程创建的管道后需要创建以管道名为文件名的文件,用返回的文件句柄进行 读写操作。
服务器进程步骤
①创建命名管道(CreateNamePipe)
②等待客户进程连接管道(ConnectNamedPipe)
③客户进程连接到命名管道
④开始读 / 写操作 (ReadFile / WriteFile)
⑤关闭管道(CloseHandle)
说明:
第②步中等待客户进程连接是一个费时(time-consumming)操作,即当没有客户进程来连接的时候会一直停在此处(同步操作),因为可以使用重叠操作,即重叠操作时:函数会立即返回而不管是不是执行完毕。
在命名管道编程中(服务器端),实现重叠操作可以有两种方式,都是在CreateNamePipe函数中指定相应标志位,一是在第二个参数中指定FILE_FLAG_OVERLAPPED标识, 这样遇到函数ConnectNamePipe函数就会立即返回,可以用WaitForSingleObject函数等待OVERLAPPED结构体中的hEvent成员有无信号判断是不是有客户进程来连接,有信号则说明有客户进程连接,否则没有。
第二种实现重叠操作的标识是在CreateNamePipe函数的第三个参数中指定PIPE_NOWAIT标识,一旦指定了该标识 ,ConnectNamedPipe, ReadFile, WriteFile函数都编程重叠操作,都可以立即返回。 而用第一种标识只能使ConnectNamedPipe 函数为重叠操作。
同时要注意:通过实验发现,如果同时指定上述两种标识,那么可能会有未知的情况发生,因此推荐第二种。
读写句柄用的是管道句柄, 而非新创建文件句柄。
在客户进程编程中,连接服务进程的函数WaitNamedPipe在MSDN中没有看到如何实现重叠操作,因此没有实验,如果超时值为0,在没有可用连接的管道的情况下也可以立即返回。
而在客户进程中需要调用CreateFile函数创建文件,可以在此函数中指定重叠操作标识FILE_FLAG_OVERLAPPED使ReadFile / WriteFile
函数实现重叠操作, 仅仅是可以,至于是不是只要,或者是否开新的线程来读写从而避免阻塞主线程就要按照实际情况来。
客户进程步骤
①等待连接服务进程(WaitNamedPipe)
②连接好后,创建文件进行读写(CreateFile)
③开始读 / 写操作(ReadFile / WriteFile)
说明:
WaitNamedPipe函数是可指定一个超时值。
函数和数据结构
①创建命名管道
HANDLE CreateNamePipe(
LPCTSTR lpName, //管道名,格式:\\.\pipe\pipename,在引号中反斜杠条数加倍,其中pipename可变,其他不变。<256字符
DWORD dwOpenMode, //管道打开模式,可指定单向读/写,双向,重叠操作等
DWORD dwPipeMode, //管道标识模式,读 / 写 的 字节 / 消息模式,重叠标识
DWORD nMaxInstance, //该名字管道实例的最大个数,从1到PIPE_UNLIMITED_INSTANCES。
DWORD nOutBufferSize, //输出缓冲区大小
DWORD nInBufferSize, //输入缓冲区大小
DWORD nDefaultTimeout, //超时值,客户进程中函数WaitNamedPipe如果指定了 //NMPWAIT_USE_DEFAULT_WAIT标识,那么则使用此超时值
LPSECURITY_ATTRIBUTES lpSecurityAttributes //安全属性,可为NULL,默认属性
);
eg.
HANDLE hPipe = INVALID_HANDLE_VALUE;
const char* pipeName = "\\\\.\\pipe\\MyPipe"; //其中"."标识本地主机
hPipe = CreateNamePipe(
pipeName,
PIPE_ACCESS_DUPLEX, //双向, 读 / 写
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_NOWAIT, //消息模式,ConnectNamedPipe/ReadFile/WriteFile
//重叠操作
PIPE_UNLIMITED_INSTANCES, //最大个数
1024, //输出缓冲区大小
1024, //输入缓冲区大小
0, //超时值
NULL //默认安全属性
);
if(INVALID_HANDLE_VALUE == hPipe)
{
MessageBox("创建命名管道错误!");
return;
}
②等待客户进程连接
BOOL ConnectNamedPipe(
HANDLE hNamedPipe, //命名管道句柄
LPOVERLAPPED lpOverlapped //OVERLAPPED结构体指针
);
说明:由于在CreateNamePipe函数中使用了PIPE_NOWAIT标识,那么这不再使用重叠结构体,下面还是对该结构体的一点使用
简单介绍。
在使用时第二个参数为NULL即可。
typedef struct _OVERLAPPED{
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
DWORD offset;
DWORD offsetHigh;
HANDLE hEvent; //人工重置事件,初始无信号。当重叠操作完成时该事件被置为有信号。
}OVERLAPPED;
如果ConnectNamedPipe函数使用该结构体来实现重叠操作,那么方法如下:
OVERLAPPED ol;
ZeroMemory(&ol, sizeof(OVERLAPPED)); //结构体成员全清零
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL); //创建一个默认安全属性,无名,人工重置,初始无信号的事件
BOOL ret = ConnectNamedPipe(
hPipe,
&ol
);
//如果该函数返回FALSE
if(!ret)
{
if(ERROR_IO_PENDING == GetLastError())
{
//如果该函数返回FALSE,而且返回这个错误,表示重叠操作未完成,属于正常,并不是真正的错误,不影响程序执行,可以按
//没有错误对待。
}
else if(ERROR_PIPE_CONNECTED == GetLastError())
{
//如果客户进程连接服务进程管道是在这样一个时机:CreateNamePipe和ConnectNamedPipe之间,就会返回这样的错误,
//但实际情况是已经连上了管道,也不影响程序执行,可以按照没有错误对待。
//由于本人知识所限,暂时认为只有这样一种情况才会发生这个错误。
}
else
{
//影响程序执行的错误发生!
CloseHandle(hPipe);
hPipe = INVALID_HANDLE_VALUE;
return;
}
}
③连接服务进程管道
BOOL WaitNamedPipe(
LPCTSTR lpNamePipeName, //管道名
DWORD nTimeOut //超时值,如果为NMPWAIT_USE_DEFAULT_WAIT则使用的超时值为CreateNamePipe函数中
//最后一个参数的值,如果为NMPWAIT_WAIT_FOREVER则是永远等待,直到可以连接。 也可以指定一个值。
);
eg.
BOOT ret = WaitNamedPipe(
"\\\\.\\pipe\\MyPipe", //管道名,必须同服务进程管道名
NMPWAIT_USE_DEFAULT_WAIT //默认超时值,在此同前为0
);
实例
//服务进程
//创建命名管道
HANDLE m_hPipe = INVALID_HANDLE_VALUE;
void CMy80NamedPipeFirstDlg::OnBtnCreatepipe()
{
char* pipeName = _T("\\\\.\\pipe\\MyPipe");
m_hPipe = CreateNamedPipe(
pipeName, PIPE_ACCESS_DUPLEX /* | FILE_FLAG_OVERLAPPED*/, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_NOWAIT, PIPE_UNLIMITED_INSTANCES, 1024, 1024, 0, NULL);
if (INVALID_HANDLE_VALUE == m_hPipe)
{
MessageBox(_T("创建命名管道失败!"));
return;
}
HANDLE hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);
ol.hEvent = hEvent;
BOOL ret = ConnectNamedPipe(m_hPipe, NULL);
if (!ret)
{
if (ERROR_PIPE_CONNECTED == GetLastError())
{
MessageBox(_T("返回错误,但客户进程已经连接!"));
GetDlgItem(IDC_BTN_READ)->EnableWindow(TRUE);
GetDlgItem(IDC_BTN_WRITE)->EnableWindow(TRUE);
}
else if (ERROR_NO_DATA == GetLastError())
{
MessageBox(_T("ERROR_NO_DATA错误!"));
GetDlgItem(IDC_BTN_READ)->EnableWindow(FALSE);
GetDlgItem(IDC_BTN_WRITE)->EnableWindow(FALSE);
CloseHandle(hEvent);
CloseHandle(m_hPipe);
m_hPipe = INVALID_HANDLE_VALUE;
return;
}
else if (ERROR_IO_PENDING)
{
MessageBox(_T("ERROR_IO_PENDING错误!"));
GetDlgItem(IDC_BTN_READ)->EnableWindow(TRUE);
GetDlgItem(IDC_BTN_WRITE)->EnableWindow(TRUE);
}
else
{
MessageBox(_T("未知错误!"));
GetDlgItem(IDC_BTN_READ)->EnableWindow(FALSE);
GetDlgItem(IDC_BTN_WRITE)->EnableWindow(FALSE);
CloseHandle(hEvent);
CloseHandle(m_hPipe);
m_hPipe = INVALID_HANDLE_VALUE;
return;
}
}
else
{
GetDlgItem(IDC_BTN_READ)->EnableWindow(TRUE);
GetDlgItem(IDC_BTN_WRITE)->EnableWindow(TRUE);
}
}
//写管道
void CMy80NamedPipeFirstDlg::OnBtnWrite()
{
char writeBuf[] = "hello client!";
DWORD length = 0;
WriteFile(m_hPipe, writeBuf, sizeof(writeBuf), &length, NULL);
}
//读管道
void CMy80NamedPipeFirstDlg::OnBtnRead()
{
char readBuf[200];
memset(readBuf, '\0', sizeof(readBuf));
DWORD length = 0;
ReadFile(m_hPipe, readBuf, sizeof(readBuf), &length, NULL);
MessageBox(readBuf);
}
//客户进程
//连接管道并创建文件
HANDLE hFile = INVALID_HANDLE_VALUE;
void CMy80NamedPipeSecondDlg::OnBtnConnect()
{
if(WaitNamedPipe(_T("\\\\.\\pipe\\MyPipe"), NMPWAIT_WAIT_FOREVER))
{
m_hFile = CreateFile(
_T("\\\\.\\pipe\\MyPipe"), GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL
);
if (INVALID_HANDLE_VALUE != m_hFile)
{
GetDlgItem(IDC_BTN_WRITE)->EnableWindow(TRUE);
GetDlgItem(IDC_BTN_READ)->EnableWindow(TRUE);
}
}
else
{
GetDlgItem(IDC_BTN_WRITE)->EnableWindow(FALSE);
GetDlgItem(IDC_BTN_READ)->EnableWindow(FALSE);
}
}
//读管道
void CMy80NamedPipeSecondDlg::OnBtnRead()
{
char readBuf[200];
memset(readBuf, '\0', sizeof(readBuf));
DWORD length = 0;
ReadFile(m_hFile, readBuf, sizeof(readBuf), &length, NULL);
MessageBox(readBuf);
}
//写管道
void CMy80NamedPipeSecondDlg::OnBtnWrite()
{
char writeBuf[] = "hello server!";
DWORD length = 0;
WriteFile(m_hFile, writeBuf, sizeof(writeBuf), &length, NULL);
}