私は3日間1つの問題と戦っていますが、解決策は見つかりません。助けてください:)
Visual Studio 2010とC#言語を使用しています。
サーバーのようなデバイスがあり、非常に不規則な期間にデータを送信します(読み取りタイムアウトを定義することはできません)。
TCPクライアントがそのサーバーに接続してデータを読み取るために作成しました。ネットワークに問題があり、サーバーが利用できなくなった場合(たとえば、コンピュータからのネットワークケーブル)、アプリケーションがサーバーに接続されていないことを「通知」して例外をスローするまでに約10秒かかります(なぜ正確に10秒なのかわかりません。どこで定義されているのですか?変更できますか? ?)
反応を速くしたい-接続が切断されてから1秒後としましょう。
しかし、答えをググリングしても、有効な解決策はありません。
テストコードは以下です、私は2つのスレッドでそれを作ろうとします:1つはデータを読んでいます、2つ目は接続ステータスを探しており、それが壊れたときに私に警告するはずです。 TCPClientでもSocketクラスでも機能しません。 tcpClient.SendTimeout = 100;
とstream.WriteTimeout = 100;
を使用して一部のデータを読み書きしようとしましたが、動作しないようです。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
namespace TCPClient
{
class Program
{
static volatile bool _continue = true;
static TcpClient tcpClient;
static NetworkStream stream;
static void Main(string[] args)
{
try
{
//client thread - listen from server
Thread tcpListenThread = new Thread(TcpListenThread);
tcpListenThread.Start();
//connection checking thread
Thread keepAliveThread = new Thread(KeepAliveThread);
keepAliveThread.Start();
while (_continue)
{
if (Console.ReadLine() == "q")
{
_continue = false;
}
}
keepAliveThread.Join(2000);
if (keepAliveThread.IsAlive)
{
Console.WriteLine("Thread keepAlive has been aborted...");
keepAliveThread.Abort();
}
tcpListenThread.Join(2000);
if (tcpListenThread.IsAlive)
{
Console.WriteLine("Listen thread has been aborted...");
tcpListenThread.Abort();
}
}
catch (Exception ex)
{
Console.WriteLine("\n" + ex.Message);
}
Console.WriteLine("\nHit any key to quit...");
Console.Read();
}
private static void TcpListenThread()
{
string server = "172.20.30.40";
int port = 3000;
try
{
using (tcpClient = new TcpClient())
{
tcpClient.Connect(server, port);
if (tcpClient.Connected)
Console.WriteLine("Successfully connected to server");
using (stream = tcpClient.GetStream())
{
while (_continue)
{
Byte[] data = new Byte[1024];
Int32 bytes = stream.Read(data, 0, data.Length);
string responseData = System.Text.Encoding.ASCII.GetString(data, 0, bytes);
Console.WriteLine("Received: {0}, responseData);
}
}
}
}
catch (Exception ex)
{
Console.WriteLine("Listen thread exception! " + ex.Message);
}
}
private static void KeepAliveThread()
{
while (_continue)
{
if (tcpClient != null)
{
try
{
//...what to put here to check or throw exception when server is not available??...
}
catch (Exception ex)
{
Console.WriteLine("Disconnected...");
}
}
Thread.Sleep(1000); //test for connection every 1000ms
}
}
}
}
編集:
@ carstenの回答:有望に見えますが、このソリューションは機能しません...
そのための最も簡単なテストアプリケーションを作成しました。
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Net.Sockets;
using System.Threading;
namespace TCPClientTest
{
class Program
{
static void Main(string[] args)
{
try
{
string server = "172.20.30.40";
int port = 3000;
using (TcpClient tcpClient = new TcpClient())
{
tcpClient.Connect(server, port);
int i = 0;
while (true)
{
// This is how you can determine whether a socket is still connected.
bool blockingState = tcpClient.Client.Blocking;
try
{
byte[] tmp = new byte[1];
tcpClient.Client.Blocking = false;
tcpClient.Client.Send(tmp, 0, 0);
Console.WriteLine("Connected!");
}
catch (SocketException e)
{
// 10035 == WSAEWOULDBLOCK
if (e.NativeErrorCode.Equals(10035))
Console.WriteLine("Still Connected, but the Send would block");
else
{
Console.WriteLine("Disconnected: error code {0}!", e.NativeErrorCode);
}
}
finally
{
tcpClient.Client.Blocking = blockingState;
}
Console.WriteLine("Connected: {0} ({1})", tcpClient.Client.Connected, i++);
Thread.Sleep(1000);
}
}
}
catch (Exception ex)
{
Console.WriteLine("Global exception: {0}", ex.Message);
}
}
}
}
結果は以下のとおりです。
接続済み!
接続済み:True
プラス1秒ごとに注文番号。ネットワークケーブルを外すと、印刷を開始するのに8秒かかります:
非推奨:エラーコード10054!
接続済み:False
そのため、8秒までに接続が失われたことに気づきません。ここではpingが最良のオプションのようですが、別のソリューションをテストします。
簡単な答え。できません。 Tcpは、これを許可しない方法で作成されています。ただし、これを実現する通常の方法は、メッセージよりも短い間隔でpingを送信することです。つまり、サーバーからメッセージを受信するたびに、1分のカウントダウンを開始するクロックを開始してから、「ping」コマンドを送信します(その間に新しいメッセージを受信していない場合)。 「ping」に対する応答が30秒以内に届かない場合は、接続が切断されていると判断します。
理論的には、パッケージレベルでこれを実行できるはずです(tcpは確認できるはずのACKを送信します)。ファームウェア/ハードウェアでそれを行う必要があります。
これはよくある質問だと思います。これが、MSDNドキュメントが本当にこれに良い答えを与える理由かもしれません-参照 Socket.Connected
そこからの引用:
Connectedプロパティは、最後のI/O操作時のソケットの接続状態を取得します。 falseが返された場合、ソケットは接続されていないか、接続されていません。
Connectedプロパティの値は、最新の操作の時点での接続の状態を反映しています。接続の現在の状態を判別する必要がある場合は、ノンブロッキングのゼロバイトのSend呼び出しを行います。呼び出しが正常に戻るか、WAEWOULDBLOCKエラーコード(10035)をスローする場合、ソケットはまだ接続されています。そうしないと、ソケットは接続されなくなります。
このサンプルコードで:
// .Connect throws an exception if unsuccessful
client.Connect(anEndPoint);
// This is how you can determine whether a socket is still connected.
bool blockingState = client.Blocking;
try
{
byte [] tmp = new byte[1];
client.Blocking = false;
client.Send(tmp, 0, 0);
Console.WriteLine("Connected!");
}
catch (SocketException e)
{
// 10035 == WSAEWOULDBLOCK
if (e.NativeErrorCode.Equals(10035))
Console.WriteLine("Still Connected, but the Send would block");
else
{
Console.WriteLine("Disconnected: error code {0}!", e.NativeErrorCode);
}
}
finally
{
client.Blocking = blockingState;
}
Console.WriteLine("Connected: {0}", client.Connected);
そして、extension-methodの単純なモチベーション:
public static bool IsConnected(this Socket client)
{
// This is how you can determine whether a socket is still connected.
bool blockingState = client.Blocking;
try
{
byte [] tmp = new byte[1];
client.Blocking = false;
client.Send(tmp, 0, 0);
return true;
}
catch (SocketException e)
{
// 10035 == WSAEWOULDBLOCK
if (e.NativeErrorCode.Equals(10035))
return true;
else
{
return false;
}
}
finally
{
client.Blocking = blockingState;
}
}
これは非常に古いスレッドですが、この質問を検索したときに最初に出てきたSOの投稿であり、他の場所でより有用な解決策を見つけたので、リンクを投稿すると思いました将来的に他の人を助けるために:
ElFenixがこの回答を投稿してくれました。
// Detect if client disconnected
if (tcp.Client.Poll(0, SelectMode.SelectRead))
{
byte[] buff = new byte[1];
if (tcp.Client.Receive(buff, SocketFlags.Peek) == 0)
{
// Client disconnected
bClosed = true;
}
}
念のため、他の誰かがシンプルで効果的なものを必要としています。
これは私が思いついたコードです
while (true)
{
string s = null;
DateTime readLag = DateTime.Now;
try
{
s = streamIn.ReadLine();
}
catch (Exception ex)
{
SocketException sockEx = ex.InnerException as SocketException;
if (sockEx != null)
{
OnDebug("(" + sockEx.NativeErrorCode + ") Exception = " + ex);
}
else
{
OnDebug("Not SockEx :" + ex);
}
}
if (enableLog) OnDebug(s);
if (s == null || s == "")
{
if (readLag.AddSeconds(.9) > DateTime.Now)
{
break;
}
}
else
{
CommandParser(s);
}
}
私が得たのは、読み取りが失敗したがブロックされるたびに、ネイティブエラー10035でした。
接続が破棄されたとき、私はすぐにデータなしで戻りました。
ReadLag.AddSeconds()を読み取りタイムアウトのすぐ下に設定すると、時間が経過しておらず、データが得られないというかなり良い考えが得られます。これは起こらないはずです。
この基準が表示されるとすぐに、ループから抜け出し、スレッドが終了します。
これが他の誰かを助けることを願っています。
次のように入力すると、正しく機能します。
byte[] tmp = new byte[] {0};
...
client.Send(tmp, 1, 0);
...
TCPプロトコルが時々プローブを送信して、パイプのもう一方の端がまだリッスンしていることを確認します。オリジナルのUNIX実装からSO_KEEPALIVEと呼ばれるソケットオプションがあります。プローブはSocket.IOControlを介して設定されます