博客
关于我
强烈建议你试试无所不能的chatGPT,快点击我
WindowsIPC机制--命名管道(本地进程间通信实现解析)
阅读量:771 次
发布时间:2019-03-24

本文共 10889 字,大约阅读时间需要 36 分钟。

命名管道就是在服务器在创建一个管道时候给该管道提供一个名称,客户端通过该名称连接道管道上,实现双向通信的一个概念。在本地指定一个"\.\Pipe<PipeName>"的本地名称,这里的“.”代表本地系统,在内核层会对名称进行解析,然后由命名管道的驱动程序进行处理。而我们的命名管道同命名的邮槽比较像,实质上是一个本地的文件系统,可以通过WinAPI的那些文件操作函数对管道进行读写,与普通文件对象不同的是,它不是一个永久的文件对象,也不存在与磁盘的文件上,它的存在伴随着服务器和客户端的存在。用到的关键WinAPI有:

CreateNamedPipe
ConnectNamedPipe
DisconnectNamedPipe
接下来我们看一下CreateNamedPipe的实现:
内部进行一些初始化操作,类似于上篇博客中命名邮件槽的实现,最终调用NtCreateNamedPipeFile来实现:

/* Convert the name */    Result = RtlDosPathNameToNtPathName_U(lpName,                                           &NamedPipeName,                                           NULL,                                           NULL);    /* Now we can initialize the object attributes */    InitializeObjectAttributes(&ObjectAttributes,                               &NamedPipeName,                               Attributes,                               NULL,                               SecurityDescriptor);........... /* Now create the pipe */    Status = NtCreateNamedPipeFile(&PipeHandle,                                   DesiredAccess,                                   &ObjectAttributes,                                   &Iosb,                                   ShareAccess,                                   FILE_OPEN_IF,                                   CreateOptions,                                   WriteModeMessage,                                   ReadModeMessage,                                   NonBlocking,                                   nMaxInstances,                                   nInBufferSize,                                   nOutBufferSize,                                   &DefaultTimeOut);

将参数赋值到NAMED_PIPE_CREATE_PARAMETERS结构体中,接下来调用IoCreateFile->IopCreateFile:

typedef struct _NAMED_PIPE_CREATE_PARAMETERS{
ULONG NamedPipeType; ULONG ReadMode; ULONG CompletionMode; ULONG MaximumInstances; ULONG InboundQuota; ULONG OutboundQuota; LARGE_INTEGER DefaultTimeout; BOOLEAN TimeoutSpecified;} NAMED_PIPE_CREATE_PARAMETERS, *PNAMED_PIPE_CREATE_PARAMETERS; /* Set Settings */ Buffer.NamedPipeType = NamedPipeType; Buffer.ReadMode = ReadMode; Buffer.CompletionMode = CompletionMode; Buffer.MaximumInstances = MaximumInstances; Buffer.InboundQuota = InboundQuota; Buffer.OutboundQuota = OutboundQuota; /* Call I/O */ return IoCreateFile(FileHandle, DesiredAccess, ObjectAttributes, IoStatusBlock, NULL, 0, ShareAccess, CreateDisposition, CreateOptions, NULL, 0, CreateFileTypeNamedPipe, (PVOID)&Buffer, 0);

如果是要创建命名管道,则进行一些参数的校验:

/* Now check if this is a named pipe */        if (CreateFileType == CreateFileTypeNamedPipe)        {
/* Make sure we have extra parameters */ if (!ExtraCreateParameters) {
DPRINT1("Invalid parameter: ExtraCreateParameters == 0!\n"); return STATUS_INVALID_PARAMETER; } /* Get the parameters and validate them */ NamedPipeCreateParameters = ExtraCreateParameters; if ((NamedPipeCreateParameters->NamedPipeType > FILE_PIPE_MESSAGE_TYPE) || (NamedPipeCreateParameters->ReadMode > FILE_PIPE_MESSAGE_MODE) || (NamedPipeCreateParameters->CompletionMode > FILE_PIPE_COMPLETE_OPERATION) || (ShareAccess & FILE_SHARE_DELETE) || ((Disposition < FILE_OPEN) || (Disposition > FILE_OPEN_IF)) || (CreateOptions & ~FILE_VALID_PIPE_OPTION_FLAGS)) {
/* Invalid named pipe create */ DPRINT1("Invalid named pipe create\n"); return STATUS_INVALID_PARAMETER; } }

对一些参数进行赋值之后,调用对象管理器的函数来创建对象:

/* Setup the Open Packet */    OpenPacket->Type = IO_TYPE_OPEN_PACKET;    OpenPacket->Size = sizeof(*OpenPacket);    OpenPacket->AllocationSize = SafeAllocationSize;    OpenPacket->CreateOptions = CreateOptions;    OpenPacket->FileAttributes = (USHORT)FileAttributes;    OpenPacket->ShareAccess = (USHORT)ShareAccess;    OpenPacket->Options = Options;    OpenPacket->Disposition = Disposition;    OpenPacket->CreateFileType = CreateFileType;    OpenPacket->ExtraCreateParameters = ExtraCreateParameters;    OpenPacket->InternalFlags = Flags;    OpenPacket->TopDeviceObjectHint = DeviceObject;    /* Update the operation count */    IopUpdateOperationCount(IopOtherTransfer);    /*     * Attempt opening the file. This will call the I/O Parse Routine for     * the File Object (IopParseDevice) which will create the object and     * send the IRP to its device object. Note that we have two statuses     * to worry about: the Object Manager's status (in Status) and the I/O     * status, which is in the Open Packet's Final Status, and determined     * by the Parse Check member.     */    Status = ObOpenObjectByName(ObjectAttributes,                                NULL,                                AccessMode,                                NULL,                                DesiredAccess,                                OpenPacket,                                &LocalHandle); //......ObOpenObjectByName内部调用     /* Now do the lookup */    Status = ObpLookupObjectName(TempBuffer->ObjectCreateInfo.RootDirectory,                                 &ObjectName,                                 TempBuffer->ObjectCreateInfo.Attributes,                                 ObjectType,                                 AccessMode,                                 ParseContext,                                 TempBuffer->ObjectCreateInfo.SecurityQos,                                 NULL,                                 PassedAccessState,                                 &TempBuffer->LookupContext,                                 &Object); ///当得到创建对象之后  返回对象句柄         /* Create the actual handle now */        Status2 = ObpCreateHandle(OpenReason,                                  Object,                                  ObjectType,                                  PassedAccessState,                                  0,                                  TempBuffer->ObjectCreateInfo.Attributes,                                  &TempBuffer->LookupContext,                                  AccessMode,                                  NULL,                                  Handle);

经过ObpLookupObjectName之后,创建对象之后,ObpCreateHandle返回对象句柄。

我做了一个简单的Ring3层测试,是客户端发送字母,经过服务器处理之后,把所有字母改为大写字母,接下来附上部分测试代码:

//服务器//给服务器创建一个互斥体对象,保证只运行一个互斥体实例	HANDLE MutexHandle = OpenMutex(MUTEX_ALL_ACCESS, true, _T("NamedPipeMutex"));	if (MutexHandle == NULL)	{
//创建一个命名的互斥体 CreateMutex(NULL, true, _T("NamedPipeMutex")); } else {
this->MessageBox(_T("服务器只能运行一个实例")); TerminateProcess(GetCurrentProcess(), 0); } void CNamed_PipeDlg::OnBnClickedStartserver(){
// TODO: 在此添加控件通知处理程序代码 UpdateData(TRUE); //管道命名规则就是这样,只有最后的Pipe\\后面的管道名可以修改 CString PipeName = _T("\\\\.\\Pipe\\KookNut"); if (m_CEdit_Max_Connect_Count > 0 && m_CEdit_Max_Connect_Count < 100) {
for (UINT i = 0; i < m_CEdit_Max_Connect_Count; i++) {
// 创建命名管道实例 //在创建时必须指定最大允许连接的实例数(1~255) //connect连接时异步执行,不需要等死 m_UserData[i].PipeHandle = CreateNamedPipe(PipeName, 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 LastError = GetLastError(); this->MessageBox(_T("创建管道错误")); 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(_T("命名管道—服务器(运行)")); this->MessageBox(_T("服务启动成功")); }}UINT ThreadProcedure(LPVOID ParameterData){
DWORD ReturnLength = 0; TCHAR BufferData[0x1000] = {
0 }; TCHAR ReturnValue[0x1000] = {
0 }; USER_DATA UserData = *(USER_DATA*)ParameterData; OVERLAPPED Overlapped = {
0, 0, 0, 0, UserData.EventHandle };//重叠模型 while (1) {
/*命名管道的连接函数,等待客户端的连接 可支持同步与异步等待 由创建管道时的FILE_FLAG_OVERLAPPED决定, 如果不设置FILE_FLAG_OVERLAPPED,那么第二参数可以为NULL, 直到客户端上线或者有什么错误发生,函数才返回*/ ConnectNamedPipe(UserData.PipeHandle, &Overlapped); // 实现重叠I/0,等待OVERLAPPED结构的事件对象 //一旦连接成功,事件就会受信,如果不成功,则永久等待连接 WaitForSingleObject(UserData.EventHandle, INFINITE); // 检测I/0是否已经完成,如果未完成,意味着该事件对象是人工设置,即服务需要停止 if (!GetOverlappedResult(UserData.PipeHandle, &Overlapped, &ReturnLength, true)) break; //For a ConnectNamedPipe or WaitCommEvent operation, this value is undefined. memset(BufferData, 0, 0x1000); // 从管道中读取客户端的请求信息 if (!ReadFile(UserData.PipeHandle, BufferData, 0x1000 * sizeof(TCHAR), &ReturnLength, NULL)) {
MessageBox(0, _T("读取管道错误"), 0, 0); break; } //小写转换大写 KtUpCase_Unicode(BufferData, ReturnValue); memset(BufferData, 0, 0x1000); _stprintf(BufferData, _T("%s"), ReturnValue); // 把反馈信息写入管道 WriteFile(UserData.PipeHandle, BufferData, sizeof(TCHAR)*(_tcslen(BufferData) + 1), &ReturnLength, NULL); // 断开客户端的连接,以便等待连接下一个客户端的到来 DisconnectNamedPipe(UserData.PipeHandle); } return 0;}void CNamed_PipeDlg::OnBnClickedStopserver(){
// TODO: 在此添加控件通知处理程序代码 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); CloseHandle(m_UserData[i].EventHandle); } this->SetWindowText(_T("命名管道—服务器(停止)")); this->MessageBox(_T("停止启动成功"));}

客户端代码:

void CNamed_PipeClientDlg::OnBnClickedButtonSubmitClient(){
// TODO: 在此添加控件通知处理程序代码 UpdateData(TRUE); //请求连接管道连接成功之后,服务器waitfor事件触信 HANDLE NamedPipeHandle = CreateFile(_T("\\\\.\\Pipe\\KookNut"), GENERIC_READ | GENERIC_WRITE, \ 0, NULL, OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL); if (NamedPipeHandle == INVALID_HANDLE_VALUE) {
this->MessageBox(_T("打开管道失败,服务器尚未启动,或者客户端数量过多")); return; } DWORD ReturnLength = 0; TCHAR BufferData[0x1000] = {
0 }; //获取字符串数据 _stprintf(BufferData, _T("%s"), LowerCase.GetString()); // 把数据写入管道 WriteFile(NamedPipeHandle, BufferData, sizeof(TCHAR)*(_tcslen(BufferData)+1), &ReturnLength, NULL); memset(BufferData, 0, 0x1000); // 读取服务器的反馈信息 while (1) {
if (!ReadFile(NamedPipeHandle, BufferData, 0x1000 * sizeof(TCHAR), &ReturnLength, NULL)) {
continue; } break; } CloseHandle(NamedPipeHandle); TCHAR* v1 = UpperCase.GetBuffer(); UpperCase.ReleaseBuffer(); memcpy(v1, BufferData, sizeof(TCHAR)*(_tcslen(BufferData) + 1)); UpdateData(FALSE);}

“天行健,君子以自强不息。”

想念我学校天行健餐厅的美味了,快快开学,大家都平安!
参考书籍:
《Windows内核原理与实现》
Reactos

转载地址:http://yitkk.baihongyu.com/

你可能感兴趣的文章