[英]IO Completion Port Initial Read and Bi-Directional Data
我有以下簡化的IO完成端口服務器C ++代碼:
int main(..)
{
startCompletionPortThreadProc();
// Await client connection
sockaddr_in clientAddress;
int clientAddressSize = sizeof( clientAddress );
SOCKET acceptSocket = WSAAccept( serverSocket, (SOCKADDR*)&clientAddress, &clientAddressSize, NULL, NULL);
// Connected
CreateIoCompletionPort( (HANDLE)acceptSocket, completionPort, 0, 0 );
// Issue initial read
read( acceptSocket );
}
DWORD WINAPI completionPortThreadProc( LPVOID param )
{
DWORD bytesTransferred = 0;
ULONG_PTR completionKey = NULL;
LPPER_IO_DATA perIoData = NULL;
while( GetQueuedCompletionStatus( completionPort, &bytesTransferred, &completionKey, (LPOVERLAPPED*)&perIoData, INFINITE ) )
{
if( WaitForSingleObject( exitEvent, 0 ) == WAIT_OBJECT_0 )
{
break;
}
if( !perIoData )
continue;
if( bytesTransferred == 0 )
{
//TODO
}
switch( perIoData->operation )
{
case OPERATION_READ:
{
// Bytes have been received
if( bytesTransferred < perIoData->WSABuf.len )
{
// Terminate string
perIoData->WSABuf.buf[bytesTransferred] = '\0';
perIoData->WSABuf.buf[bytesTransferred+1] = '\0';
}
// Add data to message build
message += std::tstring( (TCHAR*)perIoData->WSABuf.buf );
// Perform next read
perIoData->WSABuf.len = sizeof( perIoData->inOutBuffer );
perIoData->flags = 0;
if( WSARecv( perIoData->socket, &( perIoData->WSABuf ), 1, &bytesTransferred, &( perIoData->flags ), &( perIoData->overlapped ), NULL ) == 0 )
{
// Part message
continue;
}
if( WSAGetLastError() == WSA_IO_PENDING )
{
// End of message
//TODO: Process message here
continue;
}
}
}
break;
case OPERATION_WRITE:
{
perIoData->bytesSent += bytesTransferred;
if( perIoData->bytesSent < perIoData->bytesToSend )
{
perIoData->WSABuf.buf = (char*)&( perIoData->inOutBuffer[perIoData->bytesSent] );
perIoData->WSABuf.len = ( perIoData->bytesToSend - perIoData->bytesSent);
}
else
{
perIoData->WSABuf.buf = (char*)perIoData->inOutBuffer;
perIoData->WSABuf.len = _tcslen( perIoData->inOutBuffer ) * sizeof( TCHAR );
perIoData->bytesSent = 0;
perIoData->bytesToSend = perIoData->WSABuf.len;
}
if( perIoData->bytesToSend )
{
if( WSASend( perIoData->socket, &( perIoData->WSABuf ), 1, &bytesTransferred, 0, &( perIoData->overlapped ), NULL ) == 0 )
continue;
if( WSAGetLastError() == WSA_IO_PENDING )
continue;
}
}
break;
}
}
return 0;
}
bool SocketServer::read( SOCKET socket, HANDLE completionPort )
{
PER_IO_DATA* perIoData = new PER_IO_DATA;
ZeroMemory( perIoData, sizeof( PER_IO_DATA ) );
perIoData->socket = socket;
perIoData->operation = OPERATION_READ;
perIoData->WSABuf.buf = (char*)perIoData->inOutBuffer;
perIoData->WSABuf.len = sizeof( perIoData->inOutBuffer );
perIoData->overlapped.hEvent = WSACreateEvent();
DWORD bytesReceived = 0;
if( WSARecv( perIoData->socket, &( perIoData->WSABuf ), 1, &bytesReceived, &( perIoData->flags ), &( perIoData->overlapped ), NULL ) == SOCKET_ERROR )
{
int gle = WSAGetLastError();
if( WSAGetLastError() != WSA_IO_PENDING )
{
delete perIoData;
return false;
}
}
return true;
}
bool SocketServer::write( SOCKET socket, std::tstring& data )
{
PER_IO_DATA* perIoData = new PER_IO_DATA;
ZeroMemory( perIoData, sizeof( PER_IO_DATA ) );
perIoData->socket = socket;
perIoData->operation = OPERATION_WRITE;
perIoData->WSABuf.buf = (char*)data.c_str();
perIoData->WSABuf.len = _tcslen( data.c_str() ) * sizeof( TCHAR );
perIoData->bytesToSend = perIoData->WSABuf.len;
perIoData->overlapped.hEvent = WSACreateEvent();
DWORD bytesSent = 0;
if( WSASend( perIoData->socket, &( perIoData->WSABuf ), 1, &bytesSent, 0, &( perIoData->overlapped ), NULL ) == SOCKET_ERROR )
{
if( WSAGetLastError() != WSA_IO_PENDING )
{
delete perIoData;
return false;
}
}
return true;
}
1)我的第一個問題是初始閱讀。
在客戶端連接(接受)上,我發出一個讀取。 由於客戶端尚未發送任何數據,WSAGetLastError()是WSA_IO_PENDING並且read方法返回。
當客戶端然后發送數據時,線程仍然停留在GetQueuedCompletionStatus調用中(因為我假設我需要另一個WSARecv調用?)。
我應該繼續循環讀取方法,直到數據到達? 這似乎不合邏輯,我想通過發布初始讀取GetQueuedCompletionStatus將在數據到達時完成。
2)我需要在沒有確認的情況下雙向讀寫數據。 因此我也用IOCP線程創建了一個客戶端。 實際上是否可以使用完成端口執行此操作,或者必須在寫入之后執行讀操作?
對於感覺基本問題感到抱歉,但在瀏覽互聯網和構建IOCP示例后,我仍然無法回答問題。
提前謝謝了。
在客戶端連接(接受)上,我發出一個讀取。 由於客戶端尚未發送任何數據,WSAGetLastError()是WSA_IO_PENDING並且read方法返回。
這是正常的行為。
當客戶端然后發送數據時,線程仍然停留在GetQueuedCompletionStatus調用中(因為我假設我需要另一個WSARecv調用?)。
不,你不需要另一個電話。 如果它被卡住,那么您沒有正確地將讀取與I / O完成端口關聯。
我應該繼續循環讀取方法,直到數據到達?
不需要。您需要一次調用WSARecv()
進行初始讀取。 WSA_IO_PENDING
錯誤表示讀取正在等待數據,並在數據實際到達時發出I / O完成端口的信號。 在該信號實際到達之前,請勿調用WSARecv()
(或任何其他讀取函數)。 然后,您可以再次調用WSARecv()
以等待更多數據。 重復直到插座斷開連接。
我想通過發布初始讀取GetQueuedCompletionStatus將在數據到達時完成。
這正是應該發生的事情。
2)我需要在沒有確認的情況下雙向讀寫數據。 因此我也用IOCP線程創建了一個客戶端。 實際上是否可以使用完成端口執行此操作
是。 閱讀和寫作是分開的操作,它們不是相互依賴的。
寫入之后是否必須讀取?
如果您的協議不需要它,請不要。
現在,說到這一點,您的代碼存在一些問題。
在次要說明中, WSAAccept()
是同步的,您應該考慮使用AcceptEx()
以便它可以使用相同的I / O完成端口來報告新連接。
但更重要的是,當掛起的I / O操作失敗時, GetQueuedCompletionStatus()
返回FALSE,返回的LPOVERLAPPED
指針將為非NULL, GetLastError()
將報告I / O操作失敗的原因。 但是,如果GetQueuedCompletionStatus()
本身失敗,則返回的LPOVERLAPPED
指針將為NULL, GetLastError()
將報告GetQueuedCompletionStatus()
失敗的原因。 文檔中清楚地解釋了這種差異,但是你的while
循環沒有考慮到它。 請改用do..while
循環並根據LPOVERLAPPED
指針執行操作:
DWORD WINAPI completionPortThreadProc( LPVOID param )
{
DWORD bytesTransferred = 0;
ULONG_PTR completionKey = NULL;
LPPER_IO_DATA perIoData = NULL;
do
{
if( GetQueuedCompletionStatus( completionPort, &bytesTransferred, &completionKey, (LPOVERLAPPED*)&perIoData, INFINITE ) )
{
// I/O success, handle perIoData based on completionKey as needed...
}
else if( perIoData )
{
// I/O failed, handle perIoData based on completionKey as needed...
}
else
{
// GetQueuedCompletionStatus() failure...
break;
}
}
while( WaitForSingleObject( exitEvent, 0 ) == WAIT_TIMEOUT );
return 0;
}
另外,不要使用事件對象來指示completionPortThreadProc()
何時應該退出,而是考慮使用PostQueuedCompletionionStatus()
來將終止completionKey發布到I / O完成端口,然后你的循環可以查找該值:
DWORD WINAPI completionPortThreadProc( LPVOID param )
{
DWORD bytesTransferred = 0;
ULONG_PTR completionKey = NULL;
LPPER_IO_DATA perIoData = NULL;
do
{
if( GetQueuedCompletionStatus( completionPort, &bytesTransferred, &completionKey, (LPOVERLAPPED*)&perIoData, INFINITE ) )
{
if( completionKey == MyTerminateKey )
break;
if( completionKey == MySocketIOKey )
{
// I/O success, handle perIoData as needed...
}
}
else if( perIoData )
{
// I/O failed, handle perIoData based on completionKey as needed...
}
else
{
// GetQueuedCompletionStatus() failure...
break;
}
}
while( true );
return 0;
}
CreateIoCompletionPort( (HANDLE)acceptSocket, completionPort, MySocketIOKey, 0 );
PostQueuedCompletionStatus( completionPort, 0, MyTerminateKey, NULL );
聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.