C++进程通信之命名管道

命名管道定义

一个命名管道是一个命名的,单向或双面管道的管道服务器和一个或多个管道客户端之间的通信。命名管道的所有实例共享相同的管道名称,但每个实例都有自己的缓冲区和句柄,并为客户端/服务器通信提供单独的管道。实例的使用使多个管道客户端能够同时使用同一个命名管道。

这里要理解实例的概念:当我用CreateNamedPipe在服务器端创建一个名为pipeTest的命名管道时,即pipeTest拥有了一个实例。再次重复刚才的操作时,即创建了pipeTest的第二个实例;当用CreateNamedPipe在服务器端创建一个名为pipeTestAnother的命名管道时,则该pipeTestAnother管道拥有了第一个实例。

 命名管道的使用步骤

服务器端:

首先,使用CreateNamedPipe创建属于该管道的实例。然后等待客户端实例的连接,服务器端可以使用ConnectNamedPipe进行阻塞同步等待客户端实例的连接,也可以非阻塞,然后执行ReadFile不停读取客户端发送到管道的数据。

客户端:

执行WaitNamedPipe(不是真正的用于连接的函数来等待管道的出现,存在管道后,执行CreateFile来连接存在的服务器管道,获取对应句柄后,执行WriteFile往管道发送数据。

上面是以最简单的单向的客户端发送数据进管道,服务器端接受管道数据。还有其他的通信选项。包括双通道的Read&Write。Read&Write过程中的同步与异步;按字节流方式/消息的方式写入/读取管道数据。

CreateNamedPipe

HANDLE CreateNamedPipeA(
  [in] LPCSTR                     lpName,
  [in] DWORD                      dwOpenMode,
  [in] DWORD                      dwPipeMode,
  [in] DWORD                      nMaxInstances,
  [in] DWORD                      nOutBufferSize,
  [in] DWORD                      nInBufferSize,
  [in] DWORD                      nDefaultTimeOut,
  [in, LPSECURITY_ATTRIBUTES      lpSecurityAttributes
);
功能:创建命名管道的实例并返回用于后续管道操作的句柄。
lpName String,指定管道名,采用的形式是:\\.\管道\管道名。最多可达256个字符的长度,而且不用区分大小写。如果存在指定名字的一个管道,则创建那个管道的一个新实例。
dwOpenMode Long,下述常数组的一个组合
下述常数之一(对于管道的所有实例都要一样):
模式 意义

PIPE_ACCESS_DUPLEX

0x00000003

管道是双向的;服务器和客户端进程都可以读取和写入管道。此模式为服务器提供了对管道GENERIC_READGENERIC_WRITE访问权限。当客户端使用CreateFile函数连接到管道时,它可以指定GENERIC_READGENERIC_WRITE或两者 

PIPE_ACCESS_INBOUND

0x00000001

管道中的数据流仅从客户端到服务器。此模式为服务器提供了对管道GENERIC_READ访问权限。客户端在连接到管道时必须指定GENERIC_WRITE访问权限。如果客户端必须通过调用GetNamedPipeInfoGetNamedPipeHandleState函数读取管道设置,则客户端在连接到管道时必须指定GENERIC_WRITEFILE_READ_ATTRIBUTES访问权限

PIPE_ACCESS_OUTBOUND

0x00000002

管道中的数据流仅从服务器流向客户端。此模式为服务器提供了对管道GENERIC_WRITE访问权限。客户端在连接到管道时必须指定GENERIC_READ访问权限。如果客户端必须通过调用SetNamedPipeHandleState函数更改管道设置,则客户端在连接到管道时必须指定GENERIC_READFILE_WRITE_ATTRIBUTES访问权限。
此参数还可以包括以下一个或多个标志,用于启用直写和重叠模式。对于同一管道的不同实例,这些模式可能不同。
模式  意义

FILE_FLAG_FIRST_PIPE_INSTANCE

0x00080000

如果您尝试使用此标志创建管道的多个实例,则第一个实例的创建成功,但下一个实例的创建失败并显示ERROR_ACCESS_DENIED

FILE_FLAG_WRITE_THROUGH

0x80000000

直写模式已启用。此模式仅影响字节类型管道上的写入操作,并且仅当客户端和服务器进程位于不同的计算机上时。如果启用此模式,则写入命名管道的函数不会返回,直到写入的数据通过网络传输并位于远程计算机上的管道缓冲区中。如果未启用此模式,系统会通过缓冲数据来提高网络操作的效率,直到累积的字节数达到最少或经过最长时间。

FILE_FLAG_OVERLAPPED

0x40000000

重叠模式已启用。如果启用此模式,则执行可能需要很长时间才能完成的读取、写入和连接操作的函数可以立即返回。这种模式使启动操作的线程可以执行其他操作,而耗时的操作在后台执行。例如,在重叠模式下,线程可以同时处理管道的多个实例上的输入和输出 (I/O) 操作,或者在同一个管道句柄上同时执行读写操作。如果未启用重叠模式,则对管道句柄执行读取、写入和连接操作的函数在操作完成之前不会返回。该 ReadFileEx和 WriteFileEx函数只能在重叠模式下与管道句柄一起使用。的 ReadFile的, WriteFile的, ConnectNamedPipe和 TransactNamedPipe功能可以执行同步或作为重叠操作。
该参数可以包括以下安全访问模式的任意组合。对于同一管道的不同实例,这些模式可能不同。
模式 意义

WRITE_DAC

0x00040000L

调用者将拥有对命名管道的自由访问控制列表 (ACL) 的写访问权限。

WRITE_OWNER

0x00080000L

调用者将拥有对命名管道所有者的写访问权限。

ACCESS_SYSTEM_SECURITY

0x01000000L

调用者将拥有对命名管道的 SACL 的写访问权限。有关详细信息,请参阅 访问控制列表 (ACL)和 SACL 访问权限

dwPipeMode

管道模式。

如果dwPipeMode指定的值不是 0 或下表中列出的标志,则该函数将失败

可以指定以下类型模式之一。必须为管道的每个实例指定相同的类型模式。

模式 意义

PIPE_TYPE_BYTE

0x00000000

数据以字节流的形式写入管道。此模式不能与 PIPE_READMODE_MESSAGE 一起使用。管道不区分在不同写操作期间写入的字节。

PIPE_TYPE_MESSAGE

0x00000004

数据作为消息流写入管道。管道将每次写入操作期间写入的字节视为一个消息单元。当消息未完全读取时GetLastError函数返回ERROR_MORE_DATA此模式可与PIPE_READMODE_MESSAGEPIPE_READMODE_BYTE 一起使用

对于PIPE_TYPE_BYTE模式写入管道,读取端不用每次读取固定大小的数据,可以读取任意字节大小数据。

对于PIPE_TYPE_MESSAGE模式写入管道,读取端用消息模式读取时必须全部读取完,不能只读取部分消息。

具体参考:

https://www.cnblogs.com/duyy/p/3738610.html

可以指定以下读取模式之一。同一管道的不同实例可以指定不同的读取模式。

表 5
模式意义
PIPE_READMODE_BYTE
0x00000000
数据作为字节流从管道中读取。此模式可与PIPE_TYPE_MESSAGEPIPE_TYPE_BYTE 一起使用
PIPE_READMODE_MESSAGE
0x00000002
数据作为消息流从管道中读取。仅当还指定了PIPE_TYPE_MESSAGE 时才能使用此模式

可以指定以下等待模式之一。同一管道的不同实例可以指定不同的等待模式。

表 6
模式意义
PIPE_WAIT
0x00000000
阻塞模式已启用。当在ReadFileWriteFile或 ConnectNamedPipe函数中指定管道句柄时 , 直到有数据要读取、所有数据都已写入或客户端已连接时,操作才会完成。使用此模式可能意味着在某些情况下无限期地等待客户端进程执行操作。
PIPE_NOWAIT
0x00000001
非阻塞模式已启用。在这种模式下,ReadFileWriteFile和 ConnectNamedPipe总是立即返回。

请注意,为了与 Microsoft LAN Manager 2.0 版兼容,支持非阻塞模式,并且不应使用命名管道实现异步 I/O。有关异步管道 I/O 的更多信息,请参阅 同步和重叠输入和输出

nMaxInstances

可以为此管道创建的最大实例数。管道的第一个实例可以指定这个值;必须为管道的其他实例指定相同的编号。可接受的值在 1 到PIPE_UNLIMITED_INSTANCES (255)的范围内

如果此参数为PIPE_UNLIMITED_INSTANCES,则可以创建的管道实例数量仅受系统资源可用性的限制。如果nMaxInstances大于PIPE_UNLIMITED_INSTANCES,则返回值为INVALID_HANDLE_VALUE并且GetLastError返回ERROR_INVALID_PARAMETER

nOutBufferSize
为输出缓冲区保留的字节数。
nInBufferSize
为输入缓冲区保留的字节数。
nDefaultTimeOut

默认超时值(以毫秒为单位),如果 WaitNamedPipe函数指定NMPWAIT_USE_DEFAULT_WAIT命名管道的每个实例都必须指定相同的值。

零值将导致默认超时为 50 毫秒

lpSecurityAttributes

指向SECURITY_ATTRIBUTES结构的指针,该 结构为新命名管道指定安全描述符并确定子进程是否可以继承返回的句柄

返回值:

如果函数成功,则返回值是命名管道实例的服务器端的句柄。

如果函数失败,则返回值为INVALID_HANDLE_VALUE要获取扩展错误信息,请调用 GetLastError

ConnectNamedPipe

BOOL ConnectNamedPipe(
  [in] HANDLE            hNamedPipe,
  [in, LPOVERLAPPED      lpOverlapped
);

功能:允许命名管道服务器进程等待客户端进程连接到命名管道的实例。客户端进程通过调用 CreateFile或 CallNamedPipe函数进行连接。

hNamedPipe

命名管道实例的服务器端的句柄。此句柄由CreateNamedPipe函数返回 。

lpOverlapped

指向OVERLAPPED结构的指针 。

如果hNamedPipe是用 FILE_FLAG_OVERLAPPED 打开的,则lpOverlapped参数不能为NULL它必须指向一个有效的OVERLAPPED结构。如果hNamedPipe使用 FILE_FLAG_OVERLAPPED 打开并且lpOverlappedNULL,则该函数可能会错误地报告连接操作已完成。

如果hNamedPipe是用 FILE_FLAG_OVERLAPPED 创建的并且lpOverlapped不是NULL,则OVERLAPPED结构应该包含一个手动重置事件对象的句柄(服务器可以使用CreateEvent函数创建它 )。【非阻塞重叠IO,异步等待客户端连接】

如果未使用 FILE_FLAG_OVERLAPPED 打开hNamedPipe,则在连接客户端或发生错误之前,该函数不会返回。如果客户端在调用函数后连接,则成功的同步操作会导致函数返回非零值。【阻塞同步等待客户端连接】

返回值:

如果操作是同步的,则ConnectNamedPipe在操作完成之前不会返回。如果函数成功,则返回值非零。如果函数失败,则返回值为零。要获取扩展错误信息,请调用 GetLastError

如果操作是异步的,ConnectNamedPipe 会立即返回。如果操作仍处于挂起状态,则返回值为零且GetLastError返回 ERROR_IO_PENDING。(您可以使用HasOverlappedIoCompleted宏来确定操作何时完成。)如果函数失败,则返回值为零,并且 GetLastError返回 ERROR_IO_PENDING 或 ERROR_PIPE_CONNECTED 以外的值。

如果客户端在调用函数之前连接,则函数返回零并且GetLastError返回 ERROR_PIPE_CONNECTED。如果客户端在调用CreateNamedPipe和调用 ConnectNamedPipe之间的时间间隔内进行连接,则会发生这种情况【举例:比如服务器端执行CreateNamePipe后,不立即执行ConnectionNamePipe,客户端随即执行CreateFile,客户端此时可以往里面写数据、然后再执行ConnectionNamePipe时函数返回零并且GetLastError返回 ERROR_PIPE_CONNECTED)】 

WaitNamedPipe

BOOL WaitNamedPipeA(
  [in] LPCSTR      lpNamedPipeName,
  [in] DWORD       nTimeOut
);
功能:
等待直到超时间隔结束或指定命名管道的实例可用于连接(即,管道的服务器进程在管道上有一个挂起的 ConnectNamedPipe操作)。
 

 nTimeOut:

函数等待命名管道实例可用的毫秒数。您可以使用以下值之一而不是指定毫秒数。

表格1
价值意义
NMPWAIT_USE_DEFAULT_WAIT
0x00000000
超时间隔是服务器进程在CreateNamedPipe函数中指定的默认值 
NMPWAIT_WAIT_FOREVER
0xffffffff
在命名管道的实例可用之前,该函数不会返回。

 返回值:

如果管道的实例在超时间隔过去之前可用,则返回值非零。

如果管道的实例在超时间隔过去之前不可用,则返回值为零。要获取扩展错误信息,请调用 GetLastError

客户端同步写入,服务器端同步读取

服务器端读取:

#pragma warning(disable:4786)
#include<Windows.h>
#include <string>
DWORD WINAPI ThreadProc(LPVOID);
bool readMessage(HANDLE hTmpNamedPipe, std::string& message)
{
    char recvBuffer[10001] = { 0 };

    DWORD nBytesRead = 0;

    int ret = ReadFile(
        hTmpNamedPipe,
        recvBuffer,
        sizeof(recvBuffer),
        &nBytesRead,
        NULL
    );
    if (0 >= ret)
    {
        printf("Read from client error: %d.\n", errno);
        return false;
    }
    else
    {
        message = static_cast<std::string>(recvBuffer);
        return true;
    }
}
DWORD WINAPI  ThreadProc(LPVOID)
{
    HANDLE hNamedPipe = CreateNamedPipe
    ("\\\\.\\pipe\\pipeTest",
        PIPE_ACCESS_INBOUND,
        PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE,
        3,
        0,
        0,
        1000, // timeout in millseconds
        NULL);
    printf("\\\\.\\pipe\\pipeTest... \n");
    if (NULL == hNamedPipe || INVALID_HANDLE_VALUE == hNamedPipe)
    {
        return NULL;
    }
    if (NULL == hNamedPipe)
    {
        return 0;
    }
    std::string message;
    while (true)
    {
        printf("strat to read from client...\n");
        BOOL fConnected = ConnectNamedPipe(hNamedPipe, NULL);
        if (fConnected)
        {
            printf("connect success...\n");
            while (readMessage(hNamedPipe, message))
            {
                while (std::string::npos != message.find("|"))
                {
                    std::string tmpSourceStr = message.substr(0, message.find("|"));
                    if (tmpSourceStr.length() > 0)
                    {
                        printf("push message=%s in queue...\n", message.c_str());
                    }
                    message = message.substr(message.find("|") + 1);
                }
            }
        }

        DisconnectNamedPipe(hNamedPipe);

    }

    return NULL;
}


int main()
{
    HANDLE hThread1;
    DWORD threadId1;
    printf("another server is running\r\n");
    hThread1 = CreateThread(NULL, 0, ThreadProc, 0, 0, &threadId1);//创建线程
    while (true)
    {
        Sleep(30000);
    }
    return 0;
}
View Code

当客户端没有实例接入时,服务器端在ConnectNamedPipe函数处阻塞等待客户端接入。

客户端写入

#include <iostream>
#include <WinSock2.h>
using namespace std;
DWORD WINAPI ThreadProc(LPVOID);
void Execute();
void ConnectANamePipe();
void ReconnectNamePipe();
HANDLE hThread;
int main()
{
    DWORD threadId;
    hThread = CreateThread(NULL, 0, ThreadProc, 0, 0, &threadId);//创建线程
    std::cout << "Hello World!\n";
    while (1)
    {
        Sleep(5000);
    }
}

void ConnectANamePipe()
{
    if (!WaitNamedPipe(TEXT("\\\\.\\pipe\\pipeTest"), NMPWAIT_WAIT_FOREVER))
    {
        cout << "conenct namepipe failed" << endl;
    }
    DWORD len;
    HANDLE  hClientNamedPipe = CreateFile("\\\\.\\pipe\\pipeTest",
        GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_ATTRIBUTE_NORMAL,
        NULL);
    if (hClientNamedPipe == NULL || hClientNamedPipe == INVALID_HANDLE_VALUE)
    {
        printf("Connect to WDS ERROR %d...\n", GetLastError());
    }
    else
    {
        while (true)
        {
            DWORD nBytesWritten = 0;
            printf("子线程,pid=%d...\n", GetCurrentThreadId());
            char buff[256] = "i am cliect,hello server|";
            /*    if (WriteFile(hClientNamedPipe, buff, sizeof(buff), &nBytesWritten, &olWrite) == false)
                {
                    printf("write error,getlasterror=%d...\n", GetLastError());
                    ReconnectNamePipe();
                }
                else
                {
                    printf("write success\n");
                    Sleep(500);
                }*/
            int ret = (WriteFile(hClientNamedPipe, buff, sizeof(buff), &nBytesWritten, NULL));
            if (0 == ret)
            {
                long int iError = GetLastError();
                printf("write to server error=%d", iError);
            }
            printf("Write to server ok.\n");
            Sleep(1000);
        }
    }
    CloseHandle(hClientNamedPipe);
}
DWORD WINAPI ThreadProc(LPVOID)
{
    ConnectANamePipe();
    return 0;
}
void ReconnectNamePipe()
{
    CloseHandle(hThread);
    ConnectANamePipe();

}
View Code

客户端接入后,结果如下:

客户端异步写入,服务器端同步读取

// NamePipeClient.cpp : 此文件包含 "main" 函数。程序执行将在此处开始并结束。
//

#include <iostream>
#include <WinSock2.h>
DWORD WINAPI ThreadProc(LPVOID);
void Execute();
void ConnectANamePipe();
void ReconnectNamePipe();
HANDLE hThread;
int main()
{
    DWORD threadId;
    hThread = CreateThread(NULL, 0, ThreadProc, 0, 0, &threadId);//创建线程
    std::cout << "Hello World!\n";
    while (1)
    {
        Sleep(5000);
    }
}

void ConnectANamePipe()
{
    DWORD len;
    HANDLE  hClientNamedPipe = CreateFile("\\\\.\\pipe\\pipeTest",
        GENERIC_WRITE,
        0,
        NULL,
        OPEN_EXISTING,
        FILE_FLAG_OVERLAPPED,
        NULL);
    if (hClientNamedPipe == NULL || hClientNamedPipe == INVALID_HANDLE_VALUE)
    {
        printf("Connect to WDS ERROR %d...\n", GetLastError());
    }
    /*HANDLE hClientNamedPipe=CreateFile("\\\\.\\Pipe\\pipeTest",GENERIC_WRITE|GENERIC_READ,0,NULL,OPEN_EXISTING,FILE_ATTRIBUTE_NORMAL,NULL);
    if(hClientNamedPipe==INVALID_HANDLE_VALUE)
    {
        printf("error,getlasterror=%d\n",GetLastError());
    }*/
    //CreateFile连接管道成功后,服务器端ovlap.hEvent即可变为有信号
    else
    {
        while (true)
        {
            OVERLAPPED olWrite;
            memset(&olWrite, 0, sizeof(olWrite));
            olWrite.hEvent = CreateEvent(NULL, TRUE, FALSE, NULL);//创建了一个初始状态为FALSE的手动(人工)重置的事件对象,当手动重置的事件对象得到通知时,等待该事件对象的所有线程均变为可调度线程。
            DWORD nBytesWritten = 0;
            printf("子线程,pid=%d...\n", GetCurrentThreadId());
            char buff[256] = "i am cliect,hello server|";
            /*    if (WriteFile(hClientNamedPipe, buff, sizeof(buff), &nBytesWritten, &olWrite) == false)
                {
                    printf("write error,getlasterror=%d...\n", GetLastError());
                    ReconnectNamePipe();
                }
                else
                {
                    printf("write success\n");
                    Sleep(500);
                }*/
            int ret = (WriteFile(hClientNamedPipe, buff, sizeof(buff), &nBytesWritten, &olWrite));
            printf(" Write to server return : %d.\n", ret);
            if (0 == ret)
            {
                long int iError = GetLastError();
                if (iError != ERROR_IO_PENDING /*997*/)//997表示这个请求是悬而未决的
                {
                    printf("a Write to server Error : %d.\n", GetLastError());
                    ReconnectNamePipe();
                    return;
                }
            }
            if (0 == GetOverlappedResult(hClientNamedPipe, &olWrite, &nBytesWritten, true))
            {
                printf("Write to server Error : %d.\n", GetLastError());
                ReconnectNamePipe();
                return;
            }
            else
            {
                printf("Write to server ok.\n");
                Sleep(1000);
            }

        }
    }
    CloseHandle(hClientNamedPipe);
}
DWORD WINAPI ThreadProc(LPVOID)
{
    ConnectANamePipe();
    return 0;
}
void ReconnectNamePipe()
{
    CloseHandle(hThread);
    ConnectANamePipe();

}
View Code

服务器端异步

#pragma warning(disable:4786)
#include<Windows.h>
#include <string>
DWORD WINAPI ThreadProc(LPVOID);
bool readMessage(HANDLE hTmpNamedPipe, std::string& message)
{

    char recvBuffer[10001] = { 0 };
    DWORD nBytesRead = 0;
    int ret = ReadFile(
        hTmpNamedPipe,
        recvBuffer,
        sizeof(recvBuffer),
        &nBytesRead,
        NULL
    );
    if (0 >= ret)
    {
        printf("Read from client error: %d.\n", errno);
        return false;
    }
    else
    {
        message = static_cast<std::string>(recvBuffer);
        return true;
    }
}
DWORD WINAPI  ThreadProc(LPVOID)
{
    HANDLE hNamedPipe = CreateNamedPipe
    ("\\\\.\\pipe\\pipeTest",
        PIPE_ACCESS_INBOUND | FILE_FLAG_OVERLAPPED,
        PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE,
        3,//pipeTest管道最大实例数
        0,
        0,
        1000, // timeout in millseconds
        NULL);
    printf("\\\\.\\pipe\\pipeTest... \n");
    if (NULL == hNamedPipe || INVALID_HANDLE_VALUE == hNamedPipe)
    {
        return NULL;
    }
    printf("waiting for signal\n");
    while (true)
    {
        HANDLE hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);//初始状态为TRUE的人工重置对象
        if (INVALID_HANDLE_VALUE == hEvent)
        {
            return NULL;
        }
        OVERLAPPED ovlap;
        ZeroMemory(&ovlap, sizeof(OVERLAPPED));
        ovlap.hEvent = hEvent;//将一个事件与重叠I/O绑定
        std::string message;
        BOOL fPendingIO = FALSE;
        DWORD dwWait;
        printf("strat to read from client...\n");
        BOOL fConnected = ConnectNamedPipe(hNamedPipe, &ovlap);
        if (fConnected)
        {
            printf("ConnectNamedPipe failed with %d.\n", GetLastError());
            return 0;
        }
        switch (GetLastError())
        {
            // The overlapped connection in progress. 
        case ERROR_IO_PENDING:
            printf("ConnectNamedPipe status fPendingIO.\n");
            fPendingIO = true;
            break;
            // Client is already connected, so signal an event. 
        case ERROR_PIPE_CONNECTED:
            if (SetEvent(hEvent))
                break;
            // If an error occurs during the connect operation... 
        default:
        {
            printf("ConnectNamedPipe failed with %d.\n", GetLastError());
            return 0;
        }
        }
        /* 阻塞状态下,等待管道另一端的连入,当一个连接到来的时候,ovlap.hEvent会立即变为有信号状态 */
        dwWait = WaitForSingleObject(ovlap.hEvent, INFINITE);
        DWORD dwTransBytes = -1;
        switch (dwWait)
        {
        case 0:
            if (fPendingIO)
            {
                //获取Overlapped结果
                if (GetOverlappedResult(hNamedPipe, &ovlap, &dwTransBytes, TRUE) == FALSE)
                {
                    printf("ConnectNamedPipe  failed   %d\n", GetLastError());
                    return -1;
                }
            }
            //    读写完成
        case WAIT_IO_COMPLETION:
        {
            while (readMessage(hNamedPipe, message))
            {
                while (std::string::npos != message.find("|"))
                {
                    std::string tmpSourceStr = message.substr(0, message.find("|"));
                    if (tmpSourceStr.length() > 0)
                    {
                        printf("push message=%s in queue...\n", message.c_str());
                    }
                    message = message.substr(message.find("|") + 1);
                }
            }
            break;
        }
        }
        DisconnectNamedPipe(hNamedPipe);
    }


}




int main()
{
    HANDLE hThread1;
    DWORD threadId1;
    /*HANDLE hThread2;
    DWORD  threadId2;*/
    printf("another server is running\r\n");
    hThread1 = CreateThread(NULL, 0, ThreadProc, 0, 0, &threadId1);//创建线程
    //hThread2 = CreateThread(NULL, 0, ThreadProc, 0, 0, &threadId2);//创建线程
    while (true)
    {
        Sleep(30000);
    }
    return 0;
}
View Code

说明:服务器端事件同步使用了HANDLE hEvent = CreateEvent(NULL, TRUE, TRUE, NULL);创建了初始状态为TRUE的人工重置对象。

在使用CreateNamedPipe(FILE_FLAG_OVERLAPPED)时,使用ConnectNamedPipe(h, &op)后,会立即返回(非阻塞、异步的),这个时候一般是返回FALSE,使用GetLastError()会得到ERROR_IO_PENDING,表示这个请求是悬而未决的。我使用一个BOOL fPendingIO标识来记录所有悬而未决的的请求,fPendingIO=TRUE。然后使用WaitForMultipleObject方法等待这个事件。线程就会在此处阻塞。我们通过实际现象也可以看出,当客户端未建立连接时,服务器端在WaitForMultipleObject处等待事件变为有信号状态,继续往下执行。

现在来解释一下为什么开始创建事件时初始态为signaled。按照常理,WaitForMultipleObjects不会被阻塞,因为其中一个事件的状态为signaled。其实不然,它的状态在connectNamedPipe(h, &op)后已经改变了。对以OVERLAPPED关联的事件,当使用OVERLAPPED相关的方法操作后,其状态会可能会改变的,主要基于下面3个原则:1)当实际操作在函数返回前已经完成,事件的状态不会改变。2)当函数返回是,实际的操作没有完成,也即是说这个操作是Pending的,这个时候事件会被设置为nonsignaled.3) 当操作的Pending完成后,事件会被设置为signaled。有了上面的3条原则,OVERLAPPED关联的事件的状态变化就好理解了。当使用connectNamedPipe(h, &op)方法时,函数会立即返回,而实际这个操作并没有进行,而是Pending了,所以,event会由signaled变为nonsignaled,当真正有Client连接时,这个操作才会完成,这个时候,event会由nonsignaled变为signaled。这个时候,WaitForMultipleObject会继续执行下去。对于Pending后的操作,一定要使用GetOverlappedResult方法,判断结果。上面的原则适用ReadFile, WriteFile, ConnectNamedPipe, 和 TransactNamedPipe等函数。

参考:https://www.cnblogs.com/xtfnpgy/p/9285448.html#top

posted @ 2021-10-19 13:47  liweiyin  阅读(2971)  评论(0编辑  收藏  举报