views:

35

answers:

2

I have a class which has to send and receive data using sockets in Silverlight 4. It has to implement a pre-existing interface, so some things might look somewhat weird, but here it is:

public class TcpDataTransportClient : IDataTransportService
{
    private const string TCP_ADDRESS_SETTING = "tcpaddress";
    private const string TCP_PORT_SETTING = "tcpport";

    private static ManualResetEvent clientConnected = new ManualResetEvent(false);
    private static ManualResetEvent clientDataReceived = new ManualResetEvent(false);
    private static ManualResetEvent clientDataSent = new ManualResetEvent(false);

    private Dictionary<string, object> settings = new Dictionary<string, object>();
    private IDataEncapsulator dataEncapsulator;
    private IDataCollector dataCollector;

    private Socket client;
    private SocketAsyncEventArgs clientArgs;

    public event DataReceivedHandler OnDataReceived;
    public event DataSentHandler OnDataSent;

    public TcpDataTransportClient()
    {

    }

    public Dictionary<string, object> Settings
    {
        get
        {
            return this.settings;
        }
        set
        {
            this.settings = value;
        }
    }

    public IDataEncapsulator DataEncapsulator
    {
        get
        {
            return this.dataEncapsulator;
        }
        set
        {
            this.dataEncapsulator = value;
        }
    }

    public void Start(IDataCollector dataCollector)
    {
        this.dataCollector = dataCollector;
        clientArgs = new SocketAsyncEventArgs();

        client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        clientArgs.Completed += clientArgs_Completed;
        clientArgs.UserToken = client;            
        clientArgs.RemoteEndPoint = GetIPEndPoint();

        client.ConnectAsync(clientArgs);
        clientConnected.WaitOne();          
    }

    private IPEndPoint GetIPEndPoint()
    {
        IPAddress ipAddress;
        int tcpPort;

        if (!IPAddress.TryParse(settings[TCP_ADDRESS_SETTING].ToString(), out ipAddress))
            throw new ArgumentException(String.Format("Invalid setting for IP Address: '{0}'", TCP_ADDRESS_SETTING));

        if (!int.TryParse(settings[TCP_PORT_SETTING].ToString(), out tcpPort))
            throw new ArgumentException(String.Format("Invalid setting for TCP Port: '{0}'", TCP_PORT_SETTING));

        return new IPEndPoint(ipAddress, tcpPort);
    }

    void clientArgs_Completed(object sender, SocketAsyncEventArgs e)
    {
        switch (e.LastOperation)
        {
            case SocketAsyncOperation.Connect:
                ProcessConnect(e);
                break;
            case SocketAsyncOperation.Receive:
                ProcessReceive(e);
                break;
            case SocketAsyncOperation.Send:
                ProcessSend(e);
                break;
            default:
                throw new Exception("Invalid operation completed");
        }
    }

    private void ProcessConnect(SocketAsyncEventArgs e)
    {
        if (e.SocketError != SocketError.Success)
        {
            throw new SocketException((int)e.SocketError);
        }
        else
        {
            clientConnected.Set();
        }
    }

    private void ProcessReceive(SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            var socket = e.UserToken as Socket;

            var response = dataCollector.Collect(e.Buffer);

            if (response != null)
            {
                if (this.OnDataReceived != null)
                    this.OnDataReceived(response);

                clientDataReceived.Set();
            }
            else
            {
                bool willRaiseEvent = socket.ReceiveAsync(clientArgs);
                if (!willRaiseEvent)
                    ProcessReceive(e);
            }
        }
        else
        {
            throw new SocketException((int)e.SocketError);
        }
    }

    private void ProcessSend(SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {                
            var socket = e.UserToken as Socket;

            if (OnDataSent != null)
                OnDataSent(clientArgs.Buffer);

            clientDataSent.Set();
            clientDataReceived.Reset();

            bool willRaiseEvent = socket.ReceiveAsync(e);
            if (!willRaiseEvent)
                ProcessReceive(e);

            clientDataReceived.WaitOne();
        }
        else
        {
            throw new SocketException((int)e.SocketError);
        }
    }


    public void Stop()
    {            
        client.Shutdown(SocketShutdown.Send);
        client.Close();
        client.Dispose();
        clientArgs.Dispose();           
    }

    public void Write(byte[] data)
    {
        clientDataSent.Reset();

        clientArgs.SetBuffer(data, 0, data.Length);

        bool willRaiseEvent = client.SendAsync(clientArgs);
        if (!willRaiseEvent)
            ProcessSend(clientArgs);

        clientDataSent.WaitOne();
    }
}

The idea here is that every request (send data) is always answered by a response (receive data), and it works fine as long as you do not disconnect and create a new connection.

For example:

client.Connect();
client.ClearConfiguration(1);
var status = client.RequestStatusDetails(1);
client.Disconnect();

This code sends multiple requests and receives an answer to each of them. However, if you run the same code again (or in a loop), the connection is established but as soon as the code reaches this point:

public void Write(byte[] data)
{
    clientDataSent.Reset();

    clientArgs.SetBuffer(data, 0, data.Length);

    bool willRaiseEvent = client.SendAsync(clientArgs);
    if (!willRaiseEvent)
        ProcessSend(clientArgs);

    clientDataSent.WaitOne();
}

An exception will be thrown for client.SendAsync(clientArgs);

This is the exception:

An asynchronous socket operation is already in progress using this SocketAsyncEventArgs instance

If however you put a breakpoint just before this statement, let VS2010 break on it, then continue debugging it works fine.

I really can't figure out what's causing this problem, and there is no additional information.

Any suggestions?

+1  A: 

Decided to put my comments as an answer.

IMHO the AutoResetEvent Class is better suited for your needs.

AutoResetEvent clientDataSent = new AutoResetEvent(true);

public void Write(byte[] data)
{
    // Wait till the Write operation gets a green light to proceed. Consider using a timeout.
    clientDataSent.WaitOne();

    clientArgs.SetBuffer(data, 0, data.Length);

    bool willRaiseEvent = client.SendAsync(clientArgs);

    // Write operation will get a signal either from ProcessSend (sync) or clientArgs_Completed (async),
    if (!willRaiseEvent) ProcessSend(clientArgs);
}

void clientArgs_Completed(object sender, SocketAsyncEventArgs e)
{
    bool throwInvalidOperationException = false;

    switch (e.LastOperation)
    {
        ...
        default:
            throwInvalidOperationException = true;
    }

    //Signal a waiting Write operation that it can proceed.
    clientDataSent.Set();

    if (throwInvalidOperationException) throw new Exception("Invalid operation completed");
}
Jaroslav Jandek
I'm sorry, I must be missing something. The clientDataSent initialState is set to True, which means .WaitOne() will not block, so it doesn't do much in this example... (I guess)
TimothyP
Lol, I'm still not getting it. Shouldn't the initialState be true and the .WaitOne() come after the SendAsync ?
TimothyP
+1 because it helped me solve the problem, although the order of the calls is different (feel free to comment)
TimothyP
@TimothyP: the initial state is `true` so the first call to `Write` passes the `WaitOne` and then blocks all the following calls until it's signalled (when completed).
Jaroslav Jandek
A: 

Using the AutoResetEvent as Jaroslav Jandek suggested seems to have solved my problem. Although if any of you have suggestions on how to improve this code, please feel free to do so.

public class TcpDataTransportClient : IDataTransportService
{
    private const string TCP_ADDRESS_SETTING = "tcpaddress";
    private const string TCP_PORT_SETTING = "tcpport";

    private Dictionary<string, object> settings = new Dictionary<string, object>();
    private IDataEncapsulator dataEncapsulator;
    private IDataCollector dataCollector;

    private Socket client;
    private SocketAsyncEventArgs clientArgs;

    public event DataReceivedHandler OnDataReceived;
    public event DataSentHandler OnDataSent;

    AutoResetEvent clientDataSent = new AutoResetEvent(false);
    AutoResetEvent clientConnected = new AutoResetEvent(false);

    public TcpDataTransportClient()
    {

    }

    public Dictionary<string, object> Settings
    {
        get
        {
            return this.settings;
        }
        set
        {
            this.settings = value;
        }
    }

    public IDataEncapsulator DataEncapsulator
    {
        get
        {
            return this.dataEncapsulator;
        }
        set
        {
            this.dataEncapsulator = value;
        }
    }

    public void Start(IDataCollector dataCollector)
    {
        this.dataCollector = dataCollector;
        clientArgs = new SocketAsyncEventArgs();

        client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
        clientArgs.Completed += clientArgs_Completed;
        clientArgs.UserToken = client;            
        clientArgs.RemoteEndPoint = GetIPEndPoint();

        client.ConnectAsync(clientArgs);
        clientConnected.WaitOne();            
    }

    private IPEndPoint GetIPEndPoint()
    {
        IPAddress ipAddress;
        int tcpPort;

        if (!IPAddress.TryParse(settings[TCP_ADDRESS_SETTING].ToString(), out ipAddress))
            throw new ArgumentException(String.Format("Invalid setting for IP Address: '{0}'", TCP_ADDRESS_SETTING));

        if (!int.TryParse(settings[TCP_PORT_SETTING].ToString(), out tcpPort))
            throw new ArgumentException(String.Format("Invalid setting for TCP Port: '{0}'", TCP_PORT_SETTING));

        return new IPEndPoint(ipAddress, tcpPort);
    }

    void clientArgs_Completed(object sender, SocketAsyncEventArgs e)
    {
        switch (e.LastOperation)
        {
            case SocketAsyncOperation.Connect:
                ProcessConnect(e);
                break;
            case SocketAsyncOperation.Receive:
                ProcessReceive(e);
                break;
            case SocketAsyncOperation.Send:
                ProcessSend(e);
                break;
            default:
                throw new Exception("Invalid operation completed");
        }
    }

    private void ProcessConnect(SocketAsyncEventArgs e)
    {
        if (e.SocketError != SocketError.Success)
        {
            throw new SocketException((int)e.SocketError);
        }
        else
        {
            clientConnected.Set();
        }
    }

    private void ProcessReceive(SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {
            var socket = e.UserToken as Socket;

            var response = dataCollector.Collect(e.Buffer);

            if (response != null)
            {
                if (this.OnDataReceived != null)
                    this.OnDataReceived(response);
            }
            else
            {
                bool willRaiseEvent = socket.ReceiveAsync(clientArgs);
                if (!willRaiseEvent)
                    ProcessReceive(e);
            }
        }
        else
        {
            throw new SocketException((int)e.SocketError);
        }
    }

    private void ProcessSend(SocketAsyncEventArgs e)
    {
        if (e.SocketError == SocketError.Success)
        {                
            var socket = e.UserToken as Socket;

            if (OnDataSent != null)
                OnDataSent(clientArgs.Buffer);

            bool willRaiseEvent = socket.ReceiveAsync(e);
            if (!willRaiseEvent)
                ProcessReceive(e);

            clientDataSent.Set();
        }
        else
        {
            throw new SocketException((int)e.SocketError);
        }
    }


    public void Stop()
    {            
        client.Shutdown(SocketShutdown.Send);
        client.Close();
        client.Dispose();
        clientArgs.Dispose();           
    }

    public void Write(byte[] data)
    {          
        clientArgs.SetBuffer(data, 0, data.Length);

        bool willRaiseEvent = client.SendAsync(clientArgs);
        if (!willRaiseEvent)
            ProcessSend(clientArgs);

        clientDataSent.WaitOne();
    }
}

Now I can disconnect and connect as many times I want. But first I call the SendAsync() which to my understanding will send the data in the background (most of the time) and the next call is then the .WaitOne() where it blocks the thread until the data has actually been sent. The same goes for connecting.

TimothyP