Легенда:
новое сообщение
закрытая нитка
новое сообщение
в закрытой нитке
старое сообщение
|
- Напоминаю, что масса вопросов по функционированию форума снимается после прочтения его описания.
- Новичкам также крайне полезно ознакомиться с данным документом.
В принципе, вопрос был даже не в том , как построить... 23.08.07 07:56 Число просмотров: 3255
Автор: 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. Но это уж больно прямолинейно, поскольку ошибка может быть связана и с объективными причинами на клиенте или в коммуникациях. Что со всем этим лучше делать – не знаю.
|
|
|