2010-08-03 1 views
1

Silverlight 4에서 소켓을 사용하여 데이터를주고받는 클래스가 있습니다. 일부 인터페이스는 기존 인터페이스를 구현해야하므로 다소 이상하게 보일 수 있지만 는 여기있다 :SL4 AsyncEventArgs가 두 번째 소켓 연결 후 InvalidOperationException을 throw합니다.

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(); 
    } 
} 

여기 아이디어는 모든 요청 (데이터를 전송)는 항상 응답 (데이터를 수신) 응답되어, 당신은 분리하고 새로운 연결을 생성하지 않는 한 그것만큼 잘 작동 . 예를 들어

:

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

이 코드는 여러 개의 요청을 전송하고 각에 대한 답변을받습니다. 당신이 (또는 루프) 다시 동일한 코드를 실행하는 경우 그러나 연결이 확립되어 있지만 곧 코드와이 지점에 도달 :

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

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

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

    clientDataSent.WaitOne(); 
} 
예외가 client.SendAsync에 대해 발생합니다

(clientArgs는); 그냥이 문 앞에 중단 점을 넣어하지만, 는 VS2010 휴식을 할 경우,이 SocketAsyncEventArgs 예를

를 사용하여 이미 진행

비동기 소켓 작업이 :

는 예외 그것에, 다음 디버깅 계속 작동합니다.

나는이 문제의 원인을 정확히 알 수 없으므로, 과 추가 정보가 없습니다.

제안 사항?

+0

거기에 와일드 록이 있습니다. 코드가 동시에 여러 요청을 보내는 것을 허용하지 않습니다. 각각의 비동기 요청에 대해이를 거부하거나 새로운'SocketAsyncEventArgs' 인스턴스를 생성하십시오. –

+0

오 ... 정말 여러 요청을 거부해야합니다, 내가 뭘 잘못하고 있는지 나타낼 수 있습니까? – TimothyP

+0

@TimothyP :'client.SendAsync'를 호출하기 전에'clientDataSent.WaitOne'을 사용해야합니다. 작업이 완료되면'clientDataSent.Set()'을 사용하십시오. 또한,'ManualResetEvent' 대신에'AutoResetEvent'를 사용하고, 작업이 전혀 완료되지 않을 경우 기다리는 타임 아웃을 사용하는 것을 고려하십시오. –

답변

0

Jaroslav Jandek이 제안한 AutoResetEvent를 사용하면 내 문제가 해결 된 것으로 보입니다. 이 코드를 개선하는 방법에 대한 제안 사항이 있으시면 언제든지 알려주십시오.

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(); 
    } 
} 

이제 원하는만큼 여러 번 연결을 끊고 연결할 수 있습니다. 하지만 먼저 SendAsync()를 호출하여 백그라운드에서 데이터를 보내고 (실제로는) 데이터가 실제로 전송 될 때까지 스레드를 차단하는 .WaitOne()을 호출합니다. 연결시에도 마찬가지입니다.

1

답변으로 의견을 말하기로했습니다.

IMHO AutoResetEvent Class이 사용자의 요구에 더 적합합니다.

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"); 
} 
+0

죄송합니다. 누락되었습니다. clientDataSent initialState는 True로 설정됩니다. 즉, .WaitOne()이 차단되지 않으므로이 예제에서는 많은 작업을 수행하지 않습니다 ... (추측) – TimothyP

+0

Lol, 아직 이해가 안갑니다. initialState가 true가 아니고 .WaitOne()이 SendAsync 뒤에 오는 것이 좋습니까? – TimothyP

+0

+1은 호출 순서가 다르더라도 문제를 해결할 수 있기 때문에 +1 – TimothyP