微软提供了利用回调函数来实现有名管道的示例:Named Pipe Server Using Completion Routines. 该示例使用了ReadFileEx和WriteFileEx函数来进行有名管道的异步读写操作。我们在该示例的基础上进行改写来实现通过IOCP来进行异步读写。主要改动在哪下几个方面:
1.删去 CreateEvent操作,改为CreateIoCompletionPort来算成完成端口,替换事件触发为完成端口触发;2.IOCP不支持ReadFileEx和WriteFieEx函数,因此相应地替换为ReadFile和WriteFile函数。则回调函数的处理改为接收到GetQueuedCompletionStatus的返回后对完成状态进行处理;
3.在PIPEINST结构中增加opType成员变量,以便区分完成状态是牌读、写或者连接。
现在网上很多IOCP的教程示例都是基于SOCKET编程的,由于SOCKET编程相对于管道编程更加复杂,使得初学者不易掌握。而管道编程相对简单,使得初学者可以专注于IOCP部分的编程,因而更易掌握。在掌握了管道上的IOCP编程后再去学习SOCKET上的IOCP便不会觉得难了。而且管道上的IOCP编程还是有其实用性的,比如我们要编写一个网络通信程序,就可以给这个程序编写一个控制台程序,两个程序通过管道进行交互,这样网络能让部分和管道部分都可以放在一个完成端口内进行处理,不再需要用多个线程,效率就比较高,程序架构也会比较简单。当然我们也可以使用线程池的设计,但SOCKET和管道都可以在线程池里进行统一处理,不需要单独为管道处理建立一个线程。
#include <windows.h>
#include <stdio.h>
#include <tchar.h>
#include <strsafe.h>
enum enum_op_type {
OP_ACCEPT,
OP_SEND,
OP_RECV,
OP_MAX,
};
const char *op_names[] = {
"ACCEPT",
"SEND",
"RECV"
};
#define PIPE_TIMEOUT 5000
#define BUFSIZE 4096
typedef struct
{
OVERLAPPED oOverlap;
DWORD opType;
HANDLE hPipeInst;
TCHAR chRequest[BUFSIZE];
DWORD cbRead;
TCHAR chReply[BUFSIZE];
DWORD cbToWrite;
} PIPEINST, *LPPIPEINST;
VOID DisconnectAndClose(LPPIPEINST);
BOOL CreateAndConnectInstance();
BOOL ConnectToNewClient(HANDLE);
VOID GetAnswerToRequest(LPPIPEINST);
VOID WINAPI CompletedWriteRoutine(DWORD, DWORD, LPOVERLAPPED);
VOID WINAPI CompletedReadRoutine(DWORD, DWORD, LPOVERLAPPED);
// HANDLE hPipe;
HANDLE hIocp;
int _tmain(VOID)
{
DWORD dwErr;
BOOL fSuccess;
DWORD dwNoOfBytes = 0;
ULONG_PTR ulKey = 0;
OVERLAPPED* pov = NULL;
// Create one event object for the connect operation.
hIocp = CreateIoCompletionPort(INVALID_HANDLE_VALUE,NULL,0,0);
// Call a subroutine to create one instance, and wait for
// the client to connect.
CreateAndConnectInstance();
while (1)
{
// Wait for a completion notification.
pov = NULL;
fSuccess = GetQueuedCompletionStatus(
hIocp, // Completion port handle
&dwNoOfBytes, // Bytes transferred
&ulKey,
&pov, // OVERLAPPED structure
INFINITE // Notification time-out interval
);
if (FALSE == fSuccess) {
dwErr = GetLastError();
printf("GetQueuedCompletionStatus: dwErr=%x.\n", dwErr);
}
// Get the base address of the RECEIVE_CONTEXT structure
// containing the OVERLAPPED structure received.
LPPIPEINST ppi = CONTAINING_RECORD(pov, PIPEINST, oOverlap);
DWORD opType = ppi->opType;
printf("GetQueuedCompletionStatus: opType=%s,dwNoOfBytes=%d.\n", op_names[opType], dwNoOfBytes);
if (OP_ACCEPT == opType) {
// Allocate storage for this instance.
ppi->cbToWrite = 0;
CompletedWriteRoutine(0, 0, (LPOVERLAPPED)ppi);
// Create new pipe instance for the next client.
CreateAndConnectInstance();
// The wait is satisfied by a completed read or write
// operation. This allows the system to execute the
// completion routine.
}
else if (OP_RECV == opType) {
if (!dwNoOfBytes) {
DisconnectAndClose(ppi);
}
else {
CompletedReadRoutine(0, dwNoOfBytes, (LPOVERLAPPED)ppi);
}
}
else {
CompletedWriteRoutine(0, dwNoOfBytes, (LPOVERLAPPED)ppi);
}
}
return 0;
}
// CompletedWriteRoutine(DWORD, DWORD, LPOVERLAPPED)
// This routine is called as a completion routine after writing to
// the pipe, or when a new client has connected to a pipe instance.
// It starts another read operation.
VOID WINAPI CompletedWriteRoutine(DWORD dwErr, DWORD cbWritten,
LPOVERLAPPED lpOverLap)
{
LPPIPEINST lpPipeInst;
BOOL fRead = FALSE;
// lpOverlap points to storage for this instance.
lpPipeInst = (LPPIPEINST)lpOverLap;
// The write operation has finished, so read the next request (if
// there is no error).
if ((dwErr == 0) && (cbWritten == lpPipeInst->cbToWrite)) {
DWORD dwRead;
lpPipeInst->opType = OP_RECV;
fRead = ReadFile(
lpPipeInst->hPipeInst,
lpPipeInst->chRequest,
BUFSIZE * sizeof(TCHAR),
&dwRead,
(LPOVERLAPPED)lpPipeInst);
//(LPOVERLAPPED_COMPLETION_ROUTINE)CompletedReadRoutine);
if (!fRead && ERROR_IO_PENDING != GetLastError()) {
printf("ReadFile: dwRead=%d,GLE=%d\n", dwRead, GetLastError());
}
}
// Disconnect if an error occurred.
printf("%s: PipInst=%x,dwErr=%x,cbWritten=%d,fRead=%d.\n",
__func__, (int)lpPipeInst->hPipeInst, dwErr, cbWritten, fRead);
/*
if (!fRead) {
DisconnectAndClose(lpPipeInst);
}
*/
}
// CompletedReadRoutine(DWORD, DWORD, LPOVERLAPPED)
// This routine is called as an I/O completion routine after reading
// a request from the client. It gets data and writes it to the pipe.
VOID WINAPI CompletedReadRoutine(DWORD dwErr, DWORD cbBytesRead,
LPOVERLAPPED lpOverLap)
{
LPPIPEINST lpPipeInst;
BOOL fWrite = FALSE;
// lpOverlap points to storage for this instance.
/*
ERROR_BROKEN_PIPE
109 (0x6D)
The pipe has been ended.
*/
lpPipeInst = (LPPIPEINST)lpOverLap;
if (0x6d == dwErr) {
printf("%s: PipInst=%x, the pipe has been ended.\n",
__func__, (int)lpPipeInst->hPipeInst);
}
else {
printf("%s: PipInst=%x,dwErr=%x,cbBytesRead=%d.\n",
__func__, (int)lpPipeInst->hPipeInst, dwErr, cbBytesRead);
}
// The read operation has finished, so write a response (if no
// error occurred).
if ((dwErr == 0) && (cbBytesRead != 0))
{
DWORD dwWritten;
GetAnswerToRequest(lpPipeInst);
lpPipeInst->opType = OP_SEND;
fWrite = WriteFile(
lpPipeInst->hPipeInst,
lpPipeInst->chReply,
lpPipeInst->cbToWrite,
&dwWritten,
(LPOVERLAPPED)lpPipeInst);
// (LPOVERLAPPED_COMPLETION_ROUTINE)CompletedWriteRoutine);
if (!fWrite) {
printf("WriteFie: dwWritten=%d,GLE=%d\n", dwWritten, GetLastError());
}
}
// Disconnect if an error occurred.
/*
if (!fWrite) {
DisconnectAndClose(lpPipeInst);
}
*/
}
// DisconnectAndClose(LPPIPEINST)
// This routine is called when an error occurs or the client closes
// its handle to the pipe.
VOID DisconnectAndClose(LPPIPEINST lpPipeInst)
{
// Disconnect the pipe instance.
if (!DisconnectNamedPipe(lpPipeInst->hPipeInst))
{
printf("DisconnectNamedPipe failed with %d.\n", GetLastError());
}
// Close the handle to the pipe instance.
CloseHandle(lpPipeInst->hPipeInst);
// Release the storage for the pipe instance.
if (lpPipeInst != NULL)
GlobalFree(lpPipeInst);
}
// CreateAndConnectInstance(LPOVERLAPPED)
// This function creates a pipe instance and connects to the client.
// It returns TRUE if the connect operation is pending, and FALSE if
// the connection has been completed.
BOOL CreateAndConnectInstance()
{
LPCWSTR lpszPipename = TEXT("\\\\.\\pipe\\mynamedpipe");
HANDLE hPipe = CreateNamedPipe(
lpszPipename, // pipe name
PIPE_ACCESS_DUPLEX | // read/write access
FILE_FLAG_OVERLAPPED, // overlapped mode
PIPE_TYPE_MESSAGE | // message-type pipe
PIPE_READMODE_MESSAGE | // message read mode
PIPE_WAIT, // blocking mode
PIPE_UNLIMITED_INSTANCES, // unlimited instances
BUFSIZE * sizeof(TCHAR), // output buffer size
BUFSIZE * sizeof(TCHAR), // input buffer size
PIPE_TIMEOUT, // client time-out
NULL); // default security attributes
printf("CreateNamedPipe, hPipe=%llx.\n", (INT64)hPipe);
if (hPipe == INVALID_HANDLE_VALUE)
{
printf("CreateNamedPipe failed with %d.\n", GetLastError());
return 0;
}
HANDLE hRet = CreateIoCompletionPort(hPipe, hIocp, NULL, 0);
// Call a subroutine to connect to the new client.
return ConnectToNewClient(hPipe);
}
BOOL ConnectToNewClient(HANDLE hPipe)
{
BOOL fConnected, fPendingIO = FALSE;
LPPIPEINST lpPipeInst = (LPPIPEINST)GlobalAlloc(GPTR, sizeof(PIPEINST));
if (lpPipeInst == NULL)
{
printf("GlobalAlloc failed (%d)\n", GetLastError());
return 0;
}
ZeroMemory(&lpPipeInst->oOverlap, sizeof(OVERLAPPED));
lpPipeInst->hPipeInst = hPipe;
lpPipeInst->opType = OP_ACCEPT;
// Start an overlapped connection for this pipe instance.
fConnected = ConnectNamedPipe(hPipe, &lpPipeInst->oOverlap);
// Overlapped ConnectNamedPipe should return zero.
if (fConnected)
{
printf("ConnectNamedPipe failed with %d.\n", GetLastError());
return 0;
}
switch (GetLastError())
{
// The overlapped connection in progress.
case ERROR_IO_PENDING:
fPendingIO = TRUE;
break;
// Client is already connected, so signal an event.
case ERROR_PIPE_CONNECTED:
{
CreateAndConnectInstance();
}
break;
// If an error occurs during the connect operation...
default:
{
printf("ConnectNamedPipe failed with %d.\n", GetLastError());
return 0;
}
}
return fPendingIO;
}
VOID GetAnswerToRequest(LPPIPEINST pipe)
{
_tprintf(TEXT("[%llx] %s\n"), (INT64)pipe->hPipeInst, pipe->chRequest);
StringCchCopy(pipe->chReply, BUFSIZE, TEXT("Default answer from server"));
pipe->cbToWrite = (lstrlen(pipe->chReply) + 1) * sizeof(TCHAR);
}