IPC之命名管道

1.管道是通过IO接口存取得字节流, windows中利用得是ReadFile()和WriteFile(),windows利用单一句柄支持双向IO,命名管道也称做FIFO(first in first out)
命名管道得机制:一个进程把数据放到管道里,另一个知道管道名字得进程把数据把取走,实际是用于进程间通信得一段共享内存,创建管道得进程称为管道服务器,链接到一个管道得进程为管道客户机,用以下函数 

 

 管道实际是用于进程间通信的一段共享内存,创建管道的进程称为管道服务器,连
接到- 一个管道的进程为管道客户机。可以用以下函数创建管道,即
HANDLB CreateNamedPipel
LPCTSTR IpName,
DWORD dwopenMode,
DWORD dwPipeMode,
DWORD nMaxInstances,
DWORD nOutBufferSi ze,
DWORD nInBufferSize,
DWORD nDefaultrimeOut,
LPSECURITY,ATTRIBUTES 1pSecurityAttributes
);
其中参数的含义如F:
IpName: 管道名称。用下面的方法命名;
每一一个命名管道都有一一个唯一的名字以区分存在于系统的命名对象列表中的其他命
名管道。管道服务器在调用CreateNamedPipe()创建命名管道的一一个或多个实例时为其指
定了名称。对于管道客户机,则是在调用CreateFile()或CallINamedPipe()函数以连接一
个命名管道实例时对管道名进行指定。命名管道的命名规范与以后介绍的邮槽有些类似,
对其标识也是采用的UNC 格式:
\\[host.name 

]\Pipe\[Path]Name
其中,第- 一部分“\[host_name]”指定了服务器的名字,命名管道服务即在此服务
器创建,其字符串部分可表示为一一个小数点(表示本机)、星号(当前网络字段)、域名
或是一一个真 正的服务; 第二部分“Pipe”与邮槽的“Mailslot”
样是一一个不可变化的
硬编码字符串,以表示该文件是从属于NPFS; 第三部分“[Path]Name" 则使应用程序
可以唯一-定义及标识- 一个命名管道的名字,而且可以设置多级目录。
dwOpenMode: 管道建方式。可以是下面值的组合:
> PIPE ACCESS INBOUND: 管道只能用作接收数据。
4
PIPE ACCESS _OUTBOUND: 管道只能用作发送数据。
PIPE.ACCESS_ DUPLEX: 管道既可以发送也可以接收数据。
上面这三三个值只能够取其中- 一个,同时也可包括以下的- 一个或两个标识:
管道用于同步发送和接收数据,在系统内部
FILE FLAG WRITE THROUGH:
对于命名管道的处理上不经缓冲区并能直接发送,并且只有在数据被发送到目
标地址时发送函数才会返回。如果不设置这个参数,那么需要在数据积累到一
定量时才发送,并且对于发送函数的调用会马上返回。
 > FILE FLAG OVERLAPPED: 管道可以用于异步输入和输出,异步读写的有关
方法和文件异步读写是相同的。
dwPipeMode: 命名管道模式。可以是下面值的组合:
> PIPE_TYPE_BYTE: 数据在通过管道发送时作为字节流发送,不能与
PIPE READMODE MESSAGE 共用。
PIPE.TYPE.MESSAGE: 数据在通过管道发送时作为消息发送,不能与
PIPE READMODE BYTE 共用。
IPE_READMODE_BYTE: 在接收数据时接收字节流。

PIPE_READMODE_MESSAGE: 在接收数据日接收消息。
IPE _WAIT: 使用等待模式,在读、写和建立连接时都需要管道的另- -方完成相

应动作后才会返回。
IPE NOWAIT: 使用#I 等待模式,在读、写和建立连接时不需要管道的另- 一方
完成相应动作后就会立即返回。
该管道最大的实例数量。在第- 一次建立服务器方管道时这个参数表
nMaxInstances:
明该管道可以同时存在的数量。PIPE UNLIMITED INSTANCES 表明不对数量进行
限制。
nOutBufferSize 和nnBufferSize: 输出和输入缓冲区大小。
nDefaultTimeOut: 指定默认的超时时间(以亳秒为单位),如果在创建时设置为
NMPWAIT.USE DEFAULT.WAIT 表明无限制的等待,而以后服务器方的其他管道实例
也需要设置相同的值。
lpSecurtytltrbutes: 描述安全信息的一一个结构,一般设置为NULL。如果创建或打
开失败则返回INVALID HANDLE VALUE。可以通过GetLastErrort0得到错误信息。
其他管道函数简介如下所述。
CallNamedPipeO: 连接到一个命名管道,读取或写入数据之后关闭它。
ConnectNamedPipeQ: 服务进程准备好一个连接到客户进程的管道,并等待一
个客户进程连接上为止。
DiscocctNamedPipc: 服务端用来断开与客户端的连接。
GetNamedPipeHandleta 获取一个命名管道的状态信息。
获取一个命名管道的信息。
GetNamedPipeInfoO
PeekNamedPipeO: 从一个匿名或命名管道中复制数据到一个缓冲区。
SetNamedPipeHandleStat 设置管道的类型及其他状态信息,比如说是比特
流还是消息流管道。
TransactNamedPipeO: 从一 个消息管道读 息或向其 写入肖息。
WaitNamedPipeO:使服务器进程等待来自客户的实例连接。

 

2.命名管道服务端与客户端之间通信的实现流程:

 -- -4.2.2
命名管道服务端与客户端之间通信的实现流程一
(1) 连接建立
使用消息管道、邮槽和套接字通信
服务端通过函数CreateNamedPipe()创建- 一个命名管道的实例并返回用于今后操作
的句柄,或为已存在的管道创建新的实例。如果在已定义超时值变为0 以前,有一一个实
例管道可以使用,则创建成功并返回管道句柄,并用以侦听来自客户端的连接请求,该
功能通过ConneclNamedPipe(实现。
另- 一方面,客户端通过函数WaitINamedPipe()使服务进程等待来自客户的实例连接。
如果在超时值变为0以前,有一一个管道可以为连接使用,则WaitNamedPipe()返回TRUE,
并通过调用CreateFile()或CallINamedPipe()来呼叫对服务端的连接。此时服务端将接受客
户端的连接请求,成功建立连接,服务端ConnectNamedPipe(返回TRUE,客户端
CreatcFile()返回- 一个指向管道文件的句柄。
从时序上讲,首先是客户端通过WaitNamedPipe()使服务端的CreateFile()在限定时间
内创建实例成功,然后双方通过ConnectNamedPipe()和CreateFile()成功连接,并返回用
于通信的文件句柄,此时双方即可进行通信。
(2) 通信实现
建立连接之后,客户和服务端可以通过得到的管道文件句柄利用ReadFile()和
WriteFile()进行彼此间的信息交换。
(3) 连接终止
当客户端与服务端的通信结束,或由于某种原因一一方需要断开时,客户端应调用
CloseFile(),而服务端应接着调用DisconnectNamedPipe。当然服务端亦可通过单方面
调用DisconnectNamedPipe()终止连接。最后应调用CloseHandle()来关闭该管道。

 

 

Server端 (1.启动服务 创建管道和事件2.创建线程负责与客户端通信(利用IO重叠与创建的事件联系)。3.接收客户端数据做出计算再反馈到客户端)
一 启动服务StartServer
1.创建管道的名字 CString PipeFullPathData = L"\\\\.\\Pipe\\NamedPipePipe";
2.创建管道 分配事件(因为是NamePipe),通过线程来通信
3.代码
void CServerDlg::OnBnClickedButtonStartServer()
{
UpdateData(TRUE);
CString PipeFullPathData = L"\\\\.\\Pipe\\NamedPipePipe";


if (m_CEdit_Max_Connect_Count > 0 && m_CEdit_Max_Connect_Count < 100)
{
for (UINT i = 0; i < m_CEdit_Max_Connect_Count; i++)
{
// 创建管道实例
m_UserData[i].PipeHandle = CreateNamedPipe(PipeFullPathData, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, \
PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_WAIT, m_CEdit_Max_Connect_Count, 0, 0, 1000, NULL);
if (m_UserData[i].PipeHandle == INVALID_HANDLE_VALUE)
{
DWORD ErrorCode = GetLastError();
this->MessageBox(L"创建管道错误!");
return;
}
// 为每个管道实例创建一个事件对象,用于实现重叠IO
m_UserData[i].EventHandle = CreateEvent(NULL, FALSE, FALSE, NULL);
// 为每个管道实例分配一个线程,用于响应客户端的请求
m_UserData[i].ThreadHandle = AfxBeginThread(ThreadProcedure, &m_UserData[i], THREAD_PRIORITY_NORMAL);
}

{
this->SetWindowText(L"命名管道—服务器(运行)");
this->MessageBox(L"服务启动成功");
}
}
}
事件线程函数(利用的是IO重叠)注意的是BufferData的传入 两个或多个数据的传入这样做

UINT ThreadProcedure(LPVOID ParameterData)
{
DWORD ReturnLength = 0;
char BufferData[0x400] = { 0 };

USER_DATA UserData = *(USER_DATA*)ParameterData;
OVERLAPPED Overlapped = { 0, 0, 0, 0, UserData.EventHandle};

while (1)
{
memset(BufferData, 0, sizeof(BufferData));
// 命名管道的连接函数,等待客户端的连接(只针对NT)
ConnectNamedPipe(UserData.PipeHandle, &Overlapped);

// 实现重叠I/0,等待OVERLAPPED结构的事件对象
WaitForSingleObject(UserData.EventHandle, INFINITE);

// 检测I/0是否已经完成,如果未完成,意味着该事件对象是人工设置,即服务需要停止
if (!GetOverlappedResult(UserData.PipeHandle, &Overlapped, &ReturnLength, true))
break;
// 从管道中读取客户端的请求信息
if (!ReadFile(UserData.PipeHandle, BufferData, 0x400, &ReturnLength, NULL))
{
MessageBox(0, L"读取管道错误!", 0, 0);
break;
}
int v1, v2;
sscanf(BufferData, "%d %d", &v1, &v2); //字符串转换为%d
__ServerDlg->m_v1 = v1;
__ServerDlg->m_v2 = v2;
__ServerDlg->m_v3 = v1 + v2;
memset(BufferData, 0, sizeof(BufferData));
sprintf(BufferData, "%d", __ServerDlg->m_v3);//从客户端到主控端出现
// 把反馈信息写入管道
WriteFile(UserData.PipeHandle, BufferData, strlen(BufferData), &ReturnLength, NULL);
__ServerDlg->SetDlgItemInt(IDC_EDIT_V1, v1, true);
__ServerDlg->SetDlgItemInt(IDC_EDIT_V2, v2, true);
__ServerDlg->SetDlgItemInt(IDC_EDIT_V3, __ServerDlg->m_v3, true);
// 断开客户端的连接,以便等待下一客户的到来
DisconnectNamedPipe(UserData.PipeHandle);
}

return 0;
}

关闭服务
void CServerDlg::OnBnClickedButtonStopServer()
{

DWORD dwNewMode = PIPE_TYPE_BYTE | PIPE_READMODE_BYTE | PIPE_NOWAIT;
for (UINT i = 0; i < m_CEdit_Max_Connect_Count; i++)
{
// 设置重叠I/O的事件,使得工作线程安全结束
SetEvent(m_UserData[i].EventHandle);
Sleep(1);
CloseHandle(m_UserData[i].ThreadHandle);
CloseHandle(m_UserData[i].PipeHandle);
}

this->SetWindowText(L"命名管道—服务器(停止)");
this->MessageBox(L"停止启动成功");
}

Client端(提交数据到主控端,主控端反馈最终结果到客户端)
void CClientDlg::OnBnClickedButtonSubmitClient()
{
// TODO: 在此添加控件通知处理程序代码

HANDLE NamedPipeHandle = CreateFile(L"\\\\.\\Pipe\\NamedPipePipe", GENERIC_READ | GENERIC_WRITE, \
0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (NamedPipeHandle == INVALID_HANDLE_VALUE)
{
this->MessageBox(L"打开管道失败,服务器尚未启动,或者客户端数量过多");
return;
}

DWORD ReturnLength = 0;
char BufferData[0x400] = { 0 };
// 把两个整数(a,b)格式化为字符串

m_v1 = GetDlgItemInt(IDC_EDIT_V1);
m_v2 = GetDlgItemInt(IDC_EDIT_V2);
sprintf(BufferData, "%d %d", this->m_v1, this->m_v2);//v1,v2写入数组
// 把数据写入管道
WriteFile(NamedPipeHandle, BufferData, strlen(BufferData), &ReturnLength/*写入地*/, NULL);

memset(BufferData, 0,0x400);//清空
// 服务器反馈后,读取服务器的反馈信息
ReadFile(NamedPipeHandle, BufferData, 0x400, &ReturnLength, NULL);
sscanf(BufferData, "%d", &(this->m_v3));//Client写入 Server输出
/*因为是客户端做运算,然后主控端响应再反馈*/
SetDlgItemInt(IDC_EDIT_V3, m_v3, true);

CloseHandle(NamedPipeHandle);
}

posted @ 2018-03-11 19:58  _Flame  阅读(799)  评论(0编辑  收藏  举报