Отмена асинхронной операции с сокетом - PullRequest
1 голос
/ 26 января 2011

У меня есть приложение на C # .Net 3.5, которое отправляет многоадресный пакет «Hello» тому, кто может быть подписан на определенную группу многоадресной рассылки, а затем прослушивает все ответы.Таким образом, каждые X секунд я могу отправлять пакет «Hello» и отмечать всех, кто отвечает.

Он предназначен для использования следующим образом:

MulticastHello hello_ = new MulticastHello();

// alert our UI of any responses to the 'Hello'
hello_.ReceivedHelloResponse += OnHelloResponse;

// this timer function is triggered every X seconds
private void OnTimer(object sender, EventArgs e)
{
    // stop listening for responses to the last 'hello'
    hello_.CancelHello();

    // send a new 'hello' and start listening for responses
    hello_.SendHello("224.0.100.1");
}

К сожалению, яУ меня проблемы с отменой асинхронного чтения.Моя функция private void OnReceive(IAsyncResult ar) иногда выдает System.ArgumentException, который говорит: «Объект IAsyncResult не был возвращен из соответствующего асинхронного метода в этом классе.»

Как можно надежно отменить операцию асинхронного сокета.Или есть лучший способ сделать это?

Моя реализация ниже.

Спасибо, PaulH

public class HelloResponseEventArgs : EventArgs { /*...*/ }

public class MulticastHello : IDisposable
{
    public event EventHandler<HelloResponseEventArgs> ReceivedHelloResponse;

    private Socket socket_;

    private byte[] received_ = new byte[HelloResponse.Size];

    private EndPoint responder_ = new IPEndPoint(IPAddress.Any, 0);

    protected virtual void OnReceivedHelloResponse(HelloResponseEventArgs e)
    {
        EventHandler<HelloResponseEventArgs> evt = ReceivedHelloResponse;
        if (null != evt)
            evt(this, e);
    }

    private void OnReceive(IAsyncResult ar)
    {
        IPEndPoint ipendpoint = new IPEndPoint(IPAddress.Any, 0);
        EndPoint endpoint = ipendpoint as EndPoint;

        try
        {
            socket_.EndReceiveFrom(ar, ref endpoint);
        }
        catch (System.ObjectDisposedException)
        {
            // the read was canceled. This is expected.
            return;
        }

        // decode the response and set the event
        IPEndPoint remote = endpoint as IPEndPoint;
        HelloResponse response = new HelloResponse(Deserialize<HelloPacket>(received_));
        OnReceivedHelloResponse(new HelloResponseEventArgs(remote.Address.ToString(), response));

        // keep receiving responses until canceled
        socket_.BeginReceiveFrom(received_,
            0,
            received_.Length,
            SocketFlags.None,
            ref endpoint,
            new AsyncCallback(OnReceive),
            null);
    }

    // stop listening for responses to the hello frame
    public void CancelHello()
    {
        if (socket_ != null)
            socket_.Close();
    }

    // send an initial 'Hello' to the a multicast address. Start listening for responses
    public void SendHello(string address)
    {
        socket_ = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
        socket_.MulticastLoopback = false;
        socket_.Ttl = 255;

        HelloResponse send = new HelloResponse();
        byte[] data = Serialize(send.Packet);

        EndPoint remote = new IPEndPoint(IPAddress.Parse(address), 7);
        socket_.SendTo(data, remote);

        socket_.BeginReceiveFrom(received_,
            0,
            received_.Length,
            SocketFlags.None,
            ref responder_,
            new AsyncCallback(OnReceive),
            null);
    }

    #region IDisposable Members
    /* close the socket on dispose*/
    #endregion
}

Ответы [ 2 ]

3 голосов
/ 16 марта 2011

Отправить сокет как состояние arg в BeginReceive.Т.е.

socket_.BeginReceiveFrom(
         received_,
         0,
         received_.Length,
         SocketFlags.None,
         ref endpoint,
         new AsyncCallback(OnReceive),
         socket_); 

Тогда в OnReceive используйте это вместо сокета переменной класса.Т.е.

Socket socket = (Socket)ar.AsyncState;
socket.EndReceiveFrom(ar, ref endpoint); 

Это гарантирует, что вызов EndReceiveFrom происходит на том же сокете, на котором был вызван BeginReceive.Затем, как упоминает PaulH, просто перехватите исключение ObjectDisposedException в OnReceive, которое будет сигналом того, что Close () был вызван в сокете.

2 голосов
/ 15 февраля 2011

Close() не ждет.Это выходит немедленно.Таким образом, если следующий SendHello () наступит до того, как EndReceiveFrom () завершит работу, он выдаст System.ArgumentException.

. Решение состоит в том, чтобы ожидать объект события после вызова Close(), установленного при System.ObjectDisposedException пойман в OnReceive.

-PaulH

...