2011-11-01 12 views
7

Mam aplikację, która używa SslStream do wysyłania i odbierania danych z własną ramką o stałej długości. Strumień jest tworzony przez owijanie NetworkStream wrócił z TcpClient.GetStream() tak:Jakiego poziomu bezpieczeństwa wątku mogę się spodziewać w System.Net.Security.SslStream?

var client = new TcpClient(); 
client.Connect(host, port); 
var sslStream = new SslStream(client.GetStream(), false, callback, null); 
sslStream.AuthenticateAsClient(hostname); 

Ponieważ protokół jest całkowicie asynchroniczny (oprawione „wiadomości” przybyć w dowolnych godzinach, a klient może wysłać je w dowolnych godzinach), I normalnie spawnuje wątek odpowiedzialny za blokowanie na NetworkStream.Read() iw inny sposób zapewnia, że ​​istnieje tylko jeden wątek wywołujący NetworkStream.Write(...) w tym samym czasie.

Sekcja Remarks dla NetworkStream mówi:

operacje odczytu i zapisu mogą być wykonywane jednocześnie na instancji klasy NetworkStream bez potrzeby synchronizacji. Dopóki istnieje jeden unikalny wątek dla operacji zapisu i jeden unikalny wątek dla operacji odczytu, nie będzie interferencji między wątkami odczytu i zapisu i nie jest wymagana żadna synchronizacja.

Jednak Sekcja MSDN documentation "Bezpieczeństwo wątków" dla SslStream mówi:

Wszystkie publiczne statyczny (Shared w języku Visual Basic) elementy członkowskie tego typu są bezpieczne dla wątków . Nie można zagwarantować, że żaden element instancji nie będzie bezpieczny dla wątków .

Ponieważ SslStream i NetworkStream nie są w tej samej hierarchii klas, mam przyjąć, (być może błędnie), że uwagi dotyczące NetworkStream nie dotyczą SslStream.

Czy to jest najlepsze podejście do bezpieczeństwa wątków, aby po prostu owinąć SslStream.BeginRead/SslStream.EndRead i SslStream.BeginWrite/SslStream.EndWrite z czymś takim?

internal sealed class StateObject 
{ 
    private readonly ManualResetEvent _done = new ManualResetEvent(false); 

    public int BytesRead { get; set; } 
    public ManualResetEvent Done { get { return _done; } } 
} 

internal sealed class SafeSslStream 
{ 
    private readonly object _streamLock = new object(); 
    private readonly SslStream _stream; 

    public SafeSslStream(SslStream stream) 
    { 
     _stream = stream; 
    } 

    public int Read(byte[] buffer, int offset, int count) 
    { 
     var state = new StateObject(); 
     lock (_streamLock) 
     { 
      _stream.BeginRead(buffer, offset, count, ReadCallback, state); 
     } 
     state.Done.WaitOne(); 
     return state.BytesRead; 
    } 

    public void Write(byte[] buffer, int offset, int count) 
    { 
     var state = new StateObject(); 
     lock (_streamLock) 
     { 
      _stream.BeginWrite(buffer, offset, count, WriteCallback, state); 
     } 
     state.Done.WaitOne(); 
    } 

    private void ReadCallback(IAsyncResult ar) 
    { 
     var state = (StateObject)ar.AsyncState; 
     lock (_streamLock) 
     { 
      state.BytesRead = _stream.EndRead(ar); 
     } 
     state.Done.Set(); 
    } 

    private void WriteCallback(IAsyncResult ar) 
    { 
     var state = (StateObject)ar.AsyncState; 
     lock (_streamLock) 
     { 
      _stream.EndWrite(ar); 
     } 
     state.Done.Set(); 
    } 
} 
+0

Drobne szczegóły: zablokowanie _stream będzie działało, ale zaleca się utworzenie i użycie osobnego obiektu do tego celu. –

+0

@HenkHolterman Wiwaty za poradę; Zaktualizowałem kod. –

+0

FWIW: Dysk [SafeSsLStream] (https://github.com/smarkets/IronSmarkets/blob/master/IronSmarkets/Sockets/SafeSslStream.cs) jest przeznaczony dla [IronSmarkets] (https://github.com/smarkets/IronSmarkets). –

Odpowiedz

3

Wyrażenie, które wyciągnąłeś z dokumentów MSDN, jest zapisem umieszczonym w dokumentacji większości klas. Jeśli dokumentacja użytkownika jawnie określa bezpieczeństwo wątku (jako NetworkStream), możesz na nim polegać.

Jednakże, stwierdzili, że można wykonać jedno czytanie i jedno pisać w tym samym czasie, a nie dwa odczyty lub dwa zapisy. W związku z tym trzeba zsynchronizować lub kolejkować swoje odczyty i zapisy oddzielnie. Twój kod wygląda na wystarczający.

+0

Wciąż nie jestem pewien, czy uwagi pojawiają się tylko w 'NetworkStream', a' SslStream' sam nie dziedziczy po 'NetworkStream', ale zamiast tego wydaje się owijać go, gdy jest używany z' TcpClient'. –

+0

Ponieważ Twój SslStream będzie owijał obiekt NetworkStream, otrzymasz niejawne bezpieczeństwo wątku w operacjach odczytu i zapisu niezależnie. Gdyby tak nie było, SSL byłby półdupleksowym (który nie jest). Możesz po prostu owinąć czytać i pisać w swoich własnych zamkach i będzie to bezpieczne. – Polynomial

+1

Wystarczy dodać trochę precyzji: podczas gdy większość SSL jest w trybie pełnego dupleksu, uściski dłoni wymagają synchronizacji instrukcji odczytu i zapisu; a klient i serwer mogą zażądać ponownego uzgadniania w dowolnym momencie. Zatem możliwość wykonywania odczytów w jednym wątku i zapisywania w drugim, bez synchronizacji, nie jest oczywistą konsekwencją bezpieczeństwa wątków w NetworkStream. Na szczęście implementacja SslStream przez Microsoft zawiera niezbędne wewnętrzne blokady, które umożliwiają taką operację na dwóch bitach (nie sprawdziłem jednak Mono). –

Powiązane problemy