寫讀書筆記的目的是加強理解,記錄自己學習的過程
在microsoft Windows 應用程式中,線程是我們最好的工具,可以用來對工作進行劃分。
為了不讓線程閑下來,我們需要讓各個線程就他們正在執行的操作相互連信。有一種非常好的機制來進行這類通訊。 它就叫IO完成連接埠,它可以協助我們建立高效能而且伸縮性好的應用程式。通過使用IO完成連接埠,我們可以讓線程在讀取裝置和寫入裝置的時候不必等待裝置的響應,從而顯著的提高輸送量。
作為一名Windows開發人員,必須完全理解IO完成連接埠的工作方式,IO完成連接埠可以與裝置有關係,也可以與裝置無關,它是一種有無數種用途的絕佳的線程間通訊機制。
1 開啟和關閉裝置
Windows 的優勢之一是它支援的裝置數量,就是我們與之能夠通訊的任何東西.
用來開啟各種裝置的函數如下:
一般的裝置控制代碼使用closehandle來關閉,通訊端使用colosesocket.
GetFileType來查詢裝置的類型,函數返回下列值.
細看CrateFile函數
HANDLE
CreateFile(
LPCTSTRlpFileName, 檔案名稱
DWORDdwDesiredAccess, 訪問模式
DWORDdwShareMode, 共用模式
LPSECURITY_ATTRIBUTESlpSecurityAttributes, 安全屬性
DWORDdwCreationDisposition, 建立標誌
DWORDdwFlagsAndAttributes, 讀取標誌位和檔案屬性 非常大的檔案這個標誌位 FILE_FLAG_NO_BUFFERING
HANDLEhTemplateFile); 模板檔案
函數成功返迴文件控制代碼,失敗時返回invalid_handle_value
Handle hFile = CreateFile(...);
if( invalid_handle_value == hFile)
{
開啟檔案控制代碼失敗.
}
else
{
開啟成功.
}
2 使用檔案裝置
GetfileSize(handle,pDWORD) 獲得檔案大小,當檔案大於4GB時,Pword參數表示高位,傳回值表上地位.
GetCompressedFileSize 返回物理檔案大小
設定檔案指標的位置
BOOL
SetFilePointerEx(
HANDLEhFile, 檔案控制代碼對象
LARGE_INTEGERliDistanceToMove, 需要移動的位元組數
PLARGE_INTEGERlpNewFilePointer, 更新後的檔案指標位置
DWORDdwMoveMethod); FILE_BEGIN ,FILE_CURRENT , FILE_END
設定檔案尾部
強制檔案變小或者變大DWORD dSize,dsize1;
LARGE_INTEGER LagreSize;
LagreSize.QuadPart = 1024;
SetFilePointerEx(hFile, LagreSize, NULL, FILE_BEGIN);
SetEndOfFile(hFile);
CloseHandle(hFile);
3 執行同步裝置IO
最常用的對裝置資料的讀取的函數是Readfile,WriteFile函數
將資料重新整理至裝置
FlushFileBuffers(HANDLE hfile);//把所以緩衝區的資料重新整理到指定檔案控制代碼
同步IO比如,Createfile函數會阻塞其他調用線程。必須等待函數返回才可以繼續執行.
在WindowsVista中
CancelSynchronousIo(HANDLE hThread);
取消指定線程沒有完成的同步IO請求
hThread 線程控制代碼必須要有 THREAD_TERMINATE許可權
返回非0表示函數成功,返回0表示失敗,更多資訊見MSDN
4 非同步裝置IO基礎
把非同步IO請求排入佇列是開發高效能和可伸縮高的應用程式的本質所在。
為了以非同步方式訪問裝置,在CreatFile時 dwFlagsAndAttributes標示必須指定FILE_FLAG_OVERLAPPED 非同步重疊IO。 表示我們想要用非同步方式來訪問裝置。
OVERLAPPED 結構 The
OVERLAPPED structure contains information used in asynchronous (or overlapped) input and output (I/O). 用於非同步通訊的一個結構體資訊.
typedef struct _OVERLAPPED { ULONG_PTR Internal; 由驅動程式設定 這個成員用來儲存已處理的IO請求的錯誤碼 ULONG_PTR InternalHigh;由驅動程式設定 表示IO完成時已經傳輸的位元組數
union { struct { DWORD Offset; 如果裝置是檔案時需要設定讀取位移量,低32位 如果不是檔案時這2個參數都設定為0 DWORD OffsetHigh;
如果裝置是檔案時需要設定讀取位移量,高32位 大於4GB檔案需要設定
}; PVOID Pointer; 系統保留 }; HANDLE hEvent; 事件對象,當Io完成時,它變成有訊號狀態
} OVERLAPPED, *LPOVERLAPPED;
HasOverlappedIoCompleted宏使用的就是第一個參數, 函數返回true表示IO請求完成,false表示IO請求沒有完成.
非同步裝置IO的注意事項1. 非同步IO的請求不是先進先出,也就是說裝置驅動根據效率自己選擇執行哪一個IO請求。1ReadFile 2WriteFile 先調用讀檔案,在調用寫檔案,他們都是非同步請求的,那麼有可能先寫檔案,在讀檔案。2. 如何正確的來檢查錯誤 如果請求的IO非同步方式執行,那麼Readfile或者WriteFile返回 false,那麼需要調用GetLastError 來獲得資訊,如果返回的是 ERROR_IO_PENDING 表示IO請求已經排入佇列,晚些時候完成.3. 非同步IO完成前,一定不能銷毀,或者移動資料緩衝和overlapped結構體資訊.
必須為每個IO請求初始化和分配一個不同的overlapped結構體資訊void ReadData(handle hfile){ overlapped ol = {0};byte b[100];
reatefile(hfile, b, 100, NULL, &ol);}看上去這段代碼沒有問題,但其實有很大隱患。非同步IO請求排入佇列以後,函數返回了,局部變數釋放。處理IO請求的驅動程式並不知道,它會去修改那個地址,有可能引起崩潰。如果裝置驅動以同步方式處理可能沒有問題,但是以非同步方式處理時,就修改了記憶體內容,並且很難發現。取消列隊中的裝置IO請求1 CancelIO(handle hFile)2 關閉裝置控制代碼來取消所有添加到列表的請求,不管是哪個線程添加的3 線程終止時,系統自動取消該線程添加的所有IO請求。4 給定檔案控制代碼的一個指定的IO請求關閉時,調用CancleioEx(HANDLE hfile, LPOVERLAPPED lOverlapped);如果loverlapped不為NULL那麼取消一個特定的IO請求,如果為NULL取消所有控制代碼為HFile的IO請求。5 接收IO請求完成通知Windows 提供了4種不同的方法來通知IO請求已經完成的通知. 下列表格表示從容易到困難的IO完成連接埠無疑是最好的。1 觸發裝置核心對象 沒有多大用處,只能處理一個IO請求代碼如下://非同步方式開啟一個檔案控制代碼HANDLE hFile = CreateFile(L"sn_new.txt",GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED,NULL);if (INVALID_HANDLE_VALUE == hFile){printf("file open faild\n");return 0;}OVERLAPPED overlapped = {0};overlapped.Offset = 100; //表示從100的位置開始讀取 大於4GB檔案才需要設定 OffsetHighbyte buffer[100]= {0};bool bReadDone = ReadFile(hFile, buffer, 100, NULL, &overlapped);if (!bReadDone && ERROR_IO_PENDING == GetLastError()){//表示IO請求已經排入佇列,稍候完成bool bStop = false;while(!bStop){//表示裝置核心觸發了DWORD dRes = WaitForSingleObject(hFile, 1000);//等待1秒switch(dRes){case WAIT_OBJECT_0://表示IO處理完畢,buffer有資料了bStop = true;break;case WAIT_TIMEOUT://表示IO沒有處理完畢break;default: WAIT_FAILED:break;}Sleep(2000);//休息2秒}}else{//表示讀取檔案錯誤,更多資訊見GetLastError();}
2 觸發事件核心對象 比裝置核心對象好用,可以處理多個IO請求下面的代碼是核心對象處理IO請求的局限性
//非同步方式開啟一個檔案控制代碼HANDLE hFile = CreateFile(L"sn_new.txt",GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED,NULL);OVERLAPPED Readoverlapped = {0};Readoverlapped.Offset = 100; //表示從100的位置開始讀取 大於4GB檔案才需要設定 OffsetHighbyte buffer[100]= {0};bool bReadDone = ReadFile(hFile, buffer, 100, NULL, &Readoverlapped);OVERLAPPED WriteOverlapped = {0};WriteOverlapped.Offset = 0; //表示從檔案開始的位置寫byte writeBuffer[100]={"dfffffffffffff"};bool bWirteDone = WriteFile(hFile, writeBuffer,100,NULL,&WriteOverlapped);WaitForSingleObject(hFile, INFINITE);// 如果等待檔案控制代碼有資訊了,那麼是讀完成了,還是寫完成了,無法區別
所以事件核心對象出場了.
OVERLAPPED 結構的最後一個成員變數 hevent 表示一個事件,利用CreateEvent函數來建立一個事件當IO請求處理完畢時,裝置驅動程式把這個事件設定為有訊號狀態
那麼我們應該只等待事件對象,不在應該等待裝置核心對象了。
關閉觸發檔案對象有一個函數
SetFileCompletionNotificationModes(HANDLE hfile, UCHAR flags = FILE_SKIP_SET_EVENT_ON_HANDLE);
表示這個檔案核心對象有IO完成時,也不會觸發
下面的代碼介紹了怎麼使用事件對象:
//下面使用事件核心對象來處理IO完成請求//非同步方式開啟一個檔案控制代碼HANDLE hFile = CreateFile(L"sn_new.txt",GENERIC_READ | GENERIC_WRITE,FILE_SHARE_READ,NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED,NULL);OVERLAPPED Readoverlapped = {0};Readoverlapped.Offset = 100; //表示從100的位置開始讀取 大於4GB檔案才需要設定 OffsetHighReadoverlapped.hEvent = CreateEvent(NULL, FALSE, FALSE,NULL);//預設安全屬性,自動事件,未觸發,匿名byte buffer[100]= {0};bool bReadDone = ReadFile(hFile, buffer, 100, NULL, &Readoverlapped);OVERLAPPED WriteOverlapped = {0};WriteOverlapped.Offset = 0; //表示從檔案開始的位置寫WriteOverlapped.hEvent = CreateEvent(NULL, FALSE, FALSE,NULL);byte writeBuffer[100]={"dfffffffffffff"};//預設安全屬性,自動事件,未觸發,匿名bool bWirteDone = WriteFile(hFile, writeBuffer,100,NULL,&WriteOverlapped);HANDLE hEventArray[2];hEventArray[0] = Readoverlapped.hEvent;hEventArray[1] = WriteOverlapped.hEvent;DWORD drs = WaitForMultipleObjects(_countof(hEventArray), hEventArray, FALSE, 2000);switch(drs){case WAIT_OBJECT_0://讀完成break;case WAIT_OBJECT_0 + 1://寫完成break;case WAIT_TIMEOUT://讀和寫都沒有完成 去泡妞吧break;case WAIT_FAILED://控制代碼無效break;}
檢查重疊IO的結果
BOOL GetOverlappedResult( HANDLE hFile, LPOVERLAPPEDlpOverlapped, LPDWORDlpNumberOfBytesTransferred,
BOOLbWait);參數1 表示裝置控制代碼參數2 表示 重疊IO結構體參數3 表示 返回的位元組數參數4 表示 是否一直等待直到返回結果為止 3 可提醒IO一般不推薦使用,請注意它的一些基礎設施。允許我們手動添加一項到ACPQueueUserAPC(
__in PAPCFUNC pfnAPC, 函數指標
__in HANDLE hThread, 線程ID
__in ULONG_PTR dwData 回呼函數的值
);如果線程建立的時候沒有運行,那麼如果有ACP請求,會先處理APC,然後才開始運行線程,並且一起處理所以的APC即使線程掛起,也會把ACP請求添加到ACP隊列中去,如果使用了 可提醒函數等待的話,那麼每次函數執行時都會把所以的ACP列隊中的請求處理完,才返回,傳回值為WAIT_IO_COMPLETION,如果下次又有新的請求,那麼繼續要調用可提醒函數,才能處理列隊中的請求。IO完成連接埠另外發一篇文章單獨詳細說明