您的位置:首页 > Web前端

进程间通信 - 命名管道实现

2012-08-01 11:30 465 查看
总结

对于命名管道来说的话,简单理解的话,其实是可以将其看做是一种 Socket 的,

而对于命名管道也就是那几个 API 在使用,对于一些不常用的 API

感兴趣的也可以从 MSDN 中获取到这部分信息。

对于进程间的通信的话,其实也就可以利用介绍的这四种方式来实现了,

第一种是利用剪贴板实现本机进程间的通信。

第二种是利用邮槽实现本机或跨网络进程间的通信。

第三种是利用匿名管道实现本机父子进程之间的通信。

第四种是利用命名管道实现本机或跨网络进程间的通信。

然后的话,我还打算介绍一种比较偏门的实现进程间通信的手段,

当然,这要到下一篇博文中才会作出介绍。

最后的话,就是在前面的一篇博文中有一位朋友说可以利用 WCF 来实现进程之间的通信,

这个呢理论上是可以实现的,但是本人也没有做过这方面的 Demo

所以估计得看以后有时间的话,也可以拿过来写写文章的。      

命名管道概述

命名管道是通过网络来完成进程之间的通信的,命名管道依赖于底层网络接口,

其中包括有 DNS 服务,TCP/IP 协议等等机制,但是其屏蔽了底层的网络协议细节,

对于匿名管道而言,其只能实现在父进程和子进程之间进行通信,而对于命名管道而言,

其不仅可以在本地机器上实现两个进程之间的通信,还可以跨越网络实现两个进程之间的通信。

命名管道使用了 Windows 安全机制,因而命名管道的服务端可以控制哪些客户有权与其建立连接,

而哪些客户端是不能够与这个命名管道建立连接的。

利用命名管道机制实现不同机器上的进程之间相互进行通信时,

可以将命名管道作为一种网络编程方案时,也就是看做是 Socket 就可以了,

它实际上是建立了一个客户机/服务器通信体系,并在其中可靠的传输数据。

命名管道的通信是以连接的方式来进行的,

服务器创建一个命名管道对象,然后在此对象上等待连接请求,

一旦客户连接过来,则两者都可以通过命名管道读或者写数据。

                

命名管道提供了两种通信模式:字节模式和消息模式。

在字节模式下,数据以一个连续的字节流的形式在客户机和服务器之间流动,

而在消息模式下,客户机和服务器则通过一系列的不连续的数据单位,进行数据的收发,

每次在管道上发出一个消息后,它必须作为一个完整的消息读入。

 

命名管道使用流程

服务端:

服务端进程调用 CreateNamedPipe 函数来创建一个有名称的命名管道,

在创建命名管道的时候必须指定一个本地的命名管道名称(不然就不叫命名管道了),

Windows 允许同一个本地的命名管道名称有多个命名管道实例,

所以,服务器进程在调用 CreateNamedPipe 函数时必须指定最大允许的实例数(0 -255),

如果 CreateNamedPipe 函数成功返回后,服务器进程得到一个指向一个命名管道实例的句柄,

然后,服务器进程就可以调用 ConnectNamedPipe 来等待客户的连接请求,

这个 ConnectNamedPipe 既支持同步形式,又支持异步形式,

若服务器进程以同步形式调用 ConnectNamedPipe 函数,

(同步方式也就是如果没有得到客户端的连接请求,则会一直等到)

那么,当该函数返回时,客户端与服务器之间的命名管道连接也就已经建立起来了。

在已经建立了连接的命名管道实例中,

服务端进程就会得到一个指向该管道实例的句柄,这个句柄称之为服务端句柄。

同时,服务端进程可以调用 DisconnectNamedPipe 函数,

将一个管道实例与当前建立连接的客户端进程断开,从而可以重新连接到新的客户进程。

当然在服务端也是可以调用 CloseHandle 来关闭一个已经建立连接的命名管道实例。

#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <strsafe.h>

#define BUFSIZE 512

DWORD WINAPI InstanceThread(LPVOID);
VOID GetAnswerToRequest(LPTSTR, LPTSTR, LPDWORD);

int _tmain(VOID)
{
BOOL   fConnected = FALSE;
DWORD  dwThreadId = 0;
HANDLE hPipe = INVALID_HANDLE_VALUE, hThread = NULL;
LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\mynamedpipe");

// The main loop creates an instance of the named pipe and
// then waits for a client to connect to it. When the client
// connects, a thread is created to handle communications
// with that client, and this loop is free to wait for the
// next client connect request. It is an infinite loop.

for (;;)
{
_tprintf( TEXT("\nPipe Server: Main thread awaiting client connection on %s\n"), lpszPipename);
hPipe = CreateNamedPipe(
lpszPipename,             // pipe name
PIPE_ACCESS_DUPLEX,       // read/write access
PIPE_TYPE_MESSAGE |       // message type pipe
PIPE_READMODE_MESSAGE |   // message-read mode
PIPE_WAIT,                // blocking mode
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, GLE=%d.\n"), GetLastError());
return -1;
}

// Wait for the client to connect; if it succeeds,
// the function returns a nonzero value. If the function
// returns zero, GetLastError returns ERROR_PIPE_CONNECTED.

fConnected = ConnectNamedPipe(hPipe, NULL) ?
TRUE : (GetLastError() == ERROR_PIPE_CONNECTED);

if (fConnected)
{
printf("Client connected, creating a processing thread.\n");

// Create a thread for this client.
hThread = CreateThread(
NULL,              // no security attribute
0,                 // default stack size
InstanceThread,    // thread proc
(LPVOID) hPipe,    // thread parameter
0,                 // not suspended
&dwThreadId);      // returns thread ID

if (hThread == NULL)
{
_tprintf(TEXT("CreateThread failed, GLE=%d.\n"), GetLastError());
return -1;
}
else CloseHandle(hThread);
}
else
// The client could not connect, so close the pipe.
CloseHandle(hPipe);
}

return 0;
}

DWORD WINAPI InstanceThread(LPVOID lpvParam)
// This routine is a thread processing function to read from and reply to a client
// via the open pipe connection passed from the main loop. Note this allows
// the main loop to continue executing, potentially creating more threads of
// of this procedure to run concurrently, depending on the number of incoming
// client connections.
{
HANDLE hHeap      = GetProcessHeap();
TCHAR* pchRequest = (TCHAR*)HeapAlloc(hHeap, 0, BUFSIZE*sizeof(TCHAR));
TCHAR* pchReply   = (TCHAR*)HeapAlloc(hHeap, 0, BUFSIZE*sizeof(TCHAR));

DWORD cbBytesRead = 0, cbReplyBytes = 0, cbWritten = 0;
BOOL fSuccess = FALSE;
HANDLE hPipe  = NULL;

// Do some extra error checking since the app will keep running even if this
// thread fails.

if (lpvParam == NULL)
{
printf( "\nERROR - Pipe Server Failure:\n");
printf( "   InstanceThread got an unexpected NULL value in lpvParam.\n");
printf( "   InstanceThread exitting.\n");
if (pchReply != NULL) HeapFree(hHeap, 0, pchReply);
if (pchRequest != NULL) HeapFree(hHeap, 0, pchRequest);
return (DWORD)-1;
}

if (pchRequest == NULL)
{
printf( "\nERROR - Pipe Server Failure:\n");
printf( "   InstanceThread got an unexpected NULL heap allocation.\n");
printf( "   InstanceThread exitting.\n");
if (pchReply != NULL) HeapFree(hHeap, 0, pchReply);
return (DWORD)-1;
}

if (pchReply == NULL)
{
printf( "\nERROR - Pipe Server Failure:\n");
printf( "   InstanceThread got an unexpected NULL heap allocation.\n");
printf( "   InstanceThread exitting.\n");
if (pchRequest != NULL) HeapFree(hHeap, 0, pchRequest);
return (DWORD)-1;
}

// Print verbose messages. In production code, this should be for debugging only.
printf("InstanceThread created, receiving and processing messages.\n");

// The thread's parameter is a handle to a pipe object instance.

hPipe = (HANDLE) lpvParam;

// Loop until done reading
while (1)
{
// Read client requests from the pipe. This simplistic code only allows messages
// up to BUFSIZE characters in length.
fSuccess = ReadFile(
hPipe,        // handle to pipe
pchRequest,    // buffer to receive data
BUFSIZE*sizeof(TCHAR), // size of buffer
&cbBytesRead, // number of bytes read
NULL);        // not overlapped I/O

if (!fSuccess || cbBytesRead == 0)
{
if (GetLastError() == ERROR_BROKEN_PIPE)
{
_tprintf(TEXT("InstanceThread: client disconnected.\n"), GetLastError());
}
else
{
_tprintf(TEXT("InstanceThread ReadFile failed, GLE=%d.\n"), GetLastError());
}
break;
}

// Process the incoming message.
GetAnswerToRequest(pchRequest, pchReply, &cbReplyBytes);

// Write the reply to the pipe.
fSuccess = WriteFile(
hPipe,        // handle to pipe
pchReply,     // buffer to write from
cbReplyBytes, // number of bytes to write
&cbWritten,   // number of bytes written
NULL);        // not overlapped I/O

if (!fSuccess || cbReplyBytes != cbWritten)
{
_tprintf(TEXT("InstanceThread WriteFile failed, GLE=%d.\n"), GetLastError());
break;
}
}

// Flush the pipe to allow the client to read the pipe's contents
// before disconnecting. Then disconnect the pipe, and close the
// handle to this pipe instance.

FlushFileBuffers(hPipe);
DisconnectNamedPipe(hPipe);
CloseHandle(hPipe);

HeapFree(hHeap, 0, pchRequest);
HeapFree(hHeap, 0, pchReply);

printf("InstanceThread exitting.\n");
return 1;
}

VOID GetAnswerToRequest( LPTSTR pchRequest,
LPTSTR pchReply,
LPDWORD pchBytes )
// This routine is a simple function to print the client request to the console
// and populate the reply buffer with a default data string. This is where you
// would put the actual client request processing code that runs in the context
// of an instance thread. Keep in mind the main thread will continue to wait for
// and receive other client connections while the instance thread is working.
{
_tprintf( TEXT("Client Request String:\"%s\"\n"), pchRequest );

// Check the outgoing message to make sure it's not too long for the buffer.
if (FAILED(StringCchCopy( pchReply, BUFSIZE, TEXT("default answer from server") )))
{
*pchBytes = 0;
pchReply[0] = 0;
printf("StringCchCopy failed, no outgoing message.\n");
return;
}
*pchBytes = (lstrlen(pchReply)+1)*sizeof(TCHAR);
}

 

客户端

客户端进程调用 CreateFile 函数连接到一个正在等待连接的命名管道上,

在这里客户端需要指定将要连接的命名管道的名称,

CreateFile 成功返回后,客户进程就得到了一个指向已经建立连接的命名管道实例的句柄,

到这里,服务器进程的 ConnectNamedPipe 也就完成了其建立连接的任务。

客户端进程除了调用 CreateFile 函数来建立管道连接以外,

还可以调用 WaitNamedPipe 函数来测试指定名称的管道实例是否可用。

在已经建立了连接的命名管道实例中,客户端进程就会得到一个指向该管道实例的句柄,

这个句柄称之为客户端句柄。

在客户端可以调用 CloseHandle 来关闭一个已经建立连接的命名管道实例。

 

#include <windows.h>
#include <stdio.h>
#include <conio.h>
#include <tchar.h>

#define BUFSIZE 512

int _tmain(int argc, TCHAR *argv[])
{
HANDLE hPipe;
LPTSTR lpvMessage=TEXT("Default message from client.");
TCHAR  chBuf[BUFSIZE];
BOOL   fSuccess = FALSE;
DWORD  cbRead, cbToWrite, cbWritten, dwMode;
LPTSTR lpszPipename = TEXT("\\\\.\\pipe\\mynamedpipe");

if( argc > 1 )
lpvMessage = argv[1];

// Try to open a named pipe; wait for it, if necessary.

while (1)
{
hPipe = CreateFile(
lpszPipename,   // pipe name
GENERIC_READ |  // read and write access
GENERIC_WRITE,
0,              // no sharing
NULL,           // default security attributes
OPEN_EXISTING,  // opens existing pipe
0,              // default attributes
NULL);          // no template file

// Break if the pipe handle is valid.

if (hPipe != INVALID_HANDLE_VALUE)
break;

// Exit if an error other than ERROR_PIPE_BUSY occurs.

if (GetLastError() != ERROR_PIPE_BUSY)
{
_tprintf( TEXT("Could not open pipe. GLE=%d\n"), GetLastError() );
return -1;
}

// All pipe instances are busy, so wait for 20 seconds.

if ( ! WaitNamedPipe(lpszPipename, 20000))
{
printf("Could not open pipe: 20 second wait timed out.");
return -1;
}
}

// The pipe connected; change to message-read mode.

dwMode = PIPE_READMODE_MESSAGE;
fSuccess = SetNamedPipeHandleState(
hPipe,    // pipe handle
&dwMode,  // new pipe mode
NULL,     // don't set maximum bytes
NULL);    // don't set maximum time
if ( ! fSuccess)
{
_tprintf( TEXT("SetNamedPipeHandleState failed. GLE=%d\n"), GetLastError() );
return -1;
}

// Send a message to the pipe server.

cbToWrite = (lstrlen(lpvMessage)+1)*sizeof(TCHAR);
_tprintf( TEXT("Sending %d byte message: \"%s\"\n"), cbToWrite, lpvMessage);

fSuccess = WriteFile(
hPipe,                  // pipe handle
lpvMessage,             // message
cbToWrite,              // message length
&cbWritten,             // bytes written
NULL);                  // not overlapped

if ( ! fSuccess)
{
_tprintf( TEXT("WriteFile to pipe failed. GLE=%d\n"), GetLastError() );
return -1;
}

printf("\nMessage sent to server, receiving reply as follows:\n");

do
{
// Read from the pipe.

fSuccess = ReadFile(
hPipe,    // pipe handle
chBuf,    // buffer to receive reply
BUFSIZE*sizeof(TCHAR),  // size of buffer
&cbRead,  // number of bytes read
NULL);    // not overlapped

if ( ! fSuccess && GetLastError() != ERROR_MORE_DATA )
break;

_tprintf( TEXT("\"%s\"\n"), chBuf );
} while ( ! fSuccess);  // repeat loop if ERROR_MORE_DATA

if ( ! fSuccess)
{
_tprintf( TEXT("ReadFile from pipe failed. GLE=%d\n"), GetLastError() );
return -1;
}

printf("\n<End of message, press ENTER to terminate connection and exit>");
_getch();

CloseHandle(hPipe);

return 0;
}


 

 
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息