[ターン]オーバーラップI / Oの概念と使用

 

[戻る]オーバーラップI / Oの概念と使用。再
投稿元:http://blog.csdn.NET/xiaoxiaoyu85/article/details/65343211 
オーバーラップI / Oの概念と使用法
ReadFileとWriteFileを呼び出すときに、最後のパラメーターlpOverlappedがNULLに設定されている場合、スレッドはここでブロックされ、指定されたデータの読み取りと書き込みが行われるまで戻りません。このように、大きなファイルを読み書きするとき、ReadFileとWriteFileが返されるのを待つのに多くの時間が無駄になります。ReadFileとWriteFileがデータの読み取りとパイプへの書き込みを行っている場合、それらが長時間ブロックする可能性があり、その結果、プログラムのパフォーマンスが低下します。

この問題を解決するために、WindowsはオーバーラップI / Oの概念を導入しました。これは、複数のスレッドで複数のI / Oを同時に処理できます。実際、複数のスレッドを開くことで複数のI / Oを処理することもできますが、システム内のI / Oの内部処理は、パフォーマンスの点で大幅に最適化されています。これは、Windowsで非同期I / Oを実装するための最も一般的な方法です。

Windowsは、ディスクファイル、通信ポート、名前付きパイプ、ソケットなど、ほぼすべての種類のファイルにこのツールを提供します。一般に、ReadFileとWriteFileを使用すると、オーバーラップしたI / Oを適切に実行できます。

重なりモデルの中核は重なりデータ構造です。ファイルをオーバーラップモードで使用する場合は、次のようにFILE_FLAG_OVERLAPPEDフラグを指定してファイルを開く必要があります。

HANDLE hFile = CreateFile(lpFileName、GENERIC_READ | GENERIC_WRITE、FILE_SHARE_READ | FILE_SHARE_WRITE、NULL、OPEN_EXISTING、FILE_FLAG_OVERLAPPED、NULL);

このフラグが指定されていない場合、このファイル(ハンドル)のオーバーラップI / Oは使用できません。このフラグが設定されている場合、ReadFileおよびWriteFileを呼び出してこのファイル(ハンドル)を操作するときは、最後のパラメーターにOVERLAPPED構造体を指定する必要があります。

// WINBASE.H

typedef struct _OVERLAPPED{

DWORD Internal;

DWORD InternalHigh;

DWORD Offset;

DWORD OffsetHigh;

HANDLE hEvent; //关键的一个参数

}OVERLAPPED, *LPOVERLAPPED;

最初の2つの32ビット構造ワードInternalとInternalHighは、システムによって内部的に使用されます。次の2つの32ビット構造ワードOffsetとOffsetHighにより、読み取りまたは書き込みの場所である64ビットオフセットを設定できます。ファイル。

I / Oは非同期で発生するため、操作が順番に完了したかどうかを判断することはできません。したがって、現在地の概念はありません。ファイル操作の場合、オフセットは常に指定されます。データストリーム(COMポートやソケットなど)では、正確なオフセットを見つける方法がないため、これらの場合、システムはオフセットを無視します。これらの4つのフィールドは、アプリケーションで直接処理または使用しないでください。OVERLAPPED構造体の最後のパラメーターは、オプションのイベントハンドルです。このパラメーターを使用してイベント通知を設定してI / Oを完了する方法については後で説明します。ここで、ハンドルがNULLであると想定します。

OVERLAPPEDパラメーターを設定すると、ReadFile / WriteFileの呼び出しがすぐに返されます。この時点で、他の操作(いわゆる非同期)を実行でき、システムはReadFile / WriteFileに関連するI / O操作を自動的に完了します。 。複数のReadFile / WriteFile呼び出しを同時に発行することもできます(いわゆるオーバーラップ)。システムがI / O操作を完了すると、OVERLAPPED.hEventと見なされます。WaitForSingleObject/ WaitForMultipleObjectsを呼び出すことにより、I / O完了通知を待つことができます。通知シグナルを受信した後、GetOverlappedResultを呼び出してIの結果を照会できます。 / O操作。、および関連する処理。OVERLAPPED構造は、オーバーラップしたI / O要求の初期化とその後の完了の間に通信または通信メカニズムを提供することがわかります。

Win32オーバーラップI / Oメカニズムに基づいて、Winsock 2のリリース以降、オーバーラップI / OはWSARecv / WSASendなどの新しいWinsock機能に統合されました。このようにして、オーバーラップI / Oモデルは、Winsock2がインストールされているすべてのWindowsプラットフォームに適用できます。1つ以上のWinsockI / Oリクエストを一度に投稿できます。送信されたリクエストの場合、リクエストが完了すると、アプリケーションはそれらにサービスを提供できます(I / Oデータの処理)。

同様に、ソケットでオーバーラップI / Oモデルを使用してネットワークデータ通信を処理する場合は、最初にWSA_FLAG_OVERLAPPEDフラグを使用してソケットを作成する必要があります。次のように:

SOCKET s = WSASocket(AF_INET, SOCK_STEAM, 0, NULL, 0, WSA_FLAG_OVERLAPPED);

ソケットを作成するときに、WSASocket関数の代わりにソケット関数を使用すると、デフォルトでWSA_FLAG_OVERLAPPEDフラグが設定されます。ソケットを正常に作成してローカルインターフェイスにバインドした後、次のWinsock 2関数を呼び出し、同時にWSAOVERLAPPED構造パラメーター(#defineWSAOVERLAPPED OVERLAPPED // WINSOCK2.H)を指定することで、このソケットで重複するI / O操作を開始できます。

n WSASend

n WSASendTo

n WSARecv

n WSARecvFrom

n WSAIoctl

n AcceptEx

n TransmitFile

これらの関数がWSAOVERLAPPED構造体で呼び出された場合、ソケットがロックモードに設定されているかどうかに関係なく、関数はすぐに戻ります。これらは、WSAOVERLAPPED構造に依存して、I / O要求操作の結果を返します。

ブロッキング、select、WSAAsyncSelect、およびWSAEventSelectモデルと比較して、WinsockのオーバーラップI / O(オーバーラップI / O)モデルにより、アプリケーションはより優れたシステムパフォーマンスを実現できます。これら4つのモデルとは異なるため、重複モデルを使用するアプリケーションは、データを直接使用するようにバッファトランシーバシステムに通知します。つまり、アプリケーションがデータを受信するために10KBのバッファーを配信し、データがソケットに到達した場合、データは配信されたバッファーに直接コピーされます。これら4つのモデルでは、データが到着し、シングルソケットの受信バッファにコピーされます。このとき、アプリケーションは、読み取ることができるバイト数をシステムから通知されます。アプリケーションが受信関数を呼び出した後、データはシングルソケットバッファーからアプリケーションバッファーにコピーされます。これにより、I / Oバッファからアプリケーションバッファへのコピーが1つ減ります。これが違いです。

WindowsNTとWindows2000では、オーバーラップI / Oモデルにより、アプリケーションはソケット接続をオーバーラップして処理することもできます。具体的な方法は、リスニングソケットでAcceptEx関数を呼び出すことです。AcceptExは、Mswsock.hヘッダーファイルとMswsock.libライブラリファイルにある特別なWinsock1.1拡張関数です。この関数の本来の設計目的は、WindowsNTおよびWindows2000オペレーティングシステムでWin32オーバーラップI / Oメカニズムを使用することです。ただし、実際には、Winsock2のオーバーラップI / Oにも適用されます。AcceptExの定義は次のとおりです。

// MSWSOCK.H

AcceptEx(

IN SOCKET sListenSocket,

IN SOCKET sAcceptSocket,

IN PVOID lpOutputBuffer,

IN DWORD dwReceiveDataLength,

IN DWORD dwLocalAddressLength,

IN DWORD dwRemoteAddressLength,

OUT LPDWORD lpdwBytesReceived,

IN LPOVERLAPPED lpOverlapped);

パラメータ1のsListenSocketパラメータは、リスニングソケットを指定します。

パラメータ2sAcceptSocketパラメータは、着信接続要求の「受け入れ」を担当する別のソケットを指定します。AcceptEx関数とaccept関数の違いは、関数に自動的に作成させるのではなく、受け入れられたソケットを提供する必要があることです。これはまさにソケットを提供する必要があるためです。そのため、事前にソケットまたはWSASocket関数を呼び出してソケットを作成し、sAcceptSocketパラメーターを介してAcceptExに渡すことができるようにする必要があります。

パラメーター3lpOutputBufferパラメーターは、サーバーのローカルアドレス、クライアントのリモートアドレス、および新しい接続で送信される最初のデータブロックの3種類のデータの受信を担当するため、特別なバッファーを指定します。

パラメーター4dwReceiveDataLengthパラメーターは、バイトを1つの単位として使用し、データ受信用にlpOutputBufferバッファーに予約されるスペースの量を指定します。このパラメータが0に設定されている場合、接続を受け入れるプロセスで、データは再度受信されません。

パラメーター5のdwLocalAddressLengthとパラメーター6のdwRemoteAddressLengthもバイト単位であり、lpOutputBufferバッファーに予約されるスペースの量を指定し、ソケットが受け入れられたときにローカルおよびリモートのアドレス情報を保存するために使用されます。現在採用されているトランスポートプロトコルで許可されている最大アドレス長と比較して、ここで指定されているバッファサイズは少なくとも16バイト長くなければならないことに注意してください。たとえば、TCP / IPプロトコルが使用されていると仮定すると、ここでのサイズは「SOCKADDR_IN構造体の長さ+16バイト」に設定する必要があります。

パラメーター7lpdwBytesReceivedパラメーターは、受信した実際のデータ量をバイト単位で返すために使用されます。このパラメータは、操作が同期的に完了した場合にのみ設定されます。AcceptEx関数がERROR_IO_PENDINGを返す場合、このパラメーターは設定されません。実際に読み取られたバイト数を知るには、完了イベント通知メカニズムを使用する必要があります。

最後のパラメーターはlpOverlappedで、これはOVERLAPPED構造に対応し、AcceptExが非同期で機能できるようにします。前に述べたように、現時点では完了ルーチンパラメータがないため、この関数はオーバーラップしたI / Oアプリケーションでイベントオブジェクト通知メカニズム(hEventフィールド)を使用するだけで済みます。

二。オーバーラップしたI / O操作完了の結果を取得します

非同期I / O要求が一時停止されると、最終的にI / O操作が完了したかどうかを知る必要があります。オーバーラップI / O要求が最終的に完了した後、アプリケーションはオーバーラップI / O操作の結果を取得する責任があります。読み取りの場合、入力バッファーはI / Oが完了するまで有効ではありません(IRPバッファー管理を参照)。書くためには、書くことが成功したかどうかを知る必要があります。これを行うにはいくつかの方法があります。最も直接的な方法は(WSA)GetOverlappedResultを呼び出すことです。関数のプロトタイプは、次のとおりです。

WINBASEAPI BOOL WINAPI

GetOverlappedResult(

HANDLE hFile,

LPOVERLAPPED lpOverlapped,

LPDWORD lpNumberOfBytesTransferred,

BOOL bWait);

WINSOCK_API_LINKAGE BOOL WSAAPI

WSAGetOverlappedResult(

SOCKET s,

LPWSAOVERLAPPED lpOverlapped,

LPDWORD lpcbTransfer,

BOOL fWait,

LPDWORD lpdwFlags);

lパラメータ1はファイル/ソケットハンドルです。

lパラメーター2は、パラメーター1に関連付けられた(WSA)OVERLAPPED構造体であり、CreateFile、WSASocket、またはAcceptExを呼び出すときに指定されます。

lパラメータ3は、重複する送信または受信操作によって実際に送信されたバイト数を受信する役割を担うバイトカウントポインタを指します。

lパラメーター4は、コマンドが待機しているかどうかを判別するためのフラグです。Waitパラメーターは、重複する操作が完了するのを関数が待機するかどうかを決定するために使用されます。WaitがTRUEに設定されている場合、関数は操作が完了するまで戻りません。FALSEに設定されていて、操作がまだ未完了の状態である場合、(WSA)GetOverlappedResult関数はFALSE値を返します。

(WSA)GetOverlappedResult関数が正常に呼び出された場合、戻り値はTRUEです。これは、オーバーラップされたI / O操作が正常に完了し、パラメーター3BytesTransferedパラメーターが指す値が更新されたことを意味します。戻り値がFALSEの場合は、次のいずれかの理由が考えられます。

■重複したI / O操作はまだ「保留中」の状態です。

■オーバーラップ操作は完了しましたが、エラーが含まれています。

■WSAGetOverlappedResult関数に提供された1つ以上のパラメータにエラーがあるため、重複した操作の完了ステータスを判断できません。

失敗後、BytesTransferedパラメーターが指す値は更新されないため、アプリケーションは(WSA)GetLastError関数を呼び出して、対応するフォールトトレランス処理を使用して呼び出しが失敗した原因を確認する必要があります。エラーコードが

ERROR / WSA_IO_INCOMPLETE(重複したI / Oイベントがシグナル状態ではありません)または

ERROR / WSA_IO_PENDING(重複したI / O操作が進行中です)、I / Oがまだ進行中であることを示します。もちろん、これは実際のエラーではありません。他のエラーコードは、実際のエラーを示しています。

以下では、一般的に使用される2つの重複するI / O完了通知方法について説明します。

1.イベント通知を使用する

(WSA)GetOverlappedResultの使用は簡単で、オーバーラップI / Oの概念に適合します。結局のところ、I / Oを待ちたい場合は、通常のI / Oコマンドを使用する方がよい場合があります。ほとんどのプログラムでは、I / Oが完了したかどうかを繰り返しチェックすることは最適ではありません。解決策の1つは、(WSA)OVERLAPPED構造体のhEventフィールドを使用して、アプリケーションにイベントオブジェクトハンドルをファイル/ソケットに関連付けさせることです。

OVERLAPPEDパラメーターをReadFile / WriteFileまたはWSARecv / WSASendに指定した後、(WSA)OVERLAPPEDの最後のパラメーターにカスタムイベントオブジェクト((WSA)CreateEventによって作成された)を提供できます。

I / Oが完了すると、システムは(WSA)OVERLAPPED構造に対応するイベントオブジェクトのシグナリング状態を「unsignaled」から「signaled」に変更します。以前にイベントオブジェクトを(WSA)OVERLAPPED構造に割り当てたので、WaitForSingleObject / WaitForMultipleObjects関数またはWSAWaitForMultipleEvents関数を呼び出すだけで、(一部の)オーバーラップI / Oがいつ完了するかを判別できます。WaitForSingleObject / WaitForMultipleObjectsまたはWSAWaitForMultipleEvents関数によって返されるインデックスを通じて、このオーバーラップI / O完了イベントが発生したHANDLE(ファイルまたはソケット)を知ることができます。

次に、(WSA)GetOverlappedResult関数を呼び出し、イベントが発生したHANDLE(ファイルまたはソケット)をパラメーター1に渡し、このHANDLEに対応する(WSA)OVERLAPPED構造体をパラメーター2に渡して、重複した呼び出しが成功したかどうかを判別します。または失敗しました。FALSE値を返す場合、重なり操作は完了していますが、エラーが含まれています。または、WSAGetOverlappedResult関数に提供された1つ以上のパラメーターにエラーがあるため、重複操作の完了状況を判断できません。失敗後、BytesTransferedパラメーターが指す値は更新されません。アプリケーションは(WSA)GetLastError関数を呼び出して、呼び出しの失敗の原因を調査する必要があります。

(WSA)GetOverlappedResult関数がTRUEを返す場合、非同期I / O関数が以前に呼び出されたときに設定されたバッファー(ReadFile / WriteFile.lpBuffer、WSARecv / WSASend.lpBuffers)およびBytesTransferedに従って、正確な操作を受け取ることができます。ポインタオフセットポジショニングの使用データが出力されています。

イベントオブジェクトを使用して同期通知を完了する方法は、(WSA)GetOverlappedResultを繰り返し呼び出すことによってプロセッサ時間を浪費するソリューションよりもはるかに効率的です。

2.完了ルーチンを使用します

ファイルのオーバーラップI / O操作の場合、I / O操作の終了を待つ別の方法は、ReadFileExとWriteFileExを使用することです。これらのコマンドは、I / Oのオーバーラップにのみ使用されます。完了ルーチン(カスタム関数)ポインターが最後のパラメーターlpCompletionRoutineに渡されると、この関数はI / O操作の終了時に処理のために呼び出されます。

完了ルーチンポインタLPOVERLAPPED_COMPLETION_ROUTINEは、次のように定義されています。

// WINBASE.H

typedef VOID (WINAPI *LPOVERLAPPED_COMPLETION_ROUTINE)(

DWORD dwErrorCode,

DWORD dwNumberOfBytesTransfered,

LPOVERLAPPED lpOverlapped );

これに対応して、Winsock 2では、WSARecv / WSASendの最後のパラメーターlpCompletionROUTINEは、完了ルーチンを指すオプションのポインターです。このパラメーター(カスタム関数アドレス)を指定すると、重なり要求が完了した後に完了ルーチンが呼び出されます。

Winsock2の完了ルーチンポインタLWPSAOVERLAPPED_COMPLETION_ROUTINEの定義は少し異なります。

// WINSOCK2.H

typedef void (CALLBACK * LPWSAOVERLAPPED_COMPLETION_ROUTINE)(

DWORD dwError,

DWORD cbTransferred,

LPWSAOVERLAPPED lpOverlapped,

DWORD dwFlags );

最初の3つのパラメーターはLPOVERLAPPED_COMPLETION_ROUTINEと同じであり、パラメーター4は通常使用されず、0に設定されます。重複したI / O要求を完了ルーチンで完了した後、パラメーターには次の情報が含まれます。

パラメーターdwErrorは、オーバーラップした操作の完了状況を示します(lpOverlappedで指定)。

パラメーター2BytesTransferredパラメーターは、重なり操作で実際に転送されるバイト数を指定します。

パラメーター3lpOverlappedパラメーターは、この完了ルーチンを呼び出す非同期入出力操作関数(ReadFileEx / WriteFileExまたはWSARecv / WSASend)の(WSA)OVERLAPPED構造パラメーターを指定します。

オーバーラップしたI / O要求が完了ルーチンとともに送信された場合、(WSA)OVERLAPPED構造体のイベントフィールドhEventは使用されません。つまり、イベントオブジェクトを重複するI / O要求に関連付けることはできません。オーバーラップI / O要求を発行するために完了ルーチンポインターパラメーターを含む非同期I / O関数を使用した後、オーバーラップI / O操作が完了すると、呼び出しスレッドとして、ポイントされたカスタム関数に通知できる必要があります。完了ルーチンポインタによる実行を開始し、データ処理サービスを提供します。このように、呼び出しスレッドは「警告待機状態」にする必要があり、I / O操作の完了後、完了ルーチンが自動的に呼び出されて処理されます。WSAWaitForMultipleEvents関数を使用して、警告可能な待機状態にスレッドを置くことができます。このコストは、WSAWaitForMultipleEvents関数で使用できるイベントオブジェクトを作成する必要があることです。アプリケーションが重複する要求を処理するために完了ルーチンのみを使用すると仮定すると、処理するイベントオブジェクトはありません。回避策として、アプリケーションはWin32のSleepEx関数を使用して、スレッドを警告待機状態にすることができます。もちろん、疑似イベントオブジェクトを作成して、それを何にも関連付けないようにすることもできます。呼び出し元のスレッドが頻繁にビジーであり、警告待機状態にない場合、完了ルーチンはまったく呼び出されません。

前述のように、WSAWaitForMultipleEventsは通常、WSAOVERLAPPED構造に関連付けられたイベントオブジェクトを待機します。この関数を使用して、スレッドを警告待機状態に設計し、完了したオーバーラップI / O要求の完了ルーチンを呼び出すこともできます(fAlertableパラメーターがTRUEに設定されている場合)。完了ルーチンへのポインターを含む非同期I / O関数を使用して重複I / O要求を送信した後、WSAWaitForMultipleEventsの戻り値はWAIT_IO_COMPLETION(1つ以上のI / O完了ルーチンが実行のためにキューに入れられます)であり、イベントのイベントではありません。配列オブジェクトインデックス。マクロWAIT_IO_COMPLETIONの注釈から、実行する必要のある完了ルーチンがあることを意味します。SleepEx関数の動作は、イベントオブジェクトを必要としないことを除いて、実際にはWSAWaitForMultipleEventsの動作と似ています。SleepEx関数の定義は次のとおりです。

WINBASEAPI DWORD WINAPI

SleepEx(

DWORD dwMilliseconds,

BOOL bAlertable );

その中で、dwMillisecondsパラメーターは、SleepEx関数の待機時間をミリ秒単位で定義します。dwMillisecondsがINFINITEに設定されている場合、SleepExは際限なく待機します。bAlertableパラメーターは、完了ルーチンの実行方法を指定します。FALSEに設定されている場合、完了ルーチンへのポインターを含む非同期I / O関数を使用して、重複したI / O要求を送信します。I/ O完了ルーチンはnot Executeであり、SleepEx関数はdwMillisecondsで指定された時間を超えない限り戻りません。TRUEに設定されている場合、完了ルーチンが実行され、SleepEx関数はWAIT_IO_COMPLETIONを返します。

重複したI / Oを処理するために完了ルーチンを使用するWinsockプログラムを作成する手順は次のとおりです。

1)新しいリスニングソケットを作成し、指定されたポートでクライアントの接続要求を監視します。

2)クライアント接続要求を受け入れ、クライアントとの通信を担当するセッションソケットを返します。

3)セッションソケットのWSAOVERLAPPED構造を関連付けます。

4)WSAOVERLAPPEDをパラメーターとして指定し、完了ルーチンを提供することにより、非同期WSARecv要求をソケットにポストします。

5)fAlertableパラメーターをTRUEに設定することを前提として、WSAWaitForMultipleEventsを呼び出し、重複したI / O要求が完了するのを待ちます。重複したリクエストが完了すると、完了ルーチンが自動的に実行され、WSAWaitForMultipleEventsはWAIT_IO_COMPLETIONを返します。完了ルーチン内で、別の重複するWSARecv要求を完了ルーチンと一緒に投稿できます。

6)WSAWaitForMultipleEventsがWAIT_IO_COMPLETIONを返すかどうかを確認します。

7)手順5)と6)を繰り返します。

通常、acceptを呼び出して接続を処理すると、AcceptEvent疑似イベントが作成されます。クライアント接続がある場合は、手動でSetEvent(AcceptEvent)を実行する必要があります。AcceptExを呼び出して重複する接続を処理する場合は、通常、ListenSocketのListenOverlapped構造を作成して割り当てます。疑似イベント。クライアントが接続すると、システムは自動的にそれを信じます。これらの疑似イベントの機能は、完了ルーチンポインターを含む非同期I / O操作(WSARecvなど)が完了すると、fAlertableが設定されたWSAWaitForMultipleEventsがWAIT_IO_COMPLETIONを返し、完了ルーチンポインターが指す完了ルーチンを呼び出してデータ。

オーバーラップI / Oモデルの欠点は、I / O要求ごとにスレッドを開くことです。数千の要求が同時に発生すると、システム処理スレッドのコンテキスト切り替えに非常に時間がかかります。したがって、これは、スレッドプールを使用してこの問題を解決する、より高度な完了ポートモデルIOCPにつながります。

おすすめ

転載: blog.csdn.net/zhengjian1996/article/details/112941467