1

ALL, I am writing an application that will need to have an asynchronous blocking connection to the server. I have a GUI Form with the button and a class that performs a connection with BeginConnect/EndConnect pair. The implementation follows this MSDN example.

However, in the callback method thw exception is thrown.This is what I'm trying to avoid. What I want instead is to check if the connection has been made and if it's not spit the the appropriate error as a text on my GUI form.

I tried to check a return value of BeginConnect(), but it returns prior to calling the static method. I tried to re-throw the exception and catch it when I establish the connection (button click event), but it does not work. Exception is not caught.

How do I check if the connection has been made?

I can probably pass my text control to the connection class and set the text to an error when exception has been caught, but is there any other way?

Thank you.

[EDIT]

class InternetConnector
{
    public void ConnectToHost()
    {
        IPEndPoint ip = new IPEndPoint(IPAddress.Parse(connector_host), connector_port);
        client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        client.Blocking = true;
        client.BeginConnect(ip, new AsyncCallback(ConnectCallback), client);
    }

    private static void ConnectCallback(IAsyncResult ar)
    {
        try
        {
            Socket sock = (Socket)ar.AsyncState;
            sock.EndConnect(ar);
            connectDone.Set();
            Connected = true;
        }
        catch (Exception e)
        {
            throw (e);
        }
    }
}

public partial class Form1 : Form
{
    private bool isRunning = false;
    private InternetConnector client = new InternetConnector();

    private void startStop_Click(object sender, EventArgs e)
    {
        try
        {
            if (!isRunning || !InternetConnector.Connected)
            {
                if (!InternetConnector.Connected)
                {
                    client.SetAddress(ipAddress.Text);
                    client.SetPort(Convert.ToInt32(connectionport.Text));
                    client.ConnectToHost();
                    status.Text = "Signals Receiver: Connected";
                    status.ForeColor = Color.Green;
                    startStop.Text = "Stop";
                    isRunning = true;
                }
                else
                {
                    startStop.Text = "Start";
                    client.DisconnectFromHost();
                    isRunning = false;
                }
            }
        }
        catch(Exception ex)
        {
            status.Text = "Socket Error: " + ex.Message;
        }
    }
 }

If I use "throw( e );" in the callback catch block exception is not caught in click listener. But try/catch is required in the callback as faulty connection is throwing exception and not returning error.

Igor
  • 5,620
  • 11
  • 51
  • 103

3 Answers3

2

Because you are using the asynchronous BeginConnect, the callback almost never happens on the same thread as your click handler, so there is no way for your click handler to catch the exception.

If you need to set the status of a UI element from an asynchronous callback, you generally need to execute a delegate on the same thread that the UI was created on.

Any UI element deriving from Control has some methods to help you do this (InvokeRequired and BeginInvoke). I've modified your sample to help illustrate this.

Basically, you can pass in an Action delegate that will be called with the exception if it occurs in the async Connect callback. This error handling delegate can then do all of the work to set the UI status based on the exception that was thrown during the async connect callback.

Because we need to pass in the error handling delegate, we have to create a structure (Called ConnectionData) to hold the all of the state (the socket and the error handler) we need to access in the async connect callback.

class InternetConnector  
{  
    private struct ConnectionData
    {
        public Action<Exception> ErrorHandler { get; set; }
        public Socket Socket { get; set; }
    }

    public void ConnectToHost(Action<Exception> errorHandler)  
    {  
        IPEndPoint ip = new IPEndPoint(IPAddress.Parse(connector_host), connector_port);  
        client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);

        var connectionData = new ConnectionData { ErrorHandler = errorHandler, Socket = client };

        client.Blocking = true;  
        client.BeginConnect(ip, new AsyncCallback(ConnectCallback), connectionData);  
    }  

    private static void ConnectCallback(IAsyncResult ar)  
    {  
        ConnectionData connectionData = new ConnectionData();
        try  
        {  
            connectionData = (ConnectionData)ar.AsyncState;  
            connectionData.Socket.EndConnect(ar);  
            connectDone.Set();  
            Connected = true;  
        }  
        catch (Exception e)  
        {  
            if (connectionData.ErrorHandler != null)
                connectionData.ErrorHandler(e);
        }  
    }  
}  

public partial class Form1 : Form       
{       
    private bool isRunning = false;       
    private InternetConnector client = new InternetConnector();       

    private void AsyncErrorHandler(Exception e)
    {
        if (status.InvokeRequired)
        {
            // queue a call to ourself on control's UI thread and immediately return
            status.BeginInvoke(new Action(()=>AsyncErrorHandler(e)));
            return;
        }

        // we are on the right thread to set the status text
        status.Text = "Async Error: " + ex.Message;  
    }

    private void startStop_Click(object sender, EventArgs e)       
    {       
        try       
        {       
            if (!isRunning || !InternetConnector.Connected)       
            {       
                if (!InternetConnector.Connected)       
                {       
                    client.SetAddress(ipAddress.Text);       
                    client.SetPort(Convert.ToInt32(connectionport.Text));       

                    // connect, pass in the method to use for error reporting
                    client.ConnectToHost(AsyncErrorHandler);       

                    status.Text = "Signals Receiver: Connected";       
                    status.ForeColor = Color.Green;       
                    startStop.Text = "Stop";       
                    isRunning = true;       
                }       
                else       
                {       
                    startStop.Text = "Start";       
                    client.DisconnectFromHost();       
                    isRunning = false;       
                }       
            }       
        }       
        catch(Exception ex)       
        {       
            status.Text = "Socket Error: " + ex.Message;       
        }       
    }       
}       
Monroe Thomas
  • 4,962
  • 1
  • 17
  • 21
  • Got a compiler error: "error CS0037: Cannot convert null to 'WindowsFormsApplication1.InternetConnector.ConnectionData' because it is a non-nullable value type" on the line "ConnectionData connectionData = null;" – Igor Aug 08 '12 at 20:55
  • My bad. Change to `ConnectionData connectionData;`. Also note that the check for `connectionData == null` is gone in the catch block. – Monroe Thomas Aug 08 '12 at 21:06
  • "connectionData.ErrorHandler != null": "Use of unassigned local variable connectionData". Also: "status.BeginInvoke(() => AsyncErrorHandler(e));": Cannot convert lambda expression to type System.Delegate because it's not a delegate type". – Igor Aug 08 '12 at 21:38
  • Hopefully the last one. Getting "First chance exception ..." when excuting "connectionData = (ConnectionData)ar.AsyncState;" in the callback. Any idea? – Igor Aug 08 '12 at 22:11
  • @Igor Did you make sure to pass connectionData as the last param in the call to BeginConnect? client.BeginConnect(ip, new AsyncCallback(ConnectCallback), `connectionData`); Otherwise, can you post the full exception description? – Monroe Thomas Aug 08 '12 at 22:20
  • let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/15097/discussion-between-monroe-thomas-and-igor) – Monroe Thomas Aug 08 '12 at 22:23
  • 1
    Sorry, missed it. Works as expected. Thank you. – Igor Aug 08 '12 at 22:27
  • I think the `ex` in the `AsyncErrorHandler` needed to be `e`: `status.Text = "Async Error: " + e.Message;`. I'm also assuming that `status` is the name of a `TextBox`? In which case the `.Text` should be "Stopped" and "Started", not "Stop" and "Start", like it was a button. Also need a Boolean called `Connected` in `InternetConnector`. Submitted an edit. Otherwise, excellent code! – vapcguy May 11 '17 at 20:15
1

There is property Socket.Connected.

user854301
  • 5,383
  • 3
  • 28
  • 37
  • The thing is I want to show the cause of the error, like in the code I posted. Thank you. – Igor Aug 08 '12 at 20:02
  • @Igor Then you shouldn't have asked "How do I check if the connection has been made?". His answer answers your original question. If it is `true`, your connection is established. Showing the error and its cause if `false` is something different, entirely, but to do that, all you do is handle the exception - or if you use `Socket.Connected` as a Boolean, reset that Boolean each time. If you wind up with a `false`, you know to display the error in the exception block, `ex.Message`. – vapcguy May 11 '17 at 17:10
1

In windows

Return value

If no error occurs, connect returns zero. Otherwise, it returns SOCKET_ERROR, and a specific error code can be retrieved by calling WSAGetLastError.

Jay D
  • 3,263
  • 4
  • 32
  • 48