6

我正在尝试利用 MSDN 的异步客户端套接字代码示例来连接和控制一些家用设备。据我了解,示例代码的ReceiveCallback方法使用 EventWaitHandle ManualResetEvent的实例和方法receiveDone.WaitOne()来保持当前线程的处理,直到线程接收到所有套接字数据已从远程设备传输的信号. 传输完所有套接字数据后(套接字数据为空且bytesRead = 0),Waithandle 被移除,应用程序继续处理。

不幸的是,通过逐步执行代码,似乎在客户端最后一次从远程设备返回数据之后,ReceiveCallback永远不会返回查看数据队列是否为空(即 bytesRead = 0),因此永远不会在ReceiveCallback中进入“else”条件,其中ManualResetEvent的状态将被重置并且应用程序将继续处理。因此,由于它永远不会进入“else”条件,ManualResetEvent永远不会重置并且应用程序会冻结。

虽然我可以从代码中删除“receiveDone.WaitOne()”方法——允许执行而无需等待ManualResetEvent通知已收到所有数据;这会从设备返回一个通常不完整的数据字符串。

我是否错误地使用了此代码示例?有没有人以前见过这个或有任何关于如何解决这个问题的经验?

2012 年 7 月 14 日 - 更新:在进一步测试 MSDN 的异步客户端套接字示例后,很明显ReceiveCallback实际上重新轮询端口,并且只有在释放套接字时才满足“bytesRead = 0”条件(即客户端。关机(SocketShutdown.Both);client.Close();)。如果我理解正确,这意味着必须关闭连接才能通过 receiveDone.WaitOne() 方法。如果连接被关闭以满足 WaitOne() Waithandle,它完全违背了应用程序的目的,因为我一直希望保持连接打开,以便应用程序可以监听设备更新,这些更新会不断发生。

2012 年 7 月 16 日 - 更新:我已写信给Microsoft 技术支持,他们回复说“我们正在研究这个问题。我们可能需要一些时间才能回复您。” 因此,目前似乎无法通过按摩此代码来解决此挑战。

如果没有异步客户端套接字示例代码作为编写异步通信程序的基础,请问是否有人可以建议一个更可靠的替换例程? 共有三台设备,每台都有自己的 IP 地址和端口号。因此,如果可以使用一个类,为每个设备创建一个实例,那将是理想的。此外,端口必须保持打开状态才能接收设备不断发送的自发更新。最后,更新没有结束字符或定义的长度表示消息传输完成,因此例程必须不断轮询端口以获取可用数据。 任何意见或建议将不胜感激。

2012 年 7 月 18 日 - 解决方法:在花费大量时间尝试使MSDN 的异步客户端套接字代码示例正常工作后,很明显我必须寻找其他地方才能让程序持续识别设备响应。为了挽救其他人的脑损伤,我已经包含了我使用的解决方法,到目前为止似乎效果很好。如果有人有任何建议,请不要犹豫,添加到这个问题!

// 
// ORIGINAL CODE ATTEMPT
//
public static Socket LutronClient;
public static String LutronResponse = String.Empty;
private const int LutronPort = 4999;
private const string LutronIP = "192.168.1.71";
private static ManualResetEvent LutronConnectDone = new ManualResetEvent(false);
private static ManualResetEvent LutronSendDone = new ManualResetEvent(false);
private static ManualResetEvent LutronReceiveDone = new ManualResetEvent(false);

private static void StartLutronClient()
    {
        try
        {
            var lutronIPAddress = IPAddress.Parse(LutronIP);
            var lutronRemoteEP = new IPEndPoint(lutronIPAddress, LutronPort);
            LutronClient = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
            LutronClient.BeginConnect(lutronRemoteEP, LutronConnectCallback, LutronClient);
            LutronConnectDone.WaitOne();

            LutronSend(LutronClient, "sdl,14,100,0,S2\x0d");
            LutronSendDone.WaitOne();
            LutronReceive(LutronClient);
            LutronReceiveDone.WaitOne(new TimeSpan(5000));
            MessageBox.Show("Response received from Lutron: " + LutronResponse);
            txtBoxLutron.Text = LutronResponse;

            LutronClient.Shutdown(SocketShutdown.Both);
            LutronClient.Close();
        }
        catch (Exception e) { MessageBox.Show(e.ToString()); }
    }

    private static void LutronConnectCallback(IAsyncResult lutronAr)
    {
        try
        {
            var lutronClient = (Socket)lutronAr.AsyncState;
            lutronClient.EndConnect(lutronAr);
            LutronConnectDone.Set();
        }
        catch (Exception e) { MessageBox.Show(e.ToString()); }
    }

    private static void LutronReceive(Socket lutronClient)
    {
        try
        {
            var lutronState = new LutronStateObject { LutronWorkSocket = lutronClient };
            lutronClient.BeginReceive(lutronState.LutronBuffer, 0, LutronStateObject.BufferSize, 0, new AsyncCallback(LutronReceiveCallback), lutronState);
        }
        catch (Exception e) { MessageBox.Show(e.ToString()); }
    }

    private static void LutronReceiveCallback(IAsyncResult lutronAR)
    {
        try
        {
            var lutronState = (LutronStateObject)lutronAR.AsyncState;
            var lutronClient = lutronState.LutronWorkSocket;
            var bytesRead = lutronClient.EndReceive(lutronAR);
            if (bytesRead > 0)
            {
                lutronState.LutronStringBuilder.AppendLine(Encoding.ASCII.GetString(lutronState.LutronBuffer, 0, bytesRead));
                lutronClient.BeginReceive(lutronState.LutronBuffer, 0, LutronStateObject.BufferSize, 0, new AsyncCallback(LutronReceiveCallback), lutronState);
            }
            else
            {
                if (lutronState.LutronStringBuilder.Length > 0) { LutronResponse = lutronState.LutronStringBuilder.ToString(); }
                LutronReceiveDone.Set();
            }
        }
        catch (Exception e) { MessageBox.Show(e.ToString()); }
    }

    public static void LutronSend(Socket client, String data)
    {
        var byteData = Encoding.ASCII.GetBytes(data);
        client.BeginSend(byteData, 0, byteData.Length, 0, LutronSendCallback, client);
    }

    private static void LutronSendCallback(IAsyncResult ar)
    {
        try
        {
            var client = (Socket)ar.AsyncState;
            var bytesSent = client.EndSend(ar);
            LutronSendDone.Set();
        }
        catch (Exception e) { MessageBox.Show(e.ToString()); }
    }
    public class LutronStateObject
    {
        public Socket LutronWorkSocket;
        public const int BufferSize = 256;
        public byte[] LutronBuffer = new byte[BufferSize];
        public StringBuilder LutronStringBuilder = new StringBuilder();
    }

}

这是我使用的解决方法:

 //
 // WORK-AROUND
 //
 using System;
 using System.Windows.Forms;

 namespace _GlobalCacheInterface
 {
     public partial class GlobalCacheDataScreen : Form
     {

         //Interface objects
         private static GC_Interface _lutronInterface;
         private const int LutronPort = 4999;
         private const string LutronIP = "192.168.1.71";
         delegate void ThreadSafeLutronCallback(string text);

         private static GC_Interface _elanInterface;
         private const int ElanPort = 4998;
         private const string ElanIP = "192.168.1.70";
         delegate void ThreadSafeElanCallback(string text);

         private static GC_Interface _tuneSuiteInterface;
         private const int TuneSuitePort = 5000;
         private const string TuneSuiteIP = "192.168.1.70";
         delegate void ThreadSafeTuneSuiteCallback(string text);

         public GlobalCacheDataScreen()
         {
              InitializeComponent();

              _lutronInterface = new GC_Interface(LutronIP, LutronPort);
              _elanInterface = new GC_Interface(ElanIP, ElanPort);
              _tuneSuiteInterface = new GC_Interface(TuneSuiteIP, TuneSuitePort);

             // Create event handlers to notify application of available updated information.
             _lutronInterface.DataAvailable += (s, e) => ThreadSafeTxtBoxLutron(_lutronInterface._returnString);
             _elanInterface.DataAvailable += (s, e) => ThreadSafeTxtBoxElan(_elanInterface._returnString);
             _tuneSuiteInterface.DataAvailable += (s, e) => ThreadSafeTxtBoxTuneSuite(_tuneSuiteInterface._returnString);
             _lutronInterface.Connected += (s, e) => UpdateUI();
             _elanInterface.Connected += (s, e) => UpdateUI();
             _tuneSuiteInterface.Connected += (s, e) => UpdateUI();

             UpdateUI();
         }

         private void UpdateUI()
         {
             _buttonConnectToLutron.Enabled = !_lutronInterface._isConnected;
             _buttonConnectToElan.Enabled = !_elanInterface._isConnected;
             _buttonConnectToTuneSuite.Enabled = !_tuneSuiteInterface._isConnected;
             _buttonDisconnectFromLutron.Enabled = _lutronInterface._isConnected;
             _buttonDisconnectFromElan.Enabled = _elanInterface._isConnected;
             _buttonDisconnectFromTuneSuite.Enabled = _tuneSuiteInterface._isConnected;
             string connectLutronStatus = _lutronInterface._isConnected ? "Connected" : "Not Connected";
             string connectElanStatus = _elanInterface._isConnected ? "Connected" : "Not Connected";
             string connectTuneSuiteStatus = _tuneSuiteInterface._isConnected ? "Connected" : "Not Connected";
             _textBoxLutronConnectStatus.Text = connectLutronStatus;
             _textBoxElanConnectStatus.Text = connectElanStatus;
             _textBoxTuneSuiteConnectStatus.Text = connectTuneSuiteStatus;
         }


         private void ThreadSafeTxtBoxLutron(string message) { if (_lutronRichTextRxMessage.InvokeRequired) { var d = new ThreadSafeLutronCallback(ThreadSafeTxtBoxLutron); _lutronRichTextRxMessage.Invoke(d, new object[] { message }); } else { _lutronRichTextRxMessage.Text = message; } }     
         private void ThreadSafeTxtBoxElan(string message) { if (_elanRichTextRxMessage.InvokeRequired) { var d = new ThreadSafeElanCallback(ThreadSafeTxtBoxElan); _elanRichTextRxMessage.Invoke(d, new object[] { message }); } else { _elanRichTextRxMessage.Text = message; if (message.EndsWith("\r")) { MessageBoxEx.Show(message, "Message from Lutron Elan", 1000); } } }
         private void ThreadSafeTxtBoxTuneSuite(string message) { if (_tuneSuiteRichTextRxMessage.InvokeRequired) { var d = new ThreadSafeTuneSuiteCallback(ThreadSafeTxtBoxTuneSuite); _tuneSuiteRichTextRxMessage.Invoke(d, new object[] { message }); } else { _tuneSuiteRichTextRxMessage.Text = message; if (message.EndsWith("\r")) { MessageBoxEx.Show(message, "Message from TuneSuite", 1000); } } }

         private void _buttonConnectToLutron_Click(object sender, EventArgs e) { _lutronInterface.Connect(); }
         private void _buttonDisconnectFromLutron_Click(object sender, EventArgs e) { _lutronInterface.Disconnect(); }
         private void _buttonConnectToElan_Click(object sender, EventArgs e) { _elanInterface.Connect(); }
         private void _buttonDisconnectFromElan_Click(object sender, EventArgs e) { _elanInterface.Disconnect(); }
         private void _buttonConnectToTuneSuite_Click(object sender, EventArgs e) { _tuneSuiteInterface.Connect(); }
         private void _buttonDisconnectFromTuneSuite_Click(object sender, EventArgs e) { _tuneSuiteInterface.Disconnect(); }
         private void _buttonLutronSendMessage_Click(object sender, EventArgs e) { _lutronInterface.SendCommand(_lutronRichTextTxMessage.Text); }
         private void _buttonElanSendMessage_Click(object sender, EventArgs e) { _elanInterface.SendCommand(_elanRichTextTxMessage.Text); }
         private void _buttonTuneSuiteSendMessage_Click(object sender, EventArgs e) { _tuneSuiteInterface.SendCommand(_elanRichTextTxMessage.Text); }
         private void _buttonLightOn_Click(object sender, EventArgs e) { _lutronInterface.SendCommand("sdl,14,100,0,S2"); }
         private void _buttonLightOff_Click(object sender, EventArgs e) { _lutronInterface.SendCommand("sdl,14,0,0,S2"); }
         private void _buttonStereoOnOff_Click(object sender, EventArgs e) { _elanInterface.SendCommand("sendir,4:3,1,40000,4,1,21,181,21,181,21,181,21,181,21,181,21,181,21,181,21,181,21,181,21,181,21,181,21,800"); }
         private void _button30_Click(object sender, EventArgs e) { _tuneSuiteInterface.SendCommand("\xB8\x4D\xB5\x33\x30\x00\x30\x21\xB8"); }
         private void _button31_Click(object sender, EventArgs e) { _tuneSuiteInterface.SendCommand("\xB8\x4D\xB5\x33\x31\x00\x30\x21\xB8"); }
         private void _button26_Click(object sender, EventArgs e) { _tuneSuiteInterface.SendCommand("\xB8\x4D\xB5\x32\x36\x00\x30\x21\xB8"); }
     }
 }

和 GC_Interface 类:

 using System;
 using System.Net;
 using System.Net.Sockets;
 using System.Text;
 using System.Windows.Forms;

 namespace _GlobalCacheInterface
 {
     class GC_Interface
     {
         // Declare an event handler to notify when updates are available.
         public event EventHandler<EventArgs> DataAvailable;
         public string _returnString = "";

         // Declare an event handler to notify status of connection.
         public event EventHandler<EventArgs> Connected;
         public bool _isConnected;

         public AsyncCallback ReceiveCallback;
         public Socket Client;
         private string _ipAddress;
         private int _port;
         private bool _waitForEndCharacter;
         private byte _endCharacter;
         byte[] m_DataBuffer = new byte[10];
         IAsyncResult m_Result;

         public GC_Interface(string ipAddress, int port) { Init(ipAddress, port, false, 0); }

         private void Init(string ipAddress, int port, bool waitForEndCharacter, byte endCharacter)
         {
             _ipAddress = ipAddress;
             _port = port;
             _waitForEndCharacter = waitForEndCharacter;
             _endCharacter = endCharacter;
             _isConnected = false;
         }

         public bool Connect()
         {
             try
             {
                 // Create a TCP/IP socket.
                 Client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

                 // Establish the remote endpoint for the socket.
                 var address = IPAddress.Parse(_ipAddress);
                 var remoteEP = new IPEndPoint(address, _port);

                 // Connect to the remote endpoint.
                 Client.Connect(remoteEP);
                 if (Client.Connected)
                 {
                     _isConnected = true;
                     ConnectedEventHandler();
                     WaitForData();
                 }
                 return true;
             }
             catch (SocketException se) { MessageBox.Show("\n connection failed, is the server running?\n" + se.Message ); return false; }
         }
         public bool SendCommand(string command)
         {
             try
             {
                 // Convert the string data to byte data using ASCII encoding.
                 var byteData = Encoding.Default.GetBytes(command);
                 // Add a carraige-return to the end.  
                 var newArray = new byte[byteData.Length + 1];
                 byteData.CopyTo(newArray, 0);
                 newArray[newArray.Length - 1] = 13;
                 if (Client == null) { return false; }
                 Client.Send(newArray);
                 return true;
             }
             catch (SocketException se) {  MessageBox.Show(se.Message); return false;  }
         }
         public void WaitForData()
         {
             try
             {
                 if (ReceiveCallback == null) { ReceiveCallback = new AsyncCallback(OnDataReceived); }
                 var theSocPkt = new SocketPacket { thisSocket = Client };
                 m_Result = Client.BeginReceive(theSocPkt.DataBuffer, 0, theSocPkt.DataBuffer.Length, SocketFlags.None, ReceiveCallback, theSocPkt);
             }
             catch (SocketException se) { MessageBox.Show(se.Message); }
         }
         public class SocketPacket
         {
             public System.Net.Sockets.Socket thisSocket;
             public byte[] DataBuffer = new byte[1];
         }
         public void OnDataReceived(IAsyncResult asyn)
         {
             try
             {
                  SocketPacket theSockId = (SocketPacket)asyn.AsyncState;
                 var iRx = theSockId.thisSocket.EndReceive(asyn);
                 char[] Chars = new char[iRx + 1];
                 System.Text.Decoder d = System.Text.Encoding.UTF8.GetDecoder();
                 int CharLen = d.GetChars(theSockId.DataBuffer, 0, iRx, Chars, 0);
                 System.String szData = new System.String(Chars);
                 _returnString = _returnString + szData.Replace("\0", "");
                 // When an update is received, raise DataAvailable event
                 DataAvailableEventHandler();
                 WaitForData();
             }
             catch (ObjectDisposedException) { System.Diagnostics.Debugger.Log(0, "1", "\nOnDataReceived: Socket has been closed\n"); }
             catch (SocketException se) { MessageBox.Show(se.Message); }
         }
         public bool Disconnect()
         {
              try
              {
                  if (Client == null) { return false; }
                  Client.Close(); 
                  Client = null;
                  _isConnected = false;
                  return true;
              }
              catch (Exception) { return false; }
         }
         protected virtual void DataAvailableEventHandler()
         {
             var handler = DataAvailable;
             if (handler != null) { handler(this, EventArgs.Empty); }
         }
         protected virtual void ConnectedEventHandler()
         {
             var handler = Connected;
             if (handler != null) { handler(this, EventArgs.Empty); }
         }

     }
 }
4

2 回答 2

5

我遇到了同样的问题,在代码中添加了一个可用的检查来解决我的问题。下面是修改后的代码。

private static void ReceiveCallback( IAsyncResult ar ) {
        try {
            StateObject state = (StateObject) ar.AsyncState;
            Socket client = state.workSocket;

            int bytesRead = client.EndReceive(ar);
            if (bytesRead > 0) {
                state.sb.Append(Encoding.ASCII.GetString(state.buffer, 0, bytesRead));
                // Check if there is anymore data on the socket
                if (client.Available > 0) {
                    client.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReceiveCallback), state);
                }
            }

            if (bytesRead == 0 || client.Available == 0) {
                if (state.sb.Length > 1) {
                    response = state.sb.ToString();
                }
                receiveDone.Set();
            }
        } catch (Exception e) {
            Console.WriteLine(e.ToString());
        }
    }

希望有帮助。

于 2012-07-23T12:57:10.057 回答
1

我在这里回避这个问题。我尝试回答您的需求,而不是您的要求:

使用同步代码。它会更容易理解,您不需要回调或事件。此外,对于低线程数,它可能会执行得更好。

您还可以避免当前代码中存在的错误。如果发生异常,您的计算永远不会完成。同步代码没有这个问题。

于 2012-07-12T20:32:33.570 回答