Ich habe folgenden vereinfachen IO Completion-Port-Server C++ Code:IO Completion-Port Initial lesen und bidirektionaler Daten
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) Die erste Frage, die ich habe mit der anfänglichen Lese ist.
Auf Client-Verbindung (accept), gebe ich einen Lesevorgang aus. Da der Client noch keine Daten gesendet hat, ist WSAGetLastError() WSA_IO_PENDING und die Lesemethode gibt zurück.
Wenn der Client dann Daten sendet, bleibt der Thread im GetQueuedCompletionStatus-Aufruf stecken (wie ich annehme, brauche ich einen anderen WSARecv-Aufruf?).
Soll ich die Lesemethode weiterschleifen, bis Daten eintreffen? Das scheint nicht logisch zu sein, dachte ich, indem ich den ersten Read-Befehl GetQueuedCompletionStatus abgab, wenn Daten eintrafen.
2) Ich muss bidirektionale Daten ohne Bestätigungen lesen und schreiben. Deshalb habe ich auch einen Client mit dem IOCP-Thread erstellt. Ist es tatsächlich möglich, dies mit Completion-Ports zu tun, oder muss ein Read gefolgt von einem Write?
Entschuldigung für grundlegende Fragen, aber nachdem ich im Internet geforscht und IOCP-Beispiele erstellt habe, kann ich die Fragen immer noch nicht beantworten.
Vielen Dank im Voraus.
Hallo Remy, Vielen Dank für Ihre lange Erklärung, ist es sehr zu schätzen, ich werde hier verrückt. Ich werde Ihre Kommentare bearbeiten und Ihnen Bericht erstatten! Ich habe einen guten Teil des Codes von anderen Beispielen genommen, von denen ich annahm, dass sie funktionierten. – CAM79
Hallo Remy, ok, die neue Schleife war der Schlüssel und überprüft GetLastError in 'if (perIoData)'. I/O war mit WSA_OPERATION_ABORTED fehlgeschlagen, weshalb es nie abgeschlossen wurde. Ich hatte einen Annahmethread, der das Lesen ausgab und dann endete. Ich hätte gedacht, es hätte noch funktioniert, aber offensichtlich ist mein Design falsch. Vielen Dank für Ihre Hilfe, ich werde Ihre Gedanken in meinem neuen Design verwenden. Hoffentlich werden Ihre Kommentare auch anderen helfen, da viele Beispiele Code wie meinen zu verwenden scheinen. – CAM79
Wenn ein Thread beendet wird, werden alle begonnenen E/A-Vorgänge, die noch ausstehen, automatisch abgebrochen. Sie sollten 'WSAAccept()' in einer Schleife innerhalb eines Threads aufrufen, der mindestens die Lebensdauer des Listening-Sockets aushält (oder die Client-Akzeptanz zum IO-Fertigstellungs-Port verschieben und das ursprüngliche Akzeptieren von einem solchen Thread ausgeben), damit ich/Os wird nicht abgebrochen. –