Windows进程间通信--命名管道

1 相关概述

    命名管道(Named Pipes)是一种简单的进程间通信(IPC)机制。命名管道可以在同一台计算机的不同进程之间,或者跨越一个网络的不同计算机的不同进程之间的可靠的双向或单向的数据通信。

    命名管道利用了微软网络提供者(MSNP)重定向器,因此无需涉及底层的通信协议等细节。命名管道是围绕windows文件系统设计的一种机制,采用“命名管道文件系统”(Named Pipe File System,NPFS)接口。因此,客户端和服务端均可以使用标准的WIN32文件系统API函数(如ReadFile和WriteFile)来进行数据的收发。

 命名管道的命名规范遵循“通用命名规范(UNC)” :

  \\server\pipe[\path]\name

  • 其中\\server 指定一个服务器的名字,如果是本机则用\\.表示,\\192.168.1.100表示网络上的服务器。

  • \pipe 是一个不可变化的“硬编码”字串(不区分大小写),用于指出该文件从属于NPFS

  • [\path]\name 则唯一标识一个命名管道的名称。

2 相关函数

2.1 服务端函数

2.1.1     CreateNamedPipe 创建命名管道

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
/*************************************************************************
  Purpose :  创建命名管道,如果存在指定名字的管道,则创建该管道的一个实例
  Input   :  lpName              --  管道名称
             dwOpenMode          --  打开模式
             dwPipeMode          --  消息模式
             nMaxInstances       --  最大实例数(1-255)
             nOutBufferSize      --  输出缓冲区长度,0表示用默认设置
             nInBufferSize       --  输入缓冲区长度,0表示用默认设置
             nDefaultTimeOut     --  管道的默认超时时间(毫秒),0表示默认超时时间50毫秒
             lpSecurityAttributes--  安全描述符,如无特殊需求默认为0即可
  Return  :  成功 -- 返回管道句柄 失败 -- 返回INVALID_HANDLE_VALUE 通过GetLastError()获取错误代码
  Remark  : 
  *************************************************************************/
HANDLE WINAPI CreateNamedPipe(
   _In_      LPCTSTR lpName,
   _In_      DWORD dwOpenMode,
   _In_      DWORD dwPipeMode,
   _In_      DWORD nMaxInstances,
   _In_      DWORD nOutBufferSize,
   _In_      DWORD nInBufferSize,
   _In_      DWORD nDefaultTimeOut,
   _In_opt_  LPSECURITY_ATTRIBUTES lpSecurityAttributes
);

dwOpenMode 为下列常数组合

常数之一:

  • PIPE_ACCESS_DUPLEX 管道是双向的

  • PIPE_ACCESS_INBOUND 数据从客户端流到服务器端

  • PIPE_ACCESS_OUTBOUND 数据从服务器端流到客户端

常数之二:

  • FILE_FLAG_WRITE_THROUGH 在网络中建立的字节型管道内,强迫数据在每次读写操作的时候通过网络传输。否则传输会缓存导致延迟

  • FILE_FLAG_OVERLAPPED 允许(但不要求)用这个管道进行异步(重叠式)操作

dwPipeMode 为下列常数组合

常数之一:

  • PIPE_TYPE_BYTE             数据作为一个连续的字节数据流写入管道

  • PIPE_TYPE_MESSAGE      数据用数据块(名为“消息”或“报文”)的形式写入管道    

常数之二:

  • PIPE_READMODE_BYTE 数据以单独字节的形式从管道中读出

  • PIPE_READMODE_MESSAGE 数据以名为“消息”的数据块形式从管道中读出(要求指定PIPE_TYPE_MESSAGE)

常数之三:

  • PIPE_WAIT 同步操作在等待的时候挂起线程 

  • PIPE_NOWAIT 操作立即返回。这样可为异步传输提供一种落后的实现方法,已由Win32的重叠式传输机制取代了(不推荐!) 

2.1.2     ConnectNamedPipe 等待客户连接
1
2
3
4
5
6
7
8
9
10
11
/*************************************************************************
  Purpose :  等待客户连接管道
  Input   :  hNamedPipe              --  创建管道的句柄,由CreateNamedPipe成功返回
             lpOverlapped            --  打开模式
  Return  :  TRUE -- 成功 FALSE -- 失败,通过GetLastError()获取错误码
  Remark  : 
  *************************************************************************/
  BOOL WINAPI ConnectNamedPipe(
   _In_         HANDLE hNamedPipe,
   _Inout_opt_  LPOVERLAPPED lpOverlapped
);

lpOverlapped 如设为NULL,表示将线程挂起,直到一个客户同管道连接为止。否则就立即返回;此时,如管道尚未连接,客户同管道连接时就会触发lpOverlapped结构中的事件对象。随后,可用一个等待函数来监视连接。


2.2 客户端函数

2.2.1     CreateFile 连接到一个命名管道
2.2.2     WaitNamedPipe 等待管道实例是否可用
1
2
3
4
5
6
7
8
9
10
11
/*************************************************************************
  Purpose :  等待管道实例是否可用
  Input   :  lpNamedPipeName         --  管道名称
             nTimeOut                --  等待时间
  Return  :  TRUE -- 成功 FALSE -- 失败,通过GetLastError()获取错误码
  Remark  : 
  *************************************************************************/
BOOL WINAPI WaitNamedPipe(
   _In_  LPCTSTR lpNamedPipeName,
   _In_  DWORD nTimeOut
);

2.3 管道收发数据函数

该函数同文件操作,不予过多介绍。

2.3.1     ReadFile 从管道读出数据

2.3.2     WriteFile 写数据到管道

3 服务端和客户端流程图

151719309484955.png

上图显示的为一个同步非阻塞的管道通信业务模型。具体过程如下所示:

Step1:服务端通过函数CreateNamedPipe() 创建管道实例。

Step2:服务端通过函数ConnectNamePipe() 等待客户端连接,此时服务端进入阻塞状态直到有客户端调用CreateFile连接管道成功。

Step3:客户端通过函数WaitNamePipe() 检测管道是否可用。当管道实例存在但不可用时,该函数阻塞,直到管道实例可用或者到超时时间才返回。如果没有管道实例,则该函数会立即返回错误,错误代码为2。当服务端执行完CreateNamePipe函数时,该函数即可成功返回。

Step4:当客户端正确执行完WaitNamePipe后即可连接管道;MSDN上说即使WaitNamePipe当时返回成功,但是执行CreateFile 也有可能出错(会被其他进程等占用到时管道实例不可用)。所以需要后边设计了一段代码防止该情况的发生。当客户端连接成功后,服务端的ConnectNamePipe函数返回,执行第6步ReadFile进入阻塞状态,等待客户端数据写入。

Step5:当客户端成功连接管道后,即可进行通信,通过函数WriteFile写入管道数据,然后客户端调用ReadFile进入阻塞等待服务端写入数据。

Step6:当客户端数据写入成功后,服务端程序会从ReadFile的阻塞中返回。

Step7:服务端对接收到的数据进行业务处理。

Step8:服务端对处理后的数据写入管道。

Step9:客户端ReadFile函数从阻塞中成功返回,接收到服务端处理的数据。

Step10:客户端调用CloseHandle断开管道连接,通信结束。

4 编程实现

4.1 简单实现

4.1.1 服务端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
//服务端测试程序
void SrvTest()
{
     HANDLE  hSrvNamePipe;  
     char    szPipeName[MAX_PATH] = {0};
 
     char    szReadBuf[MAX_BUFFER] = {0};
     char    szWritebuf[MAX_BUFFER] = {0};
     DWORD   dwNumRead = 0;
     DWORD   dwNumWrite = 0;
     
     strcpy (szPipeName, "\\\\.\\pipe\\myTestPipe" );
 
     //step1:创建管道实例
     hSrvNamePipe = CreateNamedPipeA( szPipeName,
         PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH,
         PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT,
         PIPE_UNLIMITED_INSTANCES, 0, 0, 0, NULL);
     if ( INVALID_HANDLE_VALUE == hSrvNamePipe )
     {
         WriteLog( "CreateNamedPipeA err[%#x]" , GetLastError());
         return ;
     }
     WriteLog( "CreateNamedPipe succ..." );
     //step2:等待客户端连接
     BOOL bRt= ConnectNamedPipe(hSrvNamePipe, NULL );
     if ( false ==bRt && GetLastError() != ERROR_PIPE_CONNECTED )
     {
         WriteLog( "等待客户端连接失败,[%#x]" , GetLastError());
         return ;
     }
     WriteLog( "收到客户端的连接成功..." );
 
     //step3:接收数据
     memset ( szReadBuf, 0, MAX_BUFFER );
     bRt = ReadFile( hSrvNamePipe, szReadBuf, MAX_BUFFER-1, &dwNumRead, NULL );
     if ( !bRt || dwNumRead == 0 )
     {
         bRt = GetLastError();
         if (bRt == ERROR_BROKEN_PIPE)
         {
             WriteLog( "客户已关闭链接" );
             return ;
         }
         else
         {
             WriteLog( "读取客户数据失败!,GetLastError=%d" , GetLastError() );
             return ;
         }
     }
     WriteLog( "收到客户数据:[%s]" , szReadBuf);
     //step4:业务逻辑处理 (只为测试用返回原来的数据)
 
     //step5:发送数据
     if ( !WriteFile( hSrvNamePipe, szReadBuf, dwNumRead, &dwNumWrite, NULL ) )
     {
         WriteLog( "向客户写入数据失败:[%#x]" , GetLastError());
         return ;
     }
 
     WriteLog( "写入数据成功..." );
}
 
int main( int argc, char * argv[])
{
     SrvTest();
     system ( "pause" );
     return 0;
}
4.1.2 客户端代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
//客户端测试程序
void ClientTest()
{
     char    szPipeName[MAX_PATH] = {0};
     HANDLE  hPipe;
     DWORD   dwRet;
 
     char    szReadBuf[MAX_BUFFER] = {0};
     char    szWritebuf[MAX_BUFFER] = {0};
     DWORD   dwNumRead = 0;
     DWORD   dwNumWrite = 0;
 
     strcpy (szPipeName, "\\\\.\\pipe\\myTestPipe" );
 
     //step1:检测管道是否可用
     if (!WaitNamedPipeA(szPipeName, 10000))
     {
         WriteLog( "管道[%s]无法打开" , szPipeName);
         return ;
     }
 
     //step2:连接管道
     hPipe = CreateFileA(szPipeName,
         GENERIC_READ|GENERIC_WRITE,
         0,
         NULL,
         OPEN_EXISTING,
         FILE_ATTRIBUTE_NORMAL,
         NULL);
 
     if (INVALID_HANDLE_VALUE == hPipe)
     {
         //成功
         WriteLog( "连接管道失败[%#x]" , GetLastError());
         return ;
     }
 
     WriteLog( "管道连接成功..." );
 
     printf ( "请输入要发送的数据:" );
     scanf ( "%s" , szWritebuf );
 
     //step3:发送数据
     if ( !WriteFile( hPipe, szWritebuf, strlen (szWritebuf), &dwNumWrite, NULL ))
     {
         WriteLog( "发送数据失败,GetLastError=[%#x]" , GetLastError());
         return ;
     }
 
     printf ( "发送数据成功:%s\n" , szWritebuf );
     
     //step4:接收数据
     if ( !ReadFile( hPipe, szReadBuf, MAX_BUFFER-1, &dwNumRead, NULL ) )
     {
         WriteLog( "接收数据失败,GetLastError=[%#x]" , GetLastError() );
         return ;
     }
 
     WriteLog( "接收到服务器返回:%s" , szReadBuf );
 
     //step5:关闭管道
     CloseHandle(hPipe);
}
 
int main( int argc, char * argv[])
{
     ClientTest();
     system ( "pause" );
     return 0;
}
4.1.3 运行结果

151719322926212.png

到此,一个简单的利用命名管道的服务端和客户端的程序设计完成。

4.2 字节模式和消息模式

首先要弄清楚管道的字节模式和消息模式:

两者没有太大的区别,只是在字节模式的时候是以字节流接收与发送的。每次可以发送不同的字节数,在没有把发送的数据收走(用ReadFile)前也可以再次发送,只要没有超过管道的默认的缓冲区大小 。其实也可以说是我们用的是PIPE_WAIT,所以会是在阻塞模式,也就是说会让数据直接发送到了服务器端的缓冲区中,虽然sever端没有调用ReadFile来读取数据,其实已经发到那一边去了,它不读出来没有关系,我们客户端还是可以继续发送数据的。而且对每次发送的数据大小没有限制,不会说每次都得发送固定大小的数据 。读的那一端也会把接收到的数据作为一个无机的整体,也就是说是没有结构的二进制流。读的时候可以想读出任意的字节数都可以的。如一次发送了5个字节,然后再发送了40个字节 ,服务器端可以一次性全部读出45个字节 也可以每次读出一个字节 。以及小于45的任意字节。

而对于消息模式的话,对于写的大小与次数也没有限制。只是说若是以消息模式写的,则会发给服务器的时候说,这是一个消息,若你用消息模式读的话不能只读其中的一部分。你得全部都读走,若有一个消息发到了服务器端,10个字节。现在你读了8字节(用ReadFile函数)会返回错误,错误代码为ERRORM_More_data.也就是说即使没有超过缓冲区中已有的数据,你没有读够也会不让你读成功。

注意:以消息模式发送的数据,在服务器端 是可以用字节模式接收的,如上所说。若是这样的话也会把有格式的消息化为没有格式的字节流。但是,不能以消息模式来读取以字节模式发送的消息。

以上内容转载自:http://blog.sina.com.cn/s/blog_71b3a9690100usem.html

代码中采用字节模式,因此为了处理完整数据,采用的数据格式为: LEN+DATA 其中LEN为4字节DWORD类型表示数据的长度(不包含自身长度)

读管道数据代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
/*************************************************************************
  Purpose :  读取管道数据
  Input   :  hPipe           --  [IN] 管道句柄
             pbData          --  [OUT] 读出的数据
             pdwDataLen      --  [OUT] 读出数据长度
  Return  :  0 -- 成功 其他 -- 失败,返回对应的错误码
  Modify  :
  Remark  : 
  *************************************************************************/
DWORD ReadPipeData( HANDLE hPipe, BYTE * pbData, DWORD * pdwDataLen)
{
     BOOL    bRet;
     DWORD   dwRet;
     BYTE    bTemp[1024] = {0};
     DWORD   dwLen = 0;
     DWORD   dwTempLen = 0;
     BYTE *   p = NULL;
 
     //先读取长度
     bRet = ReadFile(hPipe, bTemp, sizeof ( DWORD ), &dwLen, NULL);
     if ( !bRet || dwLen == 0)
     {
         dwRet = GetLastError();
         if (dwRet == ERROR_BROKEN_PIPE)
         {
             WriteLog( "客户端已关闭链接" );
             return dwRet;
         }
         else
         {
             WriteLog( "读取客户端数据失败,[%#x]" , dwRet);
             return dwRet;
         }
     }
     if (dwLen != sizeof ( DWORD ))
     {
         WriteLog( "接收数据长度不正确,recv len[%d]" , dwLen);
         return 1;
     }
 
     memcpy (&dwTempLen, bTemp, sizeof ( DWORD ));
 
     //读取数据
     p = pbData;
     dwLen = 0;
     *pdwDataLen = 0;
     while (dwTempLen)
     {
         bRet = ReadFile(hPipe, p+dwLen, dwTempLen, &dwLen, NULL );
         if ( !bRet || dwLen == 0)
         {
             dwRet = GetLastError();
             if (dwRet == ERROR_BROKEN_PIPE)
             {
                 WriteLog( "客户端已关闭链接" );
                 return dwRet;
             }
             else
             {
                 WriteLog( "读取数据失败,[%#x]" , dwRet);
                 return dwRet;
             }
         }
         dwTempLen -= dwLen;
         p += dwLen;
         *pdwDataLen += dwLen;
     }
 
     return 0;
}

写管道数据代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
/*************************************************************************
  Purpose :  写入管道数据
  Input   :  hPipe           --  [IN] 管道句柄
             pbData          --  [IN] 写入的数据
             dwDataLen       --  [IN] 写入数据长度
  Return  :  0 -- 成功 其他 -- 失败,返回对应的错误码
  Modify  :
  Remark  : 
  *************************************************************************/
DWORD WritePipeData( HANDLE hPipe, BYTE * pbData, DWORD dwDataLen)
{
     BOOL        bRet;
     DWORD       dwRet;
     BYTE        bTemp[1024] = {0};
     DWORD       dwTempLen = 0;
     BYTE *       p = NULL;
     DWORD       dwNumWrite = 0;
     DWORD       dwTotleLen = 0;
     
     p = bTemp;
     memcpy (p, &dwDataLen, sizeof ( DWORD ));
     p += sizeof ( DWORD );
     memcpy (p, pbData, dwDataLen);
     
     dwTotleLen = dwDataLen+ sizeof ( DWORD );
     p = bTemp;
 
     while (dwTotleLen)
     {
         bRet = WriteFile(hPipe, p, dwTotleLen, &dwNumWrite, NULL );
         if (!bRet || dwNumWrite == 0)
         {
             dwRet = GetLastError();
             WriteLog( "WriteFile err[%#x]" , dwRet);
             return dwRet;
         }
 
         dwTotleLen -= dwNumWrite;
         p += dwNumWrite;
     }
 
     return 0;
}

后续用这两个函数即可完成完整的收发数据操作。

4.3 多客户端管道通信

因为是服务端和客户端的通信问题,所以存在多个客户端同时访问一个服务端的问题,对于管道通信也不例外。

设计思路为服务端创建一个管道实例,每来一个客户端连接,服务端就会创建一个线程去处理该客户端的业务,然后再次创建一个该管道的实例,等待客户端连接进来。

这里用客户端用多线程代替多进程进行实验。

服务端代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
void SrvTest_2()
{
     HANDLE  hSrvNamePipe;
     char    szPipeName[MAX_PATH] = {0};
     
     int     nClientCount = 0;
     int     nRunning = 1;
 
     strcpy (szPipeName, "\\\\.\\pipe\\myTestPipe" );
     
     while (nRunning)
     {
         //step1:创建管道实例
         hSrvNamePipe = CreateNamedPipeA( szPipeName,
             PIPE_ACCESS_DUPLEX|FILE_FLAG_WRITE_THROUGH,
             PIPE_TYPE_BYTE|PIPE_READMODE_BYTE|PIPE_WAIT,
             PIPE_UNLIMITED_INSTANCES, 0, 0, 0, NULL);
         if ( INVALID_HANDLE_VALUE == hSrvNamePipe )
         {
             WriteLog( "CreateNamedPipeA err[%#x]" , GetLastError());
             return ;
         }
         WriteLog( "CreateNamedPipe succ..." );
         //step2:等待客户端连接
         BOOL bRt= ConnectNamedPipe(hSrvNamePipe, NULL );
         if ( false ==bRt && GetLastError() != ERROR_PIPE_CONNECTED )
         {
             WriteLog( "等待客户端连接失败,[%#x]" , GetLastError());
             return ;
         }
         WriteLog( "收到第[%d]个客户端的连接成功" , nClientCount);
         
         //step3:创建工作线程与此客户端通信
         CLIENTINFO *pNewClient= new CLIENTINFO;
         pNewClient->nClinetNum= nClientCount;
         pNewClient->hPipe     = hSrvNamePipe;
         HANDLE hWorkThread= CreateThread( NULL, 0, WorkThreadProc, pNewClient, 0, NULL);
         if ( NULL == hWorkThread )
         {
             WriteLog( "创建工作线程[%d]失败,[%#x]" , nClientCount, GetLastError());
             break ;
         }
         nClientCount++;
         if (nClientCount > 32767)
         {
             nClientCount = 0;
         }
     }
}

线程处理代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
/*************************************************************************
  Purpose :  开启单独线程处理客户端消息
  Input   :  pArg            --  [IN] 客户端结构体
  Return  :  0 -- 成功 其他 -- 失败,返回对应的错误码
  Modify  :
  Remark  : 
  *************************************************************************/
ULONG WINAPI WorkThreadProc( void * pArg)
{
     CLIENTINFO*     pClientInfo= (CLIENTINFO*)pArg;
     HANDLE          hPipe= pClientInfo->hPipe;
     int             nNum= pClientInfo->nClinetNum;
     DWORD           dwRet;
     BYTE            bReadBuf[MAX_BUFFER] = {0};
     BYTE            bWritebuf[MAX_BUFFER] = {0};
     DWORD           dwReadLen = 0;
     DWORD           dwWriteLen = 0;
 
     //step1:接收数据
     dwRet = ReadPipeData(pClientInfo->hPipe, bReadBuf, &dwReadLen);
     if (dwRet)
     {
         if (ERROR_BROKEN_PIPE != dwRet)
         {
             WriteLog( "客户端[%d] ReadPipeData err[%#x]" , nNum, dwRet);
         }
         goto stop;
     }
     WriteLog( "客户端[%d] 接收数据[%s]" , nNum, ( char *)bReadBuf);
 
     //step2:处理数据
     memcpy (bWritebuf, bReadBuf, dwReadLen);
     dwWriteLen = dwReadLen;
 
     //step3:发送数据
     dwRet = WritePipeData(pClientInfo->hPipe, bWritebuf, dwWriteLen);
     if (dwRet)
     {
         WriteLog( "客户端[%d] WritePipeData err[%#x]" , nNum, dwRet);
         goto stop;
     }
     WriteLog( "客户端[%d] 发送数据[%s]" , nNum, ( char *)bWritebuf);
 
stop:
     //step4:关闭管道
     FlushFileBuffers(hPipe);
     DisconnectNamedPipe(hPipe);
     CloseHandle( hPipe);
     WriteLog( "客户端[%d] 线程退出" , nNum);
     return 0;
}

服务端用到一个while大循环,不断接收客户端的连接,接收到的连接都通过new新线程来处理数据及业务逻辑。

客户端代码:(用到多线程代替多进程)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
void Tread_Proc3( LPVOID lpParameter)
{
     char    szPipeName[MAX_PATH] = {0};
     HANDLE  hPipe;
     DWORD   dwRet;
     int nID = ( int )lpParameter;
 
     char    szReadBuf[MAX_BUFFER] = {0};
     char    szWritebuf[MAX_BUFFER] = {0};
     DWORD   dwNumRead = 0;
     DWORD   dwNumWrite = 0;
 
     WriteLog( "线程[%d]开始..." , nID);
     strcpy (szPipeName, "\\\\.\\pipe\\myTestPipe" );
 
     //step1:检测管道是否可用
     if (!WaitNamedPipeA(szPipeName, 10000))
     {
         WriteLog( "线程[%d] 管道[%s]无法打开" , nID, szPipeName);
         return ;
     }
     //step2:连接管道
     hPipe = CreateFileA(szPipeName,
         GENERIC_READ|GENERIC_WRITE,
         0,
         NULL,
         OPEN_EXISTING,
         FILE_ATTRIBUTE_NORMAL,
         NULL);
 
     if (INVALID_HANDLE_VALUE == hPipe)
     {
         //成功
         WriteLog( "线程[%d]连接管道失败[%#x]" , nID, GetLastError());
         return ;
     }
     WriteLog( "线程[%d] 管道连接成功..." , nID);
     sprintf (szWritebuf, "THREADDATA--[%d]" , nID);
     //step3:发送数据
     dwRet = WritePipeData(hPipe, ( PBYTE )szWritebuf, strlen (szWritebuf)+1);
     if (dwRet)
     {
         WriteLog( "线程[%d] 发送数据失败,[%#x]" , nID, dwRet);
         return ;
     }
     WriteLog( "线程[%d] 发送数据成功:%s" , nID, szWritebuf );
     //step4:接收数据
     dwRet = ReadPipeData(hPipe, ( PBYTE )szReadBuf, &dwNumRead);
     if (dwRet)
     {
         WriteLog( "线程[%d] 接收数据失败,[%#x]" , nID, dwRet);
         return ;
     }
     WriteLog( "线程[%d] 接收到服务器返回:%s" , nID, szReadBuf );
     //step5:关闭管道
     CloseHandle(hPipe);
     WriteLog( "线程[%d] 结束..." , nID);
}
 
//客户端多线程测试
void ClientTest_3()
{
     HANDLE m_hThreadSendReadData[MAX_THREAD_NUM];
     for ( int i=0;i<MAX_THREAD_NUM;++i)
     {
         m_hThreadSendReadData[i] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Tread_Proc3,( LPVOID )i,0,NULL);
     }
     WaitForMultipleObjects(MAX_THREAD_NUM, m_hThreadSendReadData, TRUE, INFINITE);
}

MAX_THREAD_NUM为1时,程序能正常运行。
151719349482267.png

当线程数为10个时,程序出错,如下图所示:151719367147694.png

出现的错误代码为0xE7 ,通过ERRORLOOKUP查询原因为:所有的管道范例都在使用中。根据该错误代码猜想,服务端在接收到一个连接并在创建新的管道实例之前,有客户端进行了连接请求导致没有一个可用的管道实例可用。通过如下修改客户端代码验证了该问题:(在每个线程启动时加入sleep),经过验证,程序正确运行。

1
2
3
4
5
6
7
8
9
10
11
//客户端多线程测试
void ClientTest_3()
{
     HANDLE m_hThreadSendReadData[MAX_THREAD_NUM];
     for ( int i=0;i<MAX_THREAD_NUM;++i)
     {
         m_hThreadSendReadData[i] = CreateThread(NULL,0,(LPTHREAD_START_ROUTINE)Tread_Proc3,( LPVOID )i,0,NULL);
         Sleep(10);
     }
     WaitForMultipleObjects(MAX_THREAD_NUM, m_hThreadSendReadData, TRUE, INFINITE);
}

因为我们不能保证客户端是否同一时刻请求连接,所以修改连接代码部分为如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
/*************************************************************************
  Purpose :  客户端连接管道
  Input   :  phPipe          -- [OUT] 管道句柄
  Return  :  0 -- 成功 其他 -- 失败,返回对应的错误码
  Modify  :
  Remark  : 
  *************************************************************************/
DWORD Pipe_ConnSrv( HANDLE * phPipe)
{
     int         i;
     DWORD       dwRet = 0;
     char        szPipeName[MAX_PATH] = {0};
 
     strcpy (szPipeName, "\\\\.\\pipe\\myTestPipe" );
     //连接管道
     for (i=0;i<10;i++)
     {
         *phPipe = CreateFileA(szPipeName,
             GENERIC_READ|GENERIC_WRITE,
             0,
             NULL,
             OPEN_EXISTING,
             FILE_ATTRIBUTE_NORMAL,
             NULL);
 
         if (INVALID_HANDLE_VALUE != *phPipe)
         {
             //成功
             WriteLog( "连接管道成功" );
             break ;
         }
 
         dwRet = GetLastError();
         if (dwRet != ERROR_PIPE_BUSY)
         {
             WriteLog( "连接管道失败,err[%#x]" , dwRet);
             return dwRet;
         }
 
         WriteLog( "管道正忙..." );
         //等待1s
         if (!WaitNamedPipeA(szPipeName, 1000))
         {
             WriteLog( "管道[%s]无法打开" , szPipeName);
             return 1;
         }
     }
     return 0;
}

为了验证不同管道实例间数据是否有影响,在客户端发送的数据中加入了线程编号,同时对于客户端和服务端收发数据做了while循环(每个线程都不停的收发数据)。结果证明不同管道实例相互不受影响。

注:MSDN上说创建的实例个数1-255,当我把客户端线程开启到260的时候,程序没报错,暂不明白这个实例怎么理解的。




    

 



转载于:https://www.cnblogs.com/dspeeding/p/4427777.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
### 回答1: Dart Windows 中使用命名管道进行 IPC 通信的代码示例如下: ```dart import 'dart:io'; void main() async { // 创建命名管道 var server = await ServerSocket.bind('\\\\.\\pipe\\my_pipe'); print('Server listening on pipe: \\\\.\\pipe\\my_pipe'); // 等待客户端连接 var client = await server.first; print('Client connected from ${client.address.address}'); // 发送数据 client.write('Hello, client!'); // 接收数据 var data = await client.first; print('Received: $data'); // 关闭管道 await client.close(); await server.close(); } ``` ```dart import 'dart:io'; void main() async { // 连接到命名管道 var client = await Socket.connect('\\\\.\\pipe\\my_pipe'); print('Connected to pipe: \\\\.\\pipe\\my_pipe'); // 接收数据 var data = await client.first; print('Received: $data'); // 发送数据 client.write('Hello, server!'); // 关闭管道 await client.close(); } ``` 这是一个简单的示例,它显示了如何在 Dart 中使用命名管道进行 IPC 通信。需要注意的是, 在 Windows 中,命名管道的名称必须以 "\\.\pipe\" 开头。 ### 回答2: Dart是一种通过Dart编程语言开发应用程序的跨平台解决方案。在Windows平台上,我们可以使用Win32 API来实现命名管道(Named Pipes)IPC通信。 首先,我们需要安装并配置Dart环境以在Windows平台上开发应用程序。在Dart环境配置完成后,我们可以使用dart:ffi库来访问Win32 API。 下面是一个简单的代码示例,展示了如何使用Dart和Win32 API来实现命名管道的IPC通信。这个示例中包含了服务端和客户端两部分的代码: 服务端代码(Server.dart): import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:win32/win32.dart'; void main() { // 创建命名管道对象 var pipeHandle = CreateNamedPipe( TEXT("\\\\.\\pipe\\myPipe"), // 命名管道的名称 PIPE_ACCESS_DUPLEX, // 可读可写的管道 PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, // 允许多个客户端连接 0, // 输出缓冲区大小 0, // 输入缓冲区大小 0, //默认超时时间 nullptr // 默认安全属性 ); if (pipeHandle == INVALID_HANDLE_VALUE) { print('命名管道创建失败'); return; } // 等待客户端连接,并处理消息 ConnectNamedPipe(pipeHandle, nullptr); final buffer = calloc<Uint8>(128); // 用于接收消息的缓冲区 while (ReadFile(pipeHandle, buffer, 128, nullptr, nullptr) != 0) { final message = utf8.decode(buffer.asTypedList()); print('接收到客户端的消息:$message'); memset(buffer, 0, 128); // 清空缓冲区 } // 断开命名管道连接并释放资源 DisconnectNamedPipe(pipeHandle); CloseHandle(pipeHandle); } 客户端代码(Client.dart): import 'dart:ffi'; import 'package:ffi/ffi.dart'; import 'package:win32/win32.dart'; void main() { // 打开命名管道 var pipeHandle = CreateFile( TEXT("\\\\.\\pipe\\myPipe"), // 命名管道的名称 GENERIC_READ | GENERIC_WRITE, // 可读可写 0, // 暂不支持共享模式 nullptr, // 默认安全属性 OPEN_EXISTING, // 打开已经存在的命名管道 0, nullptr); if (pipeHandle == INVALID_HANDLE_VALUE) { print('命名管道打开失败'); return; } final message = 'Hello, Server!'; final buffer = calloc<Uint8>(128); // 将消息写入命名管道 WriteFile(pipeHandle, message.toNativeUtf8(), message.length, nullptr, nullptr); // 从命名管道读取服务端的响应 ReadFile(pipeHandle, buffer, 128, nullptr, nullptr); final response = utf8.decode(buffer.asTypedList()); print('接收到服务端的响应:$response'); // 关闭命名管道 CloseHandle(pipeHandle); } 这个示例展示了如何使用Dart和Win32 API来实现命名管道的IPC通信。在服务端代码中,我们创建了一个命名管道对象,并等待客户端连接。随后,我们可以通过管道进行读写操作。在客户端代码中,我们打开已经存在的命名管道,并通过管道发送消息给服务端,并接收服务端的响应。 请注意,为了能够编译和运行上述示例代码,您需要安装Dart SDK、Win32 API相关库,并在Dart环境中配置好FFI支持。同时,还需要导入dart:ffi和package:win32库以访问Win32 API。 希望以上示例对您有所帮助!如果您还有其他问题,请随时提问。 ### 回答3: Dart是一种跨平台编程语言,可以用于开发桌面应用程序。而Windows命名管道是一种用于在本地进程间进行通信的机制,可以在Windows操作系统上实现进程间的数据交换。 在Dart中使用Windows命名管道进行进程间通信涉及到与系统底层API的交互,可以通过dart:ffi库将Dart代码链接到Windows的动态链接库中,并使用相应的API来操作命名管道。 下面是一个示例代码,演示了在Dart中使用Windows命名管道进行进程间通信: ```dart import 'dart:ffi'; import 'dart:io'; // 导入Windows的动态链接库 final DynamicLibrary kernel32 = DynamicLibrary.open('kernel32.dll'); // 定义相关的Win32 API函数 final CreateNamedPipe = kernel32.lookupFunction< IntPtr Function(Pointer<Utf16> lpName, Uint32 dwOpenMode, Uint32 dwPipeMode, Uint32 nMaxInstances, Uint32 nOutBufferSize, Uint32 nInBufferSize, Uint32 nDefaultTimeOut, Pointer<Void> lpSecurityAttributes), int Function(Pointer<Utf16> lpName, int dwOpenMode, int dwPipeMode, int nMaxInstances, int nOutBufferSize, int nInBufferSize, int nDefaultTimeOut, Pointer<Void> lpSecurityAttributes)>( 'CreateNamedPipeW', ); final ConnectNamedPipe = kernel32.lookupFunction< Int32 Function( IntPtr hNamedPipe, Pointer<Void> lpOverlapped), int Function( int hNamedPipe, Pointer<Void> lpOverlapped)>('ConnectNamedPipe'); final WriteFile = kernel32.lookupFunction< Int32 Function(IntPtr hFile, Pointer<Void> lpBuffer, Uint32 nNumberOfBytesToWrite, Pointer<Uint32> lpNumberOfBytesWritten, Pointer<Void> lpOverlapped), int Function(int hFile, Pointer<Void> lpBuffer, int nNumberOfBytesToWrite, Pointer<Uint32> lpNumberOfBytesWritten, Pointer<Void> lpOverlapped)>( 'WriteFile', ); final ReadFile = kernel32.lookupFunction< Int32 Function(IntPtr hFile, Pointer<Void> lpBuffer, Uint32 nNumberOfBytesToRead, Pointer<Uint32> lpNumberOfBytesRead, Pointer<Void> lpOverlapped), int Function(int hFile, Pointer<Void> lpBuffer, int nNumberOfBytesToRead, Pointer<Uint32> lpNumberOfBytesRead, Pointer<Void> lpOverlapped)>( 'ReadFile', ); final CloseHandle = kernel32.lookupFunction<Int32 Function(IntPtr hObject), int Function(int hObject)>('CloseHandle'); // 主函数 void main() { // 创建命名管道 final pipeName = '\\\\.\\pipe\\MyPipe'; final pipeHandle = CreateNamedPipe( pipeName.toNativeUtf16(), 3, 0, 255, 65536, 65536, 0, Pointer.fromAddress(0), ); // 等待客户端连接 final connectResult = ConnectNamedPipe(pipeHandle, Pointer.fromAddress(0)); if (connectResult != 0) { print('连接已建立'); // 向管道写入数据 final message = 'Hello, World!'; final buffer = message.toNativeUtf16(); final bufferLength = buffer.length * 2; final bytesWrittenPtr = Pointer<Uint32>.allocate(); WriteFile(pipeHandle, buffer.cast(), bufferLength, bytesWrittenPtr, Pointer.fromAddress(0)); print('写入成功'); // 从管道读取数据 final bufferSize = 255; final bufferPtr = Pointer<Uint16>.allocate(count: bufferSize); final bytesReadPtr = Pointer<Uint32>.allocate(); ReadFile(pipeHandle, bufferPtr.cast(), bufferSize * 2, bytesReadPtr, Pointer.fromAddress(0)); final receivedMessage = bufferPtr.cast<Utf16>().unpackString(bytesReadPtr.value); print('收到消息: $receivedMessage'); // 关闭管道句柄 CloseHandle(pipeHandle); } } ``` 以上代码演示了如何在Dart中使用Windows命名管道进行进程间通信。首先使用`CreateNamedPipe`函数创建了一个命名管道,然后使用`ConnectNamedPipe`函数等待客户端的连接。连接建立后,向管道写入数据使用`WriteFile`函数,然后从管道读取数据使用`ReadFile`函数。最后通过`CloseHandle`函数关闭管道句柄。 需要注意的是,上述代码仅适用于Windows操作系统,且需要在Windows系统上编译和运行。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值