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 服务端和客户端流程图

上图显示的为一个同步非阻塞的管道通信业务模型。具体过程如下所示:
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 运行结果

到此,一个简单的利用命名管道的服务端和客户端的程序设计完成。
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时,程序能正常运行。
当线程数为10个时,程序出错,如下图所示:
出现的错误代码为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的时候,程序没报错,暂不明白这个实例怎么理解的。
Windows进程间通信--命名管道的更多相关文章
- Windows进程间通信—命名管道
命名管道是通过网络来完成进程间的通信,它屏蔽了底层的网络协议细节.我们在不了解网络协议的情况下,也可以利用命名管道来实现进程间的通信.与Socket网络通信相比,命名管道不再需要编写身份验证的代码.将 ...
- windows namedPipe 命名管道clent and server
1.client: #include "iostream" #include "windows.h" using namespace std; void mai ...
- Linux进程间通信-命名管道
前面我们讲了进程间通信的一种方式,匿名管道.我们知道,匿名管道只能用于父子关系的进程之间.那么没有这种关系的进程之间该如何进行数据传递呢? 1.什么是命名管道 匿名管道是在缓存中开辟的输出和输入文件流 ...
- Linux - 进程间通信 - 命名管道
1.命名管道的特点: (1)是管道,可用于非血缘关系的进程间的通信 (2)使用命名管道时,梁金成需要用路径表示通道. (3)命名管道以FIFO的文件形式存储于文件系统中.(FIFO:总是按照先进先出的 ...
- C#命名管道通信
C#命名管道通信 最近项目中要用c#进程间通信,以前常见的方法包括RMI.发消息等.但在Windows下面发消息需要有窗口,我们的程序是一个后台运行程序,发消息不试用.RMI又用的太多了,准备用管道通 ...
- c# c++通信--命名管道通信
进程间通信有很多种,windows上面比较简单的有管道通信(匿名管道及命名管道) 最近做个本机c#界面与c++服务进行通信的一个需求.简单用命名管道通信.msdn都直接有demo,详见下方参考. c+ ...
- 使用命名管道承载gRPC
最近GRPC很火,感觉整RPC不用GRPC都快跟不上时髦了. gRPC设计 刚好需要使用一个的RPC应用系统,自然而然就盯上了它,但是它真能够解决所有问题吗?不见得,先看看他的优点: gRPC是一种与 ...
- 命名管道FIFO及其读写规则
一.匿名管道的一个限制就是只能在具有共同祖先的进程间通信命名管道(FIFO):如果我们想在不相关的进程之间切换数据,可以使用FIFO文件来做这项工作注意:命名管道是一种特殊类型文件.利用命令:$ mk ...
- 【windows 操作系统】进程间通信(IPC)简述|无名管道和命名管道 消息队列、信号量、共享存储、Socket、Streams等
一.进程间通信简述 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进 ...
随机推荐
- SHTSC2017酱油记
考完回来累成狗..睡了一觉..补游记.. DAY0 把最近刷的题发了下题解..NOIP RK10的蒟蒻收拾收拾准备退役了.. 12点就睡了..很久周五没这么早睡了.. DAY1 9点就醒了..莫名紧张 ...
- C# Winform 加载窗体/对象时的等待页面设计
在设计应用程序过程中,有时候加载对象需时较长,我们可以显示一个Loading等待页面,对用户来说就比较友好了. 这个还是涉及到多线程,下面是步骤. 一.创建好Loading窗体: 一个Panel用于显 ...
- SignalR-001
SignalR 是什么? ASP.NET Core SignalR 是一个开放源代码库,它简化了向应用添加实时 web 功能. 实时 web 功能立即使服务器端代码能够将内容推送到客户端. 一.有这么 ...
- Js屏蔽键盘输入的某些字符,用以部分代替正则表达式
工作当中用到的:有是,用户会在文本框里输入一些无效的(错误的)内容,比如在手机号中输入#等等,一般使用正则表达式,但是只有点击的时候才会验证,用户体验不好,所以想屏蔽这些按键,让键盘根本打不出来,以下 ...
- 3.mouseenter和mouseover事件的区别
<html> <head> <meta charset="UTF-8"> <script src="jquery-3.3.1.j ...
- iOS开发时间戳与时间NSDate,时区的转换,汉字与UTF8,16进制的转换
http://blog.sina.com.cn/s/blog_68661bd80101njdo.html 标签: ios时间戳 ios开发时间戳 ios16进制转中文 ios开发utf8转中文 ios ...
- jquery实现下拉菜单
需要实现的效果如图: <!DOCTYPE html> <html> <head lang="en"> <meta charset=&quo ...
- 用python脚本 从xls文件中读取数据
导入 xlrd 第三方模块 import xlrd data = xlrd.open_workbook('test.xlsx') # 打开xls文件 table = data.sheets()[0] ...
- 【转】IDEA快捷键功能说明及Eclipse对应操作
1.Ctrl+z是撤销快捷键 2.如果想恢复Ctrl+z 掉的内容,按快捷键为:Ctrl + Shift + Z.方可 3.Ctrl-H(Browse Type Hierarchy) Ctrl + A ...
- 不带 www 跳转 到 带 www 网站..
IIS: <rule name="已导入的规则 1-1" stopProcessing="true"> <match url="^( ...