Чтение из закрытого сокета в boost.asio
От: Wody  
Дата: 26.07.11 22:35
Оценка:
Вот такая ситуация. Есть http-клиент, который соединяется с сервером и получает пакет данных. На весь сеанс работы отводится 5 секунд. Если клиент не успевает за 5 сек. получить данные, он разрывает соединение. Проблема в том, что (редко) в случае когда соединение разрывается из-за таймаута, хендлер для чтения данных (OnRead(const error_code& err)) вызывается когда сокет уже закрыт, а в err код ошибки не operation_aborted (как следует из документации по asio), а 0, то есть ошибки-то и нет. Причем, в буффере для чтения действительно ответ сервера.
В связи с этим 2 вопроса.
1) Правильно ли я понимаю, что если данные уже пришли и находятся в буффере, хендлер удаляется из очереди io_service и даже после закрытия сокета будет вызван без ошибки?
2) Если это верно (то есть close() не гарантирует вызов хендлера с кодом ошибки operation_aborted), то как лучше решить эту проблему? Или так нормально: if(!err && skt_.is_open()) ?
Заранее благодарен.

Код для пояснения:
//соединяемся с endpoint_. Таймер нужен чтобы разорвать соединение после 5 секунд простоя.
void Client::Start()
{
 timer_.expires_from_now(posix_time::seconds(5));
 timer_.async_wait(bind(&Client::OnTimeout, shared_from_this(), asio::placeholders::error));
 skt_.async_connect(endpoint_, bind(&Client::OnConnected, shared_from_this(), asio::placeholders::error));
}

//если прошло 5 секунд, закрываем сокет
void Client::OnTimeout(const error_code& err)
{
 if(!err)
 {
  error_code e;
  skt_.shutdown(asio::ip::tcp::socket::shutdown_both, e);
  assert(!err);
  skt_.close(e);
  assert(!err);
 }
}

//Функция не имеет значения, т.к. в случае таймаута сюда не попадаем
void Stop()
{
 timer_.cancel();
}

//Если соединение установлено, посылается запрос.
void Client::OnConnected(const error_code& err)
{
 if(!err)
  asio::async_write(skt_, request_, bind(&Client::OnSent, shared_from_this(), asio::placeholders::error));
 else if(err != asio::error::operation_aborted)
  Stop();
}

//Запрос успешно отправлен серверу.
void Client::OnSent(const error_code& err)
{
 if(!err)
  asio::async_read_until(skt_, reply_, "\r\n\r\n", bind(&Client::OnRead, shared_from_this(), asio::placeholders::error));
 else if(err != asio::error::operation_aborted)
  Stop();
}

//В этот момент, между функциями OnSent и OnRead сработал таймер и вызвал OnTimeout. Было выполнено skt_.close() и OnRead должен вызваться с err == boost::asio::error::operation_aborted

void Client::OnRead(const error_code& err)
{
 if(!err)
  assert(skt_.is_open()); //Вот тут иногда программа падает после разрыва соединения по таймауту
 //...
}
Re: Чтение из закрытого сокета в boost.asio
От: abrarov Россия http://asio-samples.blogspot.com/
Дата: 27.07.11 08:44
Оценка: 3 (1)
Здравствуйте, Wody, Вы писали:

W>Вот такая ситуация. Есть http-клиент, который соединяется с сервером и получает пакет данных. На весь сеанс работы отводится 5 секунд. Если клиент не успевает за 5 сек. получить данные, он разрывает соединение. Проблема в том, что (редко) в случае когда соединение разрывается из-за таймаута, хендлер для чтения данных (OnRead(const error_code& err)) вызывается когда сокет уже закрыт, а в err код ошибки не operation_aborted (как следует из документации по asio), а 0, то есть ошибки-то и нет. Причем, в буффере для чтения действительно ответ сервера.

W>В связи с этим 2 вопроса.
W>1) Правильно ли я понимаю, что если данные уже пришли и находятся в буффере, хендлер удаляется из очереди io_service и даже после закрытия сокета будет вызван без ошибки?
W>2) Если это верно (то есть close() не гарантирует вызов хендлера с кодом ошибки operation_aborted), то как лучше решить эту проблему? Или так нормально: if(!err && skt_.is_open()) ?

Дело в том, что завершение асинхронной операции и вызов обработчика (completion handler) в proactor разделены. Второе случается после первого (причинно-следственная связь), но не одновременно. После завершения (по завершению) асинхронной операции completion handler помещается в очередь, из которой его забирает тот поток (thread), что выполняет метод asio::io_service::poll/poll_one/run/run_one.
В Вашем случае асинхронная операция завершилась до закрытия сокета (!). Но вот вызов completion handler происходит в тот момент, когда сокет уже закрыт. Что делать в этом случае, зависит от логики работы приложения.

if(!err && skt_.is_open())

Это м/б одним из подходящих вариантов.
Programs must be written for people to read, and only incidentally for machines to execute
Re[2]: Чтение из закрытого сокета в boost.asio
От: Wody  
Дата: 27.07.11 08:51
Оценка:
Здравствуйте, abrarov, Вы писали:

Огромное спасибо. Внесли ясность
 
Подождите ...
Wait...
Пока на собственное сообщение не было ответов, его можно удалить.