讨论:1.激发的文件handles
2.激发的event对象
3.异步过程调用
4.I/O completion ports(重要)适用于高负载服务器
----Win32文件操作函数
win32中有上那个基本的函数用来执行I/O,他们是:
CreateFile(),ReadFile(),WriteFile();
没有哪个函数用来关闭文件,只要调用CloseHandle()即可。
CreateFile()可以用来打开各式各样的资源:包括文件,串口和并行口,Named pipes,Console.
它的原型如下:
HANDLE CreateFile(
LPCTSTR lpFileName, // file name
DWORD dwDesiredAccess, // access mode存取模式(读或写)
DWORD dwShareMode, // share mode
LPSECURITY_ATTRIBUTES lpSecurityAttributes, // SD
DWORD dwCreationDisposition, // how to create 如何产生
DWORD dwFlagsAndAttributes, // file attributes 文件属性(使用overlapped I/O的关键)
HANDLE hTemplateFile // handle to template file一个临时文件,将拥有全部的属性拷贝
);
C runtime library不能使用overlapped I/O.Overlapped I/O的基本型式是以ReadFile()和WriteFile()完成的。这两个函数的原型如下:
BOOL ReadFile(
HANDLE hFile, // handle to file欲读取的文件
LPVOID lpBuffer, // data buffer 接收数据的缓冲区
DWORD nNumberOfBytesToRead, // number of bytes to read 欲读取的字节数
LPDWORD lpNumberOfBytesRead, // number of bytes read 实际读取的字节个数的地址
LPOVERLAPPED lpOverlapped // overlapped buffer指针,指向overlapped info
);
BOOL WriteFile(
HANDLE hFile, // handle to file欲写的文件
LPCVOID lpBuffer, // data buffer存储数据的缓冲区
DWORD nNumberOfBytesToWrite, // number of bytes to write欲写入的字节个数
LPDWORD lpNumberOfBytesWritten, // number of bytes written实际写入的字节个数
LPOVERLAPPED lpOverlapped // overlapped buffer 指针,指向overlapped info
);
这两个函数很像C runtime 函数的fread 和fwrite(),差别在于最后一个参数lpOverlapped.如果第六个参数设为
FILE_FLAG_OVERLAPPED,则必须在lpOverlapped参数中提供一个指针,指向一个OVERLAPPED结构。
---OVERLAPPED结构
它执行两个重要的功能,第一,它像一把钥匙,用以识别一个目前正在进行的overlapped操作。第二,它在你和系统之间提供了一个共享区域,参数可以在该区域中双向传递。
typedef struct _OVERLAPPED {
ULONG_PTR Internal;
ULONG_PTR InternalHigh;
DWORD Offset;
DWORD OffsetHigh;
HANDLE hEvent;
} OVERLAPPED;
由于OVERLAPPED结构的生命周期超越ReadFile()和WriteFile()函数。所以把这个结构放在局部变量不是安全的。应该放在heap上。
---被激发的File Handles
最简单的overlapped I/O类型,就是使用它自己文件handle作为同步机制。
首先,用FILE_FLAG_OVERLAPPED告诉Win32你不要使用默认的同步I/O。然后设立一个OVERLAPPED结构,其中内含“I/O请求”的所有必要的参数,并以此来识别这个“ I/O请求”,直到它完成为止。接下来,调用ReadFile()并以OVERLAPPED结构的地址作为最后一个参数。这时候,Win32会在后台处理你的请求。你的程序可以干其他的事情了。
如果需要等待overlapped I/O的执行结果,作为WaitForMultipleObjects()的一部分,在handle数组中加上这个文件的handle.文件handle是一个核心对象。一旦操作完毕即被激发。当完成操作之后,
调用GetOverlappedResult()确定结果如何。
BOOL GetOverlappedResult(
HANDLE hFile, // handle to file, pipe, or device
LPOVERLAPPED lpOverlapped, // overlapped structure 一个指针,指向OVERLAPPED结构
LPDWORD lpNumberOfBytesTransferred, // bytes transferred表示真正被传送的字节
BOOL bWait // wait option是否表示要等待操作完成
);
文件读取示例:
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
//
// Constants
//
#define READ_SIZE 512
//
// Function prototypes
//
void CheckOsVersion();
int main()
{
BOOL rc;
HANDLE hFile;
DWORD numread;
OVERLAPPED overlap;
char buf[READ_SIZE];
char szPath[MAX_PATH];
CheckOsVersion();
GetWindowsDirectory(szPath, sizeof(szPath));
strcat(szPath, "\\WINHLP32.EXE");
// Open the file for overlapped reads
hFile = CreateFile( szPath,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL
);
if (hFile == INVALID_HANDLE_VALUE)
{
printf("Could not open %s\n", szPath);
return -1;
}
// Initialize the OVERLAPPED structure
memset(&overlap, 0, sizeof(overlap));
overlap.Offset = 1500;
// Request the data
rc = ReadFile(
hFile,
buf,
READ_SIZE,
&numread,
&overlap
);
printf("Issued read request\n");
// Was the operation queued?
if (rc)
{
// The data was read successfully
printf("Request was returned immediately\n");
}
else
{
if (GetLastError() == ERROR_IO_PENDING)
{
// We could do something else for awhile here...
printf("Request queued, waiting...\n");
WaitForSingleObject(hFile, INFINITE);//这个调用其实是多余,因为GetOverlappedResult就可以等待 overlapped操作的完
成。
printf("Request completed.\n");
rc = GetOverlappedResult(
hFile,
&overlap,
&numread,
FALSE
);
printf("Result was %d\n", rc);
}
else
{
// We should check for memory and quota
// errors here and retry. See the samples
// IoByEvnt and IoByAPC.
// Something went wrong
printf("Error reading file\n");
}
}
CloseHandle(hFile);
return EXIT_SUCCESS;
}
//
// Make sure we are running under an operating
// system that supports overlapped I/O to files.
//
void CheckOsVersion()
{
OSVERSIONINFO ver;
BOOL bResult;
ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
bResult = GetVersionEx((LPOSVERSIONINFO) &ver);
if ( (!bResult) ||
(ver.dwPlatformId != VER_PLATFORM_WIN32_NT) )
{
fprintf(stderr, "IoByFile must be run under Windows NT.\n");
exit(EXIT_FAILURE);
}
}
Q:Overlapped I/O总是异步的执行吗?
虽然你要求overlapped操作,但它并不一定就是overlapped。如果数据已经被放进cache中,或如果操作系统认为它可以很快速的取得那份数据,那么文件操作就会在ReadFile()返回之前完成。而ReadFile()将传回TRUE.这种情况下,文件handle处于激发状态,而对文件的操作可以视作就像overlapped一样。
如果你要求一个文件操作为overlapped,而操作系统吧这个“操作请求”放到队列中等待执行,那么ReadFile()和WriteFile()都会传回FALSE表示失败。你必须调GetLastError()并确定它传回ERROR_IO_PENDING,那意味着“overlapped I/O请求”被放进队列之中等待执行。GetLastError()也可能传回其他的值,例如ERROR_HANDLE_EOF,那么就代表一个错误。
OVERLAPPED结构体有能力处理64位的偏移值,因此面对巨大的文件也不怕。为了等待文件操作的完成,把文件的handle交给WaitForSingleObject().一旦overlapped操作完成,该文件的handle就会成为激发状态。
---被激发的Event对象
用文件的handle作为激发机制,就没办法说出到底哪一个overlapped操作完成了。Win32有一个很好的解决办法。
OVERLAPPED结构中的最后一个栏位,是一个event handle.如果你使用文件handle作为激发对象,那么此栏设置为NULL.当使用event作为激发对象时,则赋值为event对象。系统核心会在overlapped操作
完成的时候,自动将此event对象给激发起来。由于每一个overlapped操作都有它自己独一无二的OVERLAPPED结构。所以每一个结构都有它独一无二的一个event对象,用来代表该操作。
Q:如何为overlapped I/O产生一个event对象?
你所使用的event对象必须是手动重置而非自动重置。如果使用自动重置,就有可能产生race condition,因为系统核心有可能在你有机会等待该event对象之前,先激发它。event对象的激发状态不能够保存的。于是这个event状态将遗失,而你的Wait...()函数永远不返回。但是一个手动重置的event,一旦激发,就一直处于激发状态。
使用event对象搭配overlapped I/O,可以对同一个文件发出多个读取操作和多个写入操作,每一个操作有自己的event对象;然后在调用WaitForMultipleObjects()来等待其中之一完成。
使用Event和overlapped I/O实现文件读取示例:
/* Demonstrates how to use event objects instead of
* file handles to signal multiple outstanding
* overlapped operation on a file.
*/
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "MtVerify.h"
//
// Constants
//
#define MAX_REQUESTS 5
#define READ_SIZE 512
#define MAX_TRY_COUNT 5
//
// Function prototypes
//
int QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount);
void CheckOsVersion();
//
// Global variables
//
// Need to keep the events in their own array
// so we can wait on them.
HANDLE ghEvents[MAX_REQUESTS];
// Keep track of each individual I/O operation
OVERLAPPED gOverlapped[MAX_REQUESTS];
// Handle to the file of interest.
HANDLE ghFile;
// Need a place to put all this data
char gBuffers[MAX_REQUESTS][READ_SIZE];
int main()
{
int i;
BOOL rc;
char szPath[MAX_PATH];
CheckOsVersion();
GetWindowsDirectory(szPath, sizeof(szPath));
strcat(szPath, "\\WINHLP32.EXE");
// Open the file for overlapped reads
ghFile = CreateFile( szPath,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL
);
if (ghFile == INVALID_HANDLE_VALUE)
{
printf("Could not open %s\n", szPath);
return -1;
}
for (i=0; i<MAX_REQUESTS; i++)
{
// Read some bytes every few K
QueueRequest(i, i*16384, READ_SIZE);
}
printf("QUEUED!!\n");
// Wait for all the operations to complete.
MTVERIFY( WaitForMultipleObjects(
MAX_REQUESTS, ghEvents, TRUE, INFINITE
) != WAIT_FAILED );
// Describe what just happened.
for (i=0; i<MAX_REQUESTS; i++)
{
DWORD dwNumread;
rc = GetOverlappedResult(
ghFile,
&gOverlapped[i],
&dwNumread,
FALSE
);
printf("Read #%d returned %d. %d bytes were read.\n",
i, rc, dwNumread);
CloseHandle(gOverlapped[i].hEvent);
}
CloseHandle(ghFile);
return EXIT_SUCCESS;
}
/*
* Call ReadFile to start an overlapped request.
* Make sure we handle errors that are recoverable.
* Properly set up the event object for this operation.
*/
int QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount)
{
int i;
BOOL rc;
DWORD dwNumread;
DWORD err;
MTVERIFY(
ghEvents[nIndex] = CreateEvent(
NULL, // No security
TRUE, // Manual reset - extremely important!
FALSE, // Initially set Event to non-signaled state
NULL // No name
)
);
gOverlapped[nIndex].hEvent = ghEvents[nIndex];
gOverlapped[nIndex].Offset = dwLocation;
for (i=0; i<MAX_TRY_COUNT; i++)
{
rc = ReadFile(
ghFile,
gBuffers[nIndex],
dwAmount,
&dwNumread,
&gOverlapped[nIndex]
);
// Handle success
if (rc)
{
printf("Read #%d completed immediately.\n", nIndex);
return TRUE;
}
err = GetLastError();
// Handle the error that isn't an error. rc is zero here.
if (err == ERROR_IO_PENDING)
{
// asynchronous i/o is still in progress
printf("Read #%d queued for overlapped I/O.\n", nIndex);
return TRUE;
}
// Handle recoverable error
if ( err == ERROR_INVALID_USER_BUFFER ||
err == ERROR_NOT_ENOUGH_QUOTA ||
err == ERROR_NOT_ENOUGH_MEMORY )
{
Sleep(50); // Wait around and try later
continue;
}
// Give up on fatal error.
break;
}
printf("ReadFile failed.\n");
return -1;
}
//
// Make sure we are running under an operating
// system that supports overlapped I/O to files.
//
void CheckOsVersion()
{
OSVERSIONINFO ver;
BOOL bResult;
ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
bResult = GetVersionEx((LPOSVERSIONINFO) &ver);
if ( (!bResult) ||
(ver.dwPlatformId != VER_PLATFORM_WIN32_NT) )
{
fprintf(stderr, "IoByEvnt must be run under Windows NT.\n");
exit(EXIT_FAILURE);
}
}
对于以Remote Access Service连接到一个服务器,现在可以记录这些操作过程,并做成overlapped I/O,然后在住消息循环中使用MsgWaitForMultipleObjects()以便在取得数据后有所反应。
-----------异步过程调用(Asynchronous Procedure Calls,APCs)
使用overlapped I/O配合event对象,会产生两个基础性问题。
第一,使用WaitForMultipleObjects(),你只能够等待最多MAXIMUM_WAIT_OBJECTS个对象。在Win32 SDK中,最大值为64。如果要等待64个以上的对象,就会出现问题。
第二,你必须不断根据“哪一个handle被激发”而计算如果反应。你必须分配表格和WaitForMultipleObjects()的handles数组结合起来。以上两个问题可以用异步过程调用来解决。
只要使用“Ex”版本的ReadFile()和WriteFile(),就可以调用这个机制。这两个函数允许你指定一个额外的参数,是一个callback函数地址。当一个overlapped I/O完成时,系统应该调用该callback函数。这个callback函数称为I/O completion routine.
Q:一个I/O completion routine何时被调用?
你的线程必须在所谓"alertable"状态下才行。如果线程不处于“alertable”状态,那么对I/O completion routine的调用会暂时被保存下来。因此,当一个线程终于进入“alertable”状态时,可能已经有一大堆储备的APCs等待被处理。
如果线程因为下面五个函数处于等待状态,其“alertable”标记被设为TRUE,则该线程就是处于"alertable"状态:
SleepEx(),WaitForSingleObjectEx();WaitForMultipleObjectsEx(),MsgWaitForMultipleObjectsEx();
SignalObjectAndWait();
只有在程序处于“alertable”状态下,APCs才会被调用。
I/O completion routine的形式:
VOID CALLBACK FileIOCompletionRoutine(
DWORD dwErrorCode, // completion code 0表示操作完成,ERROR_HANDLE_EOF表示操作已经到文件尾 端。
DWORD dwNumberOfBytesTransfered, // number of bytes transferred真正被传输的字节数
LPOVERLAPPED lpOverlapped // I/O information buffer
);
Q:如何把一个用户自定义的数据传递给I/O completion routine?
使用APCs时,OVERLAPPED机构体重的hEvent位不需要用来放置一个event handle.hEvent栏位可以由程序员自由使用。最大的用途就是:首先配置一个结构,描述数据来自哪里,或是要对数据进行一些什么操作,然后将hEvent栏位设定为指向该结构。
overlapped I/O与APCs的应用示例:
/* Demonstrates how to use APC's (asynchronous
* procedure calls) instead of signaled objects
* to service multiple outstanding overlapped
* operations on a file.
*/
#define WIN32_LEAN_AND_MEAN
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "MtVerify.h"
//
// Constants
//
#define MAX_REQUESTS 5
#define READ_SIZE 512
#define MAX_TRY_COUNT 5
//
// Function prototypes
//
void CheckOsVersion();
int QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount);
//
// Global variables
//
// Need a single event object so we know when all I/O is finished
HANDLE ghEvent;
// Keep track of each individual I/O operation
OVERLAPPED gOverlapped[MAX_REQUESTS];
// Handle to the file of interest.
HANDLE ghFile;
// Need a place to put all this data
char gBuffers[MAX_REQUESTS][READ_SIZE];
int nCompletionCount;
/*
* I/O Completion routine gets called
* when app is alertable (in WaitForSingleObjectEx)
* and an overlapped I/O operation has completed.
*/
VOID WINAPI FileIOCompletionRoutine(
DWORD dwErrorCode, // completion code
DWORD dwNumberOfBytesTransfered, // number of bytes transferred
LPOVERLAPPED lpOverlapped // pointer to structure with I/O information
)
{
// The event handle is really the user defined data
int nIndex = (int)(lpOverlapped->hEvent);
printf("Read #%d returned %d. %d bytes were read.\n",
nIndex,
dwErrorCode,
dwNumberOfBytesTransfered);
if (++nCompletionCount == MAX_REQUESTS)
SetEvent(ghEvent); // Cause the wait to terminate
}
int main()
{
int i;
char szPath[MAX_PATH];
CheckOsVersion();
// Need to know when to stop
MTVERIFY(
ghEvent = CreateEvent(
NULL, // No security
TRUE, // Manual reset - extremely important!
FALSE, // Initially set Event to non-signaled state
NULL // No name
)
);
GetWindowsDirectory(szPath, sizeof(szPath));
strcat(szPath, "\\WINHLP32.EXE");
// Open the file for overlapped reads
ghFile = CreateFile( szPath,
GENERIC_READ,
FILE_SHARE_READ|FILE_SHARE_WRITE,
NULL,
OPEN_EXISTING,
FILE_FLAG_OVERLAPPED,
NULL
);
if (ghFile == INVALID_HANDLE_VALUE)
{
printf("Could not open %s\n", szPath);
return -1;
}
// Queue up a few requests
for (i=0; i<MAX_REQUESTS; i++)
{
// Read some bytes every few K
QueueRequest(i, i*16384, READ_SIZE);
}
printf("QUEUED!!\n");
// Wait for all the operations to complete.
for (;;)
{
DWORD rc;
rc = WaitForSingleObjectEx(ghEvent, INFINITE, TRUE );
if (rc == WAIT_OBJECT_0)
break;
MTVERIFY(rc == WAIT_IO_COMPLETION);
}
CloseHandle(ghFile);
return EXIT_SUCCESS;
}
/*
* Call ReadFileEx to start an overlapped request.
* Make sure we handle errors that are recoverable.
*/
int QueueRequest(int nIndex, DWORD dwLocation, DWORD dwAmount)
{
int i;
BOOL rc;
DWORD err;
gOverlapped[nIndex].hEvent = (HANDLE)nIndex;
gOverlapped[nIndex].Offset = dwLocation;
for (i=0; i<MAX_TRY_COUNT; i++)
{
rc = ReadFileEx(
ghFile,
gBuffers[nIndex],
dwAmount,
&gOverlapped[nIndex],
FileIOCompletionRoutine
);
// Handle success
if (rc)
{
// asynchronous i/o is still in progress
printf("Read #%d queued for overlapped I/O.\n", nIndex);
return TRUE;
}
err = GetLastError();
// Handle recoverable error
if ( err == ERROR_INVALID_USER_BUFFER ||
err == ERROR_NOT_ENOUGH_QUOTA ||
err == ERROR_NOT_ENOUGH_MEMORY )
{
Sleep(50); // Wait around and try later
continue;
}
// Give up on fatal error.
break;
}
printf("ReadFileEx failed.\n");
return -1;
}
//
// Make sure we are running under an operating
// system that supports overlapped I/O to files.
//
void CheckOsVersion()
{
OSVERSIONINFO ver;
BOOL bResult;
ver.dwOSVersionInfoSize = sizeof(OSVERSIONINFO);
bResult = GetVersionEx((LPOSVERSIONINFO) &ver);
if ( (!bResult) ||
(ver.dwPlatformId != VER_PLATFORM_WIN32_NT) )
{
fprintf(stderr, "IoByAPC must be run under Windows NT.\n");
exit(EXIT_FAILURE);
}
}
Q:如何把C++成员函数当做一个 I/O completion routine?
一个C++成员函数不能拿来做一个I/O completion routine,除非它是static函数。如果它是static函数,它就没有this指针,也就不能直接调用那些需要this 指针的成员函数。
解决的方法是存储一个指针,指向用户自定义数据,然后经由此指针调用一个C++成员函数。由于static成员函数是类的一部分,还是可以调用private成员函数。与如何把C++成员函数当做一个线程函数类似。
另外一个I/O completion ports非常重要,对产生高效率的服务器很受欢迎,这里略,详细参考相关文献资料。