ときどきboost :: asioが切断する前に、つまりサーバーが切断を適切に処理する前に切断するように見えることがあります。クライアントがメッセージを完全に送信したと考えているように見えるため、これがどのように可能であるかはわかりませんが、サーバーがエラーを出力すると、メッセージヘッダーは読み取られません。サーバーはクライアントシャットダウンメッセージを受信し、クライアントを完全に切断します。
エラー:「既存の接続がリモートホストによって強制的に閉じられました」
クライアントの切断:
void disconnect()
{
boost::system::error_code error;
//just creates a simple buffer with a shutdown header
boost::uint8_t *packet = createPacket(PC_SHUTDOWN,0);
//sends it
if(!sendBlocking(socket,packet,&error))
{
//didnt get here in my tests, so its not that the write failed...
logWrite(LOG_ERROR,"server",
std::string("Error sending shutdown message.\n")
+ boost::system::system_error(error).what());
}
//actaully disconnect
socket.close();
ioService.stop();
}
bool sendBlocking(boost::asio::ip::tcp::socket &socket,
boost::uint8_t *data, boost::system::error_code* error)
{
//get the length section from the message
boost::uint16_t len = *(boost::uint16_t*)(data - 3);
//send it
asio::write(socket, asio::buffer(data-3,len+3),
asio::transfer_all(), *error);
deletePacket(data);
return !(*error);
}
サーバー:
void Client::clientShutdown()
{
//not getting here in problem cases
disconnect();
}
void Client::packetHandler(boost::uint8_t type, boost::uint8_t *data,
boost::uint16_t len, const boost::system::error_code& error)
{
if(error)
{
//error handled here
delete[] data;
std::stringstream ss;
ss << "Error recieving packet.\n";
ss << logInfo() << "\n";
ss << "Error: " << boost::system::system_error(error).what();
logWrite(LOG_ERROR,"Client",ss.str());
disconnect();
}
else
{
//call handlers based on type, most will then call startRead when
//done to get the next packet. Note however, that clientShutdown
//does not
...
}
}
void startRead(boost::asio::ip::tcp::socket &socket, PacketHandler handler)
{
boost::uint8_t *header = new boost::uint8_t[3];
boost::asio::async_read(socket,boost::asio::buffer(header,3),
boost::bind(&handleReadHeader,&socket,handler,header,
boost::asio::placeholders::bytes_transferred,boost::asio::placeholders::error));
}
void handleReadHeader(boost::asio::ip::tcp::socket *socket, PacketHandler handler,
boost::uint8_t *header, size_t len, const boost::system::error_code& error)
{
if(error)
{
//error "thrown" here, len always = 0 in problem cases...
delete[] header;
handler(0,0,0,error);
}
else
{
assert(len == 3);
boost::uint16_t payLoadLen = *((boost::uint16_t*)(header + 0));
boost::uint8_t type = *((boost::uint8_t*) (header + 2));
delete[] header;
boost::uint8_t *payLoad = new boost::uint8_t[payLoadLen];
boost::asio::async_read(*socket,boost::asio::buffer(payLoad,payLoadLen),
boost::bind(&handleReadBody,socket,handler,
type,payLoad,payLoadLen,
boost::asio::placeholders::bytes_transferred,boost::asio::placeholders::error));
}
}
void handleReadBody(ip::tcp::socket *socket, PacketHandler handler,
boost::uint8_t type, boost::uint8_t *payLoad, boost::uint16_t len,
size_t readLen, const boost::system::error_code& error)
{
if(error)
{
delete[] payLoad;
handler(0,0,0,error);
}
else
{
assert(len == readLen);
handler(type,payLoad,len,error);
//delete[] payLoad;
}
}
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec)
を呼び出す前に、おそらくsocket.close()
を呼び出す必要があると思います。
basic_stream_socket :: closeのブースト:: asioドキュメント 状態:
接続されたソケットの正常なクローズに関する移植可能な動作については、ソケットをクローズする前にshutdown()を呼び出します。
これにより、ソケットに対する保留中の操作が適切にキャンセルされ、socket.closeを呼び出す前にバッファーがフラッシュされます。
これをclose()メソッドとshutdown()メソッドの両方で実行しようとしました
socket.shutdown(boost::asio::ip::tcp::socket::shutdown_both, ec)
シャットダウンの方法は、2つの方法のうち最良のものです。ただし、ASIOがすべてを処理するので、ASIOソケットのデストラクタを使用するのがクリーンな方法であることがわかりました。したがって、あなたの目標は、ソケットをスコープ外にすることです。これで、shared_ptrを使用して簡単にこれを行うことができ、shared_ptrを新しいソケットまたはnullにリセットできます。これはASIOソケットのデストラクタを呼び出し、寿命は良好です。
多分これが起こっていることです:
私はあなたの読み取りハンドラで、エラーがある場合、シャットダウンパケットがそこにあるかどうかを確認することはありません。多分そうです。基本的に私が言っているのは、サーバーが個別に処理する前に、クライアントがクローズパケットとシャットダウンパケットの両方を送信できる場合があるということです。
Async_write()を使用して、socket.close()を書き込みハンドラ内に配置します。これにより、(close()呼び出しのため)パケットがboost asioによって処理され、処理の途中で無視されないようになります。
非常によく似た問題があります。 Windowsのリサイクル接続に関連していると思います。以下はおなじみですか?
Tcp仕様では、tcp接続が閉じられたときに、デフォルトで最終確認応答を4分間待機する必要があることを指定しています。これらの接続は、FIN_WAIT状態でnetstatを使用して確認できます。 Windows OSは、まったく同じシステムに接続しようとしたときにそれを検出し、これらの部分的に閉じられた接続を取得してリサイクルします。プログラムの2回目の呼び出しでは、最初の実行で残された「クローズ」接続が取得されます。次の確認応答を取得し、実際に閉じます。