命名管道更加高级。它由一个名字来标识,以使得客户端和服务端应用程序可以通过它进行彼此通信。而且win32命名管道甚至可以在不同系统的进程间使用。命名管道有时候也被称为fifo。有了命名管道后。一个进程可以把数据放到管道中。另一个知道管道名字的进程把数据取走。命名管道与其他交换数据方式不同的地方在于。如果进程不知道这个管道的名字就不可能把数据取走。
管道实际是用于进程间通信的一段共享内存。创建管道的进程称为管道服务器。连接到一个管道的进程为管道客户机。可以用以下函数创建管道。
Handle CreateNamedPipe(LPCTSTR lpName,DWORD dwOpenMode,DWord dwPipeMode)
lpName命名规范:\\[host_name]\pipe\[Path]Name
比如:LPCSTR szPipeName=TEXT("\\\\.\\pipe\\ssnp\\");
第一部分\\[host_name]指定了服务器的名字。命名管道服务即在此服务器创建。其字符串部分可表示为小数点(本机),星号(当前网络字段),域名或是一个真正的服务。第二部分"\pipe"是一个硬编码字符串,第三部分为命名管道的名字。而且可以设置多级目录。
dwOpenMode:管道创建方式。可以是下面值的组合。
pipe_access_inbound:管道只能用作接收数据。
pipe_Access_outbound:管道只能用作发送数据。
pipe_Access_Duplex:管道即可以发送也可以接收数据。
上面这三个值只能够取其中一个。同时也可以包括以下一个或两个标示
File_flag_write_through:管道用于同步发送和接收数据。在系统内部对于命名管道的处理上不经过缓冲区并能直接发送。并且只有在数据被发送到目标地址时发送函数才会返回。
file_flag_overlapped管道可以用于异步输入和输出。
dwPipeMode:命名管道模式:
通信的实现流程
1:连接建立
服务端通过函数CreateNamedPipe创建一个命名管道的实例并返回用于今后操作的句柄,或为已存在的管道创建新的实例。如果在已定义超时值变为0以前。有一个实例管道可以使用。则创建成功并返回管道句柄。并用以侦听来自客户端的连接请求。该功能通过connectnamepipe实现。另一方面。客户端通过函数waitnamepipe使服务进程等待来自客户的实例连接。如果在超时值变为0以前。有一个管道可以为连接使用。则waitnamedpipe返回true.并通过调用createfile或callnamedpipe来呼叫对服务端的连接。此时服务端将接受客户端的连接请求。成功建立连接。服务端connectnamepipe返回true.客户端createFile返回一个指向管道文件的句柄。
从时序上讲。首先是客户端通过waitnamedpipe使服务端的createfile在限定时间内创建实例成功。然后双方通过connectnamedpipe和createfile成功连接。并返回用于通信的文件句柄。此时双方即可进行通信。
2:通信实现
建立连接之后。客户和服务端可以通过得到的管道文件句柄利用readfile和writefile进行彼此间的信息交换。
3:连接终止
客户端应调用closefile而服务端应接着调用disconnectnamedpipe。当然服务端也可通过单方面调用disconnectnamedpipe终止连接。最后应调用closehandle来关闭该管道。
实例解析
服务端:
(1)定义一个定时器不断的更新收到的消息。
m_strPipe = thePipe.GetRequest();
UpdateData(FALSE);
(2)创建命名管道,并创建线程
m_strReply=strReply;
LPCSTR szPipeName=TEXT("\\\\.\\pipe\\ssnp\\");
// 为接收消息创建命名管道.
m_hPipe=CreateNamedPipe(szPipeName,
PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH,
// 阻塞模式.
PIPE_WAIT|PIPE_TYPE_BYTE,
PIPE_UNLIMITED_INSTANCES,
128,128,NULL,NULL);
// 检查是否命名管道被创建.
if (m_hPipe == INVALID_HANDLE_VALUE)
{
TRACE("Unable to create a named pipe.\n");
return;
}
m_pThread=AfxBeginThread(ServerReadProc, this); // 启动线程.
(3)等待客户端连接上并读写管道。
为避免在服务程序出现阻塞现象。创建了一个进程。
UINT CMyPipe::ServerReadProc(LPVOID lpVoid)
{
DWORD dwNumBytesRead,dwNumBytesWrite;
char toDisptxt[80];
// 允许客户连接命名管道,如果不成功,终止.
TRACE("Waiting for connection... \n");
CMyPipe *Parent= (CMyPipe*)lpVoid;
//ConnectNmaepipe:服务进程准备好一个连接到客户进程的管道。并等待一个客户进程连接上为至;
//当没有客户程序连接时。服务程序就停止在此处等待外界的连接。只有在接收到来自客户端的连接时。程序才继续向下执行。对于这种情况。如果不采用线程。服务程序窗口就不能显示出来。连接成功后。用readfile和writefile分别从管道里读取数据和向管道里写入数据。
if(!ConnectNamedPipe(Parent->m_hPipe, (LPOVERLAPPED) NULL))
{
TRACE("Unable to connect a named pipe. Error: %d\n",GetLastError());
CloseHandle(Parent->m_hPipe);
return 1;
}
// 反复检查消息直到程序终止.
while(1)
{
// 读取消息并检查读取数据是否成功.
if (!ReadFile(Parent->m_hPipe, toDisptxt,sizeof(toDisptxt),
&dwNumBytesRead, (LPOVERLAPPED) NULL))
{
TRACE("Unable to read from named pipe. Error: %d\n" ,GetLastError());
CloseHandle(Parent->m_hPipe);
return 1;
}
else
{
// 保存接收的字符串.
Parent->m_strRequest=toDisptxt;
strcpy(toDisptxt,Parent->m_strReply);
// 写回一个字符串.
WriteFile(Parent->m_hPipe, toDisptxt,sizeof(toDisptxt),
&dwNumBytesWrite, (LPOVERLAPPED) NULL);
}
}
return 0;
}
客户端:
(1):在命名管道服务程序启动后。可以用createfile打开命名管道。就像创建文件一样。当文件名必须与服务端创建命名管道的名称相同。否则不能与服务相连。
LPCSTR szPipeName=TEXT("\\\\.\\pipe\\ssnp\\");
m_hPipe=CreateFile(szPipeName,
GENERIC_READ|GENERIC_WRITE,
FILE_SHARE_READ| FILE_SHARE_WRITE,NULL, OPEN_EXISTING,
FILE_ATTRIBUTE_ARCHIVE|FILE_FLAG_WRITE_THROUGH,
// FILE_FLAG_WRITE_THROUGH 设定阻塞.
NULL);
// 检查并判别命名管道文件是否被打开,如果没有被打开,终止程序.
if (m_hPipe == INVALID_HANDLE_VALUE)
TRACE("Unable to create a named pipe. Error: %d\n",GetLastError());
(2):向管道写入数据读出数据。
m_strRequest=strRequest;
char toSendtxt[80];
// 反复发送消息直到程序终止.
while(1)
{
TRACE("Sending...\n");
DWORD dwNumBytesWritten,dwNumBytesRead;
strcpy(toSendtxt,m_strRequest);
// 向管道写入消息.
if (!WriteFile(m_hPipe,toSendtxt, (DWORD)sizeof(toSendtxt),
&dwNumBytesWritten,(LPOVERLAPPED) NULL))
{
// 如果向命名管道写入时出现错误,终止程序.
TRACE("Unable to write to named pipe. Error: %d\n",GetLastError());
CloseHandle(m_hPipe);
}
else{
ReadFile(m_hPipe,toSendtxt, (DWORD)sizeof(toSendtxt),
&dwNumBytesRead,(LPOVERLAPPED) NULL);
m_strReply=toSendtxt;
break;
}
// 在再次发送消息前等待.
Sleep(4800);
}