информационная безопасность
без паники и всерьез
 подробно о проектеRambler's Top100
Страшный баг в WindowsПортрет посетителя
BugTraq.Ru
Русский BugTraq
 Анализ криптографических сетевых... 
 Модель надежности двухузлового... 
 Специальные марковские модели надежности... 
 Три миллиона электронных замков... 
 Doom на газонокосилках 
 Умер Никлаус Вирт 
главная обзор RSN блог библиотека закон бред форум dnet о проекте
bugtraq.ru / форум / programming
Имя Пароль
ФОРУМ
если вы видите этот текст, отключите в настройках форума использование JavaScript
регистрация





Легенда:
  новое сообщение
  закрытая нитка
  новое сообщение
  в закрытой нитке
  старое сообщение
  • Напоминаю, что масса вопросов по функционированию форума снимается после прочтения его описания.
  • Новичкам также крайне полезно ознакомиться с данным документом.
[Win32] "Блокировка" WriteFile IOCP уведомления 22.08.07 04:47  
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
Есть простенький сервер на пайпах на IOCP. Сервер отправляет клиенту асинхронный:
::WriteFile(m_device, m_buffer, (DWORD) _tcslen(m_buffer)+1, NULL, (OVERLAPPED_PERCLIENT*) pointerOverlapped));

Если клиент прочитает только часть из _tcslen(m_buffer)+1,
то GetQueuedCompletionStatus(hIOCP, &dwNumBytes, &CompKey, &pOverlapped, PIPE_TIMEOUT)не вернёт соответствующий риквест на серверной стороне. Запрос сервера ::WriteFile так и останется в очереди IOCP, пока все _tcslen(m_buffer)+1 не будут вычитаны клиентом.

Таким образом возникает "подвисший клиент", который находится в памяти сервера и очереди IOCP. Сервер никогда не узнает о таких подвисших пайпах, поскольку GetQueuedCompletionStatus по тайм ауту возврашает только OVERLAPPED * == NULL.

Если клиент создаёт тысячи таких "подвисших" пайпов, то сервер становится жертвой - он никогда не узнает, что клиент ничего ни делает.

Вопрос, как быть? Как определить, что "плохой клиент" не хочет читать данный, и надо прибить пайп.
Я никогда не работал с пайпами через IOCP, но, например, с... 22.08.07 09:36  
Автор: IgorR <Igor Razin> Статус: Member
<"чистая" ссылка>
Я никогда не работал с пайпами через IOCP, но, например, с сокетами такого:
> Если клиент прочитает только часть из _tcslen(m_buffer)+1,
> то GetQueuedCompletionStatus(hIOCP, &dwNumBytes,
> &CompKey, &pOverlapped, PIPE_TIMEOUT)не вернёт
> соответствующий риквест на серверной стороне.
быть не может. Да и вообще какое-то странное поведение, когда GetQueuedCompletionStatus возвращается пополучению_данных_клиентом
В принципе, вопрос был даже не в том , как построить... 23.08.07 07:56  
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
В принципе, вопрос был даже не в том , как построить коммуникации Client/Server более надёжными, а как сделать сервер менее чувствительным к ошибкам в дизайне клиента и коммуникациям.

> Я никогда не работал с пайпами через IOCP, но, например, с
> сокетами такого:
> > Если клиент прочитает только часть из
> _tcslen(m_buffer)+1,
> > то GetQueuedCompletionStatus(hIOCP, &dwNumBytes,
> > &CompKey, &pOverlapped, PIPE_TIMEOUT)не вернёт
> > соответствующий риквест на серверной стороне.
> быть не может.

Ну может такое поведение IOCP выглядит вполне логично для message-ориентированного Client(PIPE_READMODE_MESSAGE)/Server(PIPE_TYPE_MESSAGE) обмена?

>Да и вообще какое-то странное поведение, когда GetQueuedCompletionStatus возвращается по >_получению_данных_клиентом_.

Я этого не говорил. Если клиент неправильно вычитывает из PIPE (например, не все данные), то операция WriteFile на серверной стороне помечается в очереди как pending и IOCP не устанавливает такую операцию завершённой (completion).

Некоторые смутные пояснения можно найти здесь(хотя я могу ошибаться):
http://msdn2.microsoft.com/en-us/library/aa365747.aspx

“...If hFile was opened with FILE_FLAG_OVERLAPPED and lpOverlapped is not NULL, the write operation starts at the offset specified in the OVERLAPPED structure and WriteFile may return before the write operation has been completed. In this case, WriteFile returns FALSE and the GetLastError function returns ERROR_IO_PENDING... The event specified in the OVERLAPPED structure is set to the signaled state upon completion of the write operation....”

После асинхронного вызова WriteFile, который затем оказывается в состоянии ERROR_IO_PENDING, GetQueuedCompletionStatus не вернётся, вернее ждать здесь нечего до I/O completion (или ошибки на IOCP):

“... WriteFile resets the event specified by the hEvent member of the OVERLAPPED structure to a nonsignaled state when it begins the I/O operation...”

Рассмотрим нехитрый дизайн (ниже опущена обработка ошибок и прочая обвязка):
Обмен – message ориентированный. Серверный PIPE создаётся, как

HANDLE hPipe = ::CreateNamedPipe( PIPE_NAME, PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED, PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT, PIPE_UNLIMITED_INSTANCES, PIPE_BUF_SIZE, PIPE_BUF_SIZE, PIPE_TIMEOUT, NULL);

Клиент создаётся как:

hNamedPipe = CreateFile( PIPE_NAME, GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL);
<skip>
DWORD dwMode = PIPE_READMODE_MESSAGE; SetNamedPipeHandleState( hNamedPipe, &dwMode, NULL, NULL );


Пусть для обмена на сервере используется простая per client структура:

struct OVERLAPPED_PERCLIENT: public OVERLAPPED
{
public:
enum enCompleted { read, write, unknown };

private:
TCHAR m_buffer[IO_BUFSIZE];
HANDLE m_device;
enCompleted m_status;

public:
...
<skip>
...
inline enCompleted GetStatus( void ) { return m_status; }
inline HANDLE GetDevice( void ) {return (m_device); }
bool ReadFile( void ) {
m_status = OVERLAPPED_PERCLIENT::read;
return(TRUE ==::ReadFile( m_device, m_buffer, (DWORD) array_size(m_buffer), NULL, this));}

bool WriteFile( void ) {
m_status = OVERLAPPED_PERCLIENT::write;
m_buffer[array_size(m_buffer)-1] = _T('\0');
return(TRUE==::WriteFile(m_device, m_buffer, (DWORD) _tcslen(m_buffer)+1, NULL, this)); }

};


Пусть упрощённая реализация рабочих потоков:

DWORD WINAPI pipeWorkerThread(LPVOID lParam)
{
DWORD rc = 1;
HANDLE hIOCP = (HANDLE)lParam;
DWORD dwNumBytes = 0;
ULONG_PTR CompKey = 0;
OVERLAPPED* pOverlapped = NULL;


while ( true )
{
BOOL fOk = GetQueuedCompletionStatus(hIOCP, &dwNumBytes, &CompKey, &pOverlapped, PIPE_TIMEOUT);
OVERLAPPED_PERCLIENT * pClient = (OVERLAPPED_PERCLIENT *) pOverlapped;

if (fOk) {

OVERLAPPED_PERCLIENT::enCompleted completed = pClient->GetStatus();
if ( CK_REQUEST == CompKey )
{
if ( completed == OVERLAPPED_PERCLIENT::read )
{
_tprintf(_T("Read from a client-> %s\n"), pClient->GetBuffer() );
<...>
<skip>
<...>

pClient->SetReplyBuffer();
pClient->WriteFile();
}
else if ( completed == OVERLAPPED_PERCLIENT::write )
{
pClient->ReadFile();
}
else if ( .... <skip>
{
<...>
<skip>
<...>
}
continue;
}
} else {
DWORD dwError = GetLastError();
<...>
<skip ERROR_BROKEN_PIPE, WAIT_TIMEOUT, ERROR_OPERATION_ABORTED, ....>
<...>
}

}
return rc;
}


Пусть теперь, сервер, прочитав сообщение клиента, отправляет клиенту ответ:

if ( completed == OVERLAPPED_PERCLIENT::read )
{
_tprintf(_T("Read from a client-> %s\n"), pClient->GetBuffer() );
<...>
<skip>
<...>

pClient->SetReplyBuffer();
pClient->WriteFile();
}



Если клиент, например, не вычитывает весь посланный ему message, то GetQueuedCompletionStatus на сервере не вернётся. Даже более того, если клиент теперь будет слать сообщения серверу, то сервер о них не узнает, т.к. он слушает по завершению completion WriteFile:

else if ( completed == OVERLAPPED_PERCLIENT::write )
{
pClient->ReadFile();
}


Можно немного улучшить ситуацию, если на сервере при посылке сообщения анализировать:

if ( ! pClient->WriteFile() ) {
DWORD dwError = GetLastError();
if ( dwError == ERROR_INVALID_USER_BUFFER|dwError == ERROR_NOT_ENOUGH_MEMORY)
{
...
} else if (dwError == ERROR_IO_PENDING ) {
...
}
else {
}

}

Самое простое, что можно сделать, так это сразу же прибить клиента с dwError == ERROR_IO_PENDING. Но это уж больно прямолинейно, поскольку ошибка может быть связана и с объективными причинами на клиенте или в коммуникациях. Что со всем этим лучше делать – не знаю.
Не знаю, по-моему использование IOCP для пайпов вообще... 23.08.07 13:40  
Автор: IgorR <Igor Razin> Статус: Member
<"чистая" ссылка>
> Ну может такое поведение IOCP выглядит вполне логично для
> message-ориентированного
> Client(PIPE_READMODE_MESSAGE)/Server(PIPE_TYPE_MESSAGE)
> обмена?
Не знаю, по-моему использование IOCP для пайпов вообще выглядит не совсем логичным :)

> Я этого не говорил. Если клиент неправильно вычитывает из
> PIPE (например, не все данные), то операция WriteFile на
> серверной стороне помечается в очереди как pending и IOCP
> не устанавливает такую операцию завершённой (completion).
Говорил, и сейчас повторяешь ;) За пайпы ничего не могу сказать, но с сокетами завершенка приходит сразу послеуходаданных, независимо от клиента (он и отвалиться уже может).

> Даже более того, если клиент теперь будет слать
> сообщения серверу, то сервер о них не узнает, т.к. он
> слушает по завершению completion WriteFile:
А если ReadFile параллельно запостить?
Да вроде обычное дело. Пайп комуникации, например,... 24.08.07 06:37  
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
> Не знаю, по-моему использование IOCP для пайпов вообще
> выглядит не совсем логичным :)
Да вроде обычное дело. Пайп комуникации, например, поддерживают MSSQL, MySQL.

> > Даже более того, если клиент теперь будет слать
> > сообщения серверу, то сервер о них не узнает, т.к. он
> > слушает по завершению completion WriteFile:
> А если ReadFile параллельно запостить?

Нельзя, поскольку это сломает синхронизацию последовательности риквест/ответ прикладного протокола. Вернее нет гарантии, что этого не будет. При последовательностиасинхронныхвызовов ReadFile(...); WriteFile(...) драйвет может вначале выполнить WriteFile(...) а затем ReadFile(...). Это потому, что в IO очередиасинхронныезапросы не обрабатываются FIFO. Поэтому выполнив WriteFile(...), надо ждать completion IO. А только затем делать ReadFile(...).

Если всё же после неудачного асинхронного WriteFile в данной задаче делать ReadFile(...), то может так? :

f ( ! pClient->WriteFile() ) {
DWORD dwError = GetLastError();
if ( dwError == ERROR_INVALID_USER_BUFFER|dwError == ERROR_NOT_ENOUGH_MEMORY)
{
delete pClient;
::InterlockedDecrement(&g_nClients);
::SetEvent( g_evtCloseClient );
_tprintf(_T("Closing the client pipe...(dwError %d).\n"), dwError);
} else if (dwError == ERROR_IO_PENDING ) {
_tprintf(_T("Process a failed completed I/O request (ERROR_IO_PENDING, dwError %d).\n"), dwError);
if (! CancelIo( pClient->GetDevice() ))
{
_tprintf(_T("Failed canceling pending I/O. Close the client pipe\n"));
delete pClient;
::InterlockedDecrement(&g_nClients);
::SetEvent( g_evtCloseClient );
}
else {
_tprintf(_T("Cancel the client pending I/O.\n"));
pClient->ReadFile();
}

} else { _tprintf(_T("Process a failed completed I/O request (dwError %d).\n"), dwError);
}
}

Здесь pClient->ReadFile() делается, если отменены все pending IO для данного пайп. Иначе -удаляем клиента.
Ну теорию я немного знаю ;) Я просто предлагал для... 24.08.07 11:50  
Автор: IgorR <Igor Razin> Статус: Member
<"чистая" ссылка>
> Это потому, что в IO очередиасинхронныезапросы не обрабатываются FIFO.
Ну теорию я немного знаю ;) Я просто предлагал для эксперимента это попробовать.
Хорошо, допустим пайпы работают именно так - завершенка не приходит до полного получения данных клиентом. Что будет, если получить на клиенте не все данные, и закрыть пайп (убить процесс)? Я надеюсь что на сервере вернется ошибка на GetQueuedCompletionStatus с dwNumBytes == 0. Если так, то не вижу проблемы.
Кстати, почему именно пайпы а не сокеты?
В принципе проблема в том, что получив error_io_pending мы... 25.08.07 05:03  
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
>Что будет,
> если получить на клиенте не все данные, и закрыть пайп
> (убить процесс)? Я надеюсь что на сервере вернется ошибка
> на GetQueuedCompletionStatus с dwNumBytes == 0. Если так,
> то не вижу проблемы.

В принципе проблема в том, что получив ERROR_IO_PENDING мы не делаем различия между плохим и хорошим клиентом. Для обоих мы закрываем пайп на серверной стороне. Если от хорошего клиента (который не завершил чтение по уважительным причинам) появляется ошибка ERROR_IO_PENDING, то надо как-то попытаться подождать ещё чуток, пока не будет получена уведомление о completion. Пока не знаю, как это сделать просто. Но ты прав - самое простое и безопасное для сервера - это закрыть на сервере пайп клиента подчистив затем PER_OVERLAPPED_PERCLIENT данные.

> Я надеюсь что на сервере вернется ошибка
> на GetQueuedCompletionStatus с dwNumBytes == 0. Если так,
> то не вижу проблемы.

Это не так. GetQueuedCompletionStatus не вернётся в этом случае. И это как раз та проблема, из-за которой вся эта нитка постингов. Это потому, что GetQueuedCompletionStatus поджигается только по: или IO copmletion, или IOCP error, или WAIT_TIMEOUT.
Поэтому есть только один шанс - незавершённое клиентом чтение необходимо ловить сразу же по FALSE == Server->WriteFile() и затем по ERROR_IO_PENDING == GetLastError()

> Кстати, почему именно пайпы а не сокеты?

Не знаю... Число клиентов заведомо невелико. Всё только в локальной сети. И не будет ли легче в Windows domain обеспечить аутинтификацию клиента используя пайп? С сокетами - не придётся ли наворачивать поверх? Могу ошибаться.
В общем потестил. Как я и ожидал, всё работает так как надо... 27.08.07 12:15  
Автор: IgorR <Igor Razin> Статус: Member
<"чистая" ссылка>
В общем потестил. Как я и ожидал, всё работает так как надо и как должно работать. Твоих граблей вроде не наблюдается.

Сервер:
==================================================================
HANDLE hPipe = CreateNamedPipe( T("\\\\.\\PIPE\\IOCP_TEST"),
PIPE_ACCESS_DUPLEX | FILE_FLAG_OVERLAPPED,
PIPE_TYPE_MESSAGE | PIPE_READMODE_MESSAGE | PIPE_WAIT,
PIPE_UNLIMITED_INSTANCES,
1024, 1024,
NMPWAIT_WAIT_FOREVER, NULL );

CIOCP iocp;

iocp.Init();
iocp.AssociateDevice( hPipe, 123 /* key */ );

OVERLAPPED op = {0};

ConnectNamedPipe( hPipe, &op );

DWORD dwKey = 0;
DWORD dwBytes = 0;
LPOVERLAPPED pop = NULL;

// Ждем коннекта.
iocp.GetStatus( &dwKey, &dwBytes, &pop );

BYTE bData[1024] = {0};

// Отправим данные 2 раза, при этом клиент данные не читает.
// Первый раз вернется 1 - операция завершилась синхронно.
// Второй раз вернется 0 - асинхронно, т.к. клиент не вычитал первую партию.
ZeroMemory( &op, sizeof( op ) );
DWORD dwRet = WriteFile( hPipe, bData, 1024, NULL, &op ); // 1
DWORD dwError = GetLastError();

ZeroMemory( &op, sizeof( op ) );
dwRet = WriteFile( hPipe, bData, 1024, NULL, &op ); // 0
dwError = GetLastError();

// Завершенка на первую посылку будет сразу.
iocp.GetStatus( &dwKey, &dwBytes, &pop );

// Ждем пока клиент прочтет 1-ю партию.
// Если сейчас вырубить клиента, то завершенка ПРИДЕТ.
iocp.GetStatus( &dwKey, &dwBytes, &pop );

iocp.Release();

CloseHandle( hPipe );
==================================================================

Клиент:
==================================================================
HANDLE hPipe = CreateFile( T("\\\\.\\PIPE\\IOCP_TEST"),
GENERIC_READ | GENERIC_WRITE, 0, NULL, OPEN_EXISTING, 0, NULL );

/*
BYTE bBuffer[1024] = {0};
DWORD dwRead = 0;
ReadFile( hPipe, bBuffer, 1024, &dwRead, NULL );
ReadFile( hPipe, bBuffer, 1024, &dwRead, NULL );
*/

CloseHandle( hPipe );
==================================================================
Попробуй слегка обобщить твой код на простейший "эхо"... 28.08.07 04:22  
Автор: void <Grebnev Valery> Статус: Elderman
<"чистая" ссылка>
Попробуй слегка обобщить твой код на простейший "эхо" клиент/сервер.
Зачем?! Я проверил конкретный механизм, работа которого... 28.08.07 08:43  
Автор: IgorR <Igor Razin> Статус: Member
<"чистая" ссылка>
> Попробуй слегка обобщить твой код на простейший "эхо"
> клиент/сервер.
Зачем?! Я проверил конкретный механизм, работа которого вызывала вопрос, он работает как надо. А обобщить и построить взаимодействие посложней - дело техники.
1




Rambler's Top100
Рейтинг@Mail.ru


  Copyright © 2001-2024 Dmitry Leonov   Page build time: 0 s   Design: Vadim Derkach