у вас много неправильного кода здесь.
while(WAIT_OBJECT_0 != WaitForSingleObject(EventShutdown, 0)){
QCS = GetQueuedCompletionStatus(hIOCP, &transfered, (PULONG_PTR)&client, &overlapped, INFINITE);
это не эффективный и неправильный код для остановки WorkerThread
.сначала вы делаете избыточный вызов WaitForSingleObject
, используете избыточный EventShutdown
и включаете его в любом случае, чтобы не завершить работу.если ваш код ожидает пакета внутри GetQueuedCompletionStatus
, который вы говорите EventShutdown
- не прерывайте GetQueuedCompletionStatus
вызов - вы продолжаете бесконечное ожидание здесь.Правильный способ отключения - PostQueuedCompletionStatus(hIOCP, 0, 0, 0)
вместо вызова SetEvent(EventShutdown)
и если сработал вид потока client == 0
- он разрывает цикл.и обычно вам нужно иметь несколько WorkerThread
(не один).и множественные вызовы PostQueuedCompletionStatus(hIOCP, 0, 0, 0)
- точное количество рабочих потоков.также вам нужно синхронизировать эти вызовы с io - делайте это только после того, как все io уже завершено и новые io-пакеты не будут поставлены в очередь в iocp.поэтому «нулевые пакеты» должны быть последними в очереди на порт
if(!QCS || (QCS && !transfered)){
printf("Error %d\n", WSAGetLastError());
DeleteClient(client);
continue;
}
, если !QCS
- значение в client
не инициализировано, вы просто не можете его использовать, и вызов DeleteClient(client);
неверенусловие
когда объект (client
) используется из нескольких потоков - кто должен его удалить?что будет, если один поток удалит объект, а другой все еще его использует?правильное решение будет, если вы используете подсчет ссылок на такой объект (клиент).и на основе вашего кода - у вас есть один клиент на HIOCP?потому что вы извлекаете указатель для клиента в качестве ключа завершения для hIOCP, который является единым для всех операций ввода-вывода на сокетах, привязанных к hIOCP.все это неправильный дизайн.
вам нужно сохранить указатель на клиента в IO_Context
.и добавьте ссылку на клиента в IO_Context
и освободите клиента в деструкторе IO_Context
.
class IO_Context : public OVERLAPPED {
Client *client;
ULONG opcode;
// ...
public:
IO_Context(Client *client, ULONG opcode) : client(client), opcode(opcode) {
client->AddRef();
}
~IO_Context() {
client->Release();
}
void OnIoComplete(ULONG transfered) {
OnIoComplete(RtlNtStatusToDosError(Internal), transfered);
}
void OnIoComplete(ULONG error, ULONG transfered) {
client->OnIoComplete(opcode, error, transfered);
delete this;
}
void CheckIoError(ULONG error) {
switch(error) {
case NOERROR:
case ERROR_IO_PENDING:
break;
default:
OnIoComplete(error, 0);
}
}
};
тогда у вас есть один IO_Context
?если да, то это фатальная ошибка.IO_Context
должен быть уникальным для каждой операции ввода / вывода.
if (IO_Context* ctx = new IO_Context(client, op))
{
ctx->CheckIoError(WSAxxx(ctx) == 0 ? NOERROR : WSAGetLastError());
}
и из обработанного потока s
ULONG WINAPI WorkerThread(void * param)
{
ULONG_PTR key;
OVERLAPPED *overlapped;
ULONG transfered;
while(GetQueuedCompletionStatus(hIOCP, &transfered, &key, &overlapped, INFINITE)) {
switch (key){
case '_io_':
static_cast<IO_Context*>(overlapped)->OnIoComplete(transfered);
continue;
case 'stop':
// ...
return 0;
default: __debugbreak();
}
}
__debugbreak();
return GetLastError();
}
код типа while(!HasOverlappedIoCompleted(&overlapped)) Sleep(1);
всегда неправильно.абсолютный и всегда.никогда не пишите такой код.
ctx = (IO_Context *)overlapped;
, несмотря на то, что в вашем конкретном случае это дает правильный результат, что не очень хорошо и может быть нарушено, если вы измените определение IO_Context
.вы можете использовать CONTAINING_RECORD(overlapped, IO_Context, overlapped)
, если вы используете struct IO_Context{
OVERLAPPED overlapped; }
, но лучше использовать class IO_Context : public OVERLAPPED
и static_cast<IO_Context*>(overlapped)
, теперь о Почему IOCP выбирает эти события?Как справиться с этими «плохими» событиями в IOCP?
В IOCP ничего не выбрать .он просто сигнализирует о завершении ввода / вывода.все.какие конкретные ошибки wsa вы получили в различных сетевых операциях, абсолютно независимо от использования IOCP или любого другого механизма завершения.
при постепенном отключении - это нормально, когда код ошибки равен 0 и 0 байтов переданы в режиме recv.Вам необходимо постоянно иметь активный запрос recv после того, как соединение установлено, и если recv завершен с 0 переданными байтами, это означает, что разъединение происходит