本文共 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/