使用重叠输入和输出的服务器

【勇芳软件工作室】汉化HomePreviousNext

此示例是使用重叠操作来同时连接到多个客户端进程的单线程服务器进程。服务器进程创建固定数量的管道实例,每个管道实例可以连接到单独的客户端进程。当客户端进程完成使用其管道实例时,服务器将断开与客户端的连接,并重新使用管道实例连接到新的客户端。

与每个管道实例相关联的是一个包含事件对象的OVERLAPPED结构。该结构在管道实例的每个ReadFileWriteFileConnectNamedPipe操作中指定为参数。尽管该示例显示了不同管道实例上的并发操作,但它避免了在单个管道实例上的同时操作。因为相同的事件对象用于每个实例的读,写和连接操作,所以无法知道哪个操作的完成导致将事件设置为信号状态,以便使用相同的管道实例进行同时操作。

每个管道实例的事件句柄也存储在WaitForMultipleObjects函数使用的数组中。该函数等待其中一个事件发信号,其返回值是满足等待事件的数组索引。该示例使用此索引来检索包含管道实例的信息的结构。服务器使用结构的fPendingIO成员来跟踪实例上最近的I / O操作是否挂起,需要调用GetOverlappedResult函数。它使用结构的dwState成员来确定必须为实例执行的下一个操作。

当函数返回时,重叠的ReadFileWriteFileConnectNamedPipe操作可能已经完成,或者当函数返回时,它们可能仍然处于挂起状态。如果操作未决,则指定的OVERLAPPED结构中的事件对象将在函数返回之前设置为非信号状态。当待处理操作完成后,系统会将事件对象的状态设置为发信号。如果在函数返回之前操作完成,则事件对象的状态不会改变。

由于该示例使用手动复位事件对象,事件对象的状态不会被WaitForMultipleObjects功能更改为非指定状态。这很重要,因为该示例依赖于事件对象保持在信号状态,除非有待处理的操作。

如果ReadFile WriteFileConnectNamedPipe返回时操作已经完成,则函数的返回值表示结果。对于读写操作,还会返回传输的字节数。如果操作仍在等待,则ReadFileWriteFileConnectNamedPipe函数返回FALSE,并且GetLastError函数返回ERROR_IO_PENDING。在这种情况下,操作完成后,使用GetOverlappedResult功能检索结果。GetOverlappedResult仅返回待处理操作的结果,并且不会报告在返回的重叠ReadFileWriteFileConnectNamedPipe函数之前完成的操作结果。

在与客户端断开连接之前,上一节中的多线程服务器示例使用FlushFileBuffers来确保客户端已读取写入管道的所有内容。这将破坏重叠I / O的目的,因为在等待客户端清空管道时,flush操作会阻止服务器线程的执行。因此,有必要在断开连接之前等待客户端已经完成的信号。在此示例中,信号是在客户端进程关闭其句柄后尝试从管道读取的错误。

#include < windows.h >

#define CONNECTING_STATE 0

#define READING_STATE 1

#define WRITING_STATE 2

#define INSTANCES 4

typedef结构en

{

OVERLAPPED oOverlap;

HANDLE hPipeInst;

CHAR chBuf[BUFSIZE];

DWORD cbToWrite;

DWORD dwState;

BOOL fPendingIO;

} PIPEINST, *LPPIPEINST;

VOID DisconnectAndReconnect(DWORD);

BOOL ConnectToNewClient(HANDLE, LPOVERLAPPED);

VOID GetDataToWriteToClient(LPPIPEINST);

PIPEINST Pipe[INSTANCES];

HANDLE hEvents[INSTANCES];

DWORD主(VOID)

{

DWORD i, dwWait, cbBytes, dwErr;

BOOL fSuccess;

LPTSTR lpszPipename = "\\\\.\\pipe\\mynamedpipe";

//初始循环创建一个命名管道的几个实例

//以及每个实例的事件对象。An

//重新启动ConnectNamedPipe操作

//每个实例。

for (i = 0; i < INSTANCES; i++)

{

//为此实例创建一个事件对象。

hEvents[i] = CreateEvent(

NULL, //没有安全属性

TRUE, //手动重置事件

TRUE, //初始状态=发信号

NULL); //未命名的事件对象

if (hEvents[i] == NULL)

MyErrExit("CreateEvent");

Pipe[i].oOverlap.hEvent = hEvents[i];

Pipe[i].hPipeInst = CreateNamedPipe(

lpszPipename, //管道名称

PIPE_ACCESS_DUPLEX | //读/写访问

FILE_FLAG_OVERLAPPED, //重叠模式

PIPE_TYPE_MESSAGE | // message-type pipe

PIPE_READMODE_MESSAGE | //消息读取模式

PIPE_WAIT, //阻止模式

INSTANCES, //实例数

BUFSIZE, //输出缓冲区大小

BUFSIZE, //输入缓冲区大小

PIPE_TIMEOUT, //客户端超时

NULL); //没有安全属性

if (Pipe[i].hPipeInst == INVALID_HANDLE_VALUE)

MyErrExit("CreatePipe");

//调用子程序连接到新的客户端

Pipe[i].fPendingIO = ConnectToNewClient(

Pipe[i].hPipeInst,

&Pipe[i].oOverlap);

Pipe[i].dwState = Pipe[i].fPendingIO ?

CONNECTING_STATE : //仍然连接

READING_STATE; //准备阅读

}

而(1)

{

//等待事件对象发出信号,指示

//完成重叠的读,写或

//连接操作。

dwWait = WaitForMultipleObjects(

INSTANCES, //事件对象的数量

hEvents, //数组的事件对象

FALSE, //不等待所有

INFINITE); //无限期地等待

// dwWait显示哪个管道完成了操作。

i = dwWait - WAIT_OBJECT_0; //确定哪个管道

if(i < 0 || i >(INSTANCES - 1))

MyErrExit("index out of range");

//如果操作正在等待,请获取结果。

if(Pipe [i] .fPendingIO)

{

fSuccess = GetOverlappedResult(

Pipe[i].hPipeInst, //处理管道

&Pipe[i].oOverlap, // OVERLAPPED结构

&cbBytes, //传送字节

FALSE); // 不要等

开关(管[i] .dwState)

{

//待连接操作

case CONNECTING_STATE:

如果(!fSuccess)

MyErrExit("ConnectNamedPipe");

Pipe[i].dwState = READING_STATE;

break;

//待处理的读操作

case READING_STATE:

if (! fSuccess || cbBytes == 0)

{

DisconnectAndReconnect(i);

continue;

}

Pipe[i].dwState = WRITING_STATE;

break;

//等待写入操作

case WRITING_STATE:

if (! fSuccess || cbBytes != Pipe[i].cbToWrite)

{

DisconnectAndReconnect(i);

continue;

}

Pipe[i].dwState = READING_STATE;

break;

默认:

MyErrExit("invalid pipe state");

}

}

//管道状态确定下一步要执行哪个操作。

开关(管道[i] .dwState)

{

// READING_STATE:

//管道实例连接到客户端

//并准备好从客户端读取请求。

case READING_STATE:

fSuccess = ReadFile(

Pipe[i].hPipeInst,

Pipe[i].chBuf,

BUFSIZE,

& cbBytes,

&Pipe[i].oOverlap);

//读取操作成功完成。

if (fSuccess && cbBytes != 0)

{

Pipe[i].fPendingIO = FALSE;

Pipe[i].dwState = WRITING_STATE;

continue;

}

//读取操作仍在等待

dwErr = GetLastError();

if (! fSuccess && (dwErr == ERROR_IO_PENDING))

{

Pipe[i].fPendingIO = TRUE;

continue;

}

//发生错误与客户端断开连接。

DisconnectAndReconnect(i);

break;

// WRITING_STATE:

//请求已成功从客户端读取。

//获取答复数据并将其写入客户端。

case WRITING_STATE:

GetDataToWriteToClient(&Pipe[i]);

fSuccess = WriteFile(

Pipe[i].hPipeInst,

Pipe[i].chBuf,

Pipe[i].cbToWrite,

& cbBytes,

&Pipe[i].oOverlap);

//写操作成功完成。

if (fSuccess && cbBytes == Pipe[i].cbToWrite)

{

Pipe[i].fPendingIO = FALSE;

Pipe[i].dwState = READING_STATE;

continue;

}

//写操作仍在等待。

dwErr = GetLastError();

if (! fSuccess && (dwErr == ERROR_IO_PENDING))

{

Pipe[i].fPendingIO = TRUE;

continue;

}

//发生错误与客户端断开连接。

DisconnectAndReconnect(i);

break;

默认:

MyErrExit("invalid pipe state");

}

}

return 0;

}

// DisconnectAndReconnect(DWORD)

//发生错误或客户端时调用此函数

//关闭管道的句柄。然后断开这个客户端

//调用ConnectNamedPipe等待另一个客户端连接。

VOID DisconnectAndReconnect(DWORD i)

{

//断开管道实例。

if(!DisconnectNamedPipe(Pipe [i] .hPipeInst))

MyErrExit("DisconnectNamedPipe");

//调用子程序连接到新的客户端。

Pipe[i].fPendingIO = ConnectToNewClient(

Pipe[i].hPipeInst,

&Pipe[i].oOverlap);

Pipe[i].dwState = Pipe[i].fPendingIO ?

CONNECTING_STATE : //仍然连接

READING_STATE; //准备阅读

}

// ConnectToNewClient(HANDLE,LPOVERLAPPED)

//调用此函数来启动重叠的连接操作。

//如果某个操作正在挂起,则返回TRUE,否则返回FALSE

//连接已经完成。

BOOL ConnectToNewClient(HANDLE hPipe, LPOVERLAPPED lpo)

{

BOOL fConnected, fPendingIO = FALSE;

//启动此管道实例的重叠连接。

fConnected = ConnectNamedPipe(hPipe, lpo);

//重叠的ConnectNamedPipe应返回FALSE。

if(fConnected)

MyErrExit("ConnectNamedPipe");

switch(GetLastError())

{

//重叠连接正在进行中。

case ERROR_IO_PENDING:

fPendingIO = TRUE;

break;

// Client已经连接,所以发出一个事件。

case ERROR_PIPE_CONNECTED:

如果(SetEvent的(LPO- {} hEvent 989796004))

break;

//如果在连接操作期间发生错误...

默认:

MyErrExit("ConnectNamedPipe");

}

return fPendingIO;

}