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进程间通信--命名管道的更多相关文章

  1. Windows进程间通信—命名管道

    命名管道是通过网络来完成进程间的通信,它屏蔽了底层的网络协议细节.我们在不了解网络协议的情况下,也可以利用命名管道来实现进程间的通信.与Socket网络通信相比,命名管道不再需要编写身份验证的代码.将 ...

  2. windows namedPipe 命名管道clent and server

    1.client: #include "iostream" #include "windows.h" using namespace std; void mai ...

  3. Linux进程间通信-命名管道

    前面我们讲了进程间通信的一种方式,匿名管道.我们知道,匿名管道只能用于父子关系的进程之间.那么没有这种关系的进程之间该如何进行数据传递呢? 1.什么是命名管道 匿名管道是在缓存中开辟的输出和输入文件流 ...

  4. Linux - 进程间通信 - 命名管道

    1.命名管道的特点: (1)是管道,可用于非血缘关系的进程间的通信 (2)使用命名管道时,梁金成需要用路径表示通道. (3)命名管道以FIFO的文件形式存储于文件系统中.(FIFO:总是按照先进先出的 ...

  5. C#命名管道通信

    C#命名管道通信 最近项目中要用c#进程间通信,以前常见的方法包括RMI.发消息等.但在Windows下面发消息需要有窗口,我们的程序是一个后台运行程序,发消息不试用.RMI又用的太多了,准备用管道通 ...

  6. c# c++通信--命名管道通信

    进程间通信有很多种,windows上面比较简单的有管道通信(匿名管道及命名管道) 最近做个本机c#界面与c++服务进行通信的一个需求.简单用命名管道通信.msdn都直接有demo,详见下方参考. c+ ...

  7. 使用命名管道承载gRPC

    最近GRPC很火,感觉整RPC不用GRPC都快跟不上时髦了. gRPC设计 刚好需要使用一个的RPC应用系统,自然而然就盯上了它,但是它真能够解决所有问题吗?不见得,先看看他的优点: gRPC是一种与 ...

  8. 命名管道FIFO及其读写规则

    一.匿名管道的一个限制就是只能在具有共同祖先的进程间通信命名管道(FIFO):如果我们想在不相关的进程之间切换数据,可以使用FIFO文件来做这项工作注意:命名管道是一种特殊类型文件.利用命令:$ mk ...

  9. 【windows 操作系统】进程间通信(IPC)简述|无名管道和命名管道 消息队列、信号量、共享存储、Socket、Streams等

    一.进程间通信简述 每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程1把数据从用户空间拷到内核缓冲区,进 ...

随机推荐

  1. Android手机中怎么样在没root的情况下 修改 hosts 文件

    工具  链接:https://pan.baidu.com/s/1AENluDCQ-2qYDPcE5K6l8g 密码:t7eu  http://bbs.360.cn/forum.php?mod=view ...

  2. luoguP3690 列队

    https://www.luogu.org/problemnew/show/P3960 作为一个初二蒟蒻要考提高组,先做一下17年的题目 我们发现进行一次操作相当于 把第 x 行的第 y 个弹出记为 ...

  3. 《条目十八》避免使用vector<bool>

    <条目十八>避免使用vector 先说结论: 一是:vector<bool>不是标准容器,因为标准容器的对于T *p = &c[0];必须是可编译的. 二是:vecto ...

  4. 本地localhost:端口号(自己设置的Apache的端口号)打不开问题解决了!开心、哭泣

    想不来自己有多蠢!历经4个月再没学,刚开始xampp的端口问题解决不了,系统竟然会自动改回去端口数据(哭晕) 后来一直显示Apache端口80占用,各种百度之后发现单纯浏览器都访问不了localhos ...

  5. JSONP原理及简单实现 可做简单插件使用

    JSONP实现跨域通信的解决方案. 在jquery中,我们可以通过$.ajax的dataType设置为jsonp来调用jsonp,但是jsonp和ajax的实现原理一个关系都木有.jsonp主要是通过 ...

  6. [JLOI2015]管道连接(斯坦纳树)

    [Luogu3264] 原题解 多个频道,每个频道的关键点要求相互联通 详见代码,非常巧妙 #include<cstdio> #include<iostream> #inclu ...

  7. bootdo开源项目修改代码后页面无效

    修改了JS文件,重启服务后,发现页面没有刷新出效果. 清空缓存一般就可以解决此问题.

  8. P2754 [CTSC1999]家园

    传送门 人在各个太空站流动,所以显然的网络流模型 因为不同时间能走的边不同,所以显然按时间拆点 但是因为不知道要多少时间,所以要枚举时间,动态拆点 每一点向下一个时间的同一点连流量为 $INF$ 的边 ...

  9. poj1182 食物链 带权并查集

    题目传送门 题目大意:大家都懂. 思路: 今天给实验室的学弟学妹们讲的带权并查集,本来不想细讲的,但是被学弟学妹们的态度感动了,所以写了一下这个博客,思想在今天白天已经讲过了,所以直接上代码. 首先, ...

  10. BZOJ 3224 Treap

    部分还没调到满意的程度,效率比splay略好 #include<bits/stdc++.h> using namespace std; const int maxn = 1e6+11; u ...