2010-01-24 23 views
7

Ciao a tutti,TCPIP rete con C#

ho intenzione di scrivere un codice che deve ascoltare i messaggi TCPIP provenienti da telefoni cellulari GSM con GPRS. Nella pienezza dei tempi, vedo che questo è in esecuzione su un server virtuale privato e potrebbe elaborare più messaggi al secondo.

Sono un po 'un programmatore di rete vergine, quindi ho fatto un po' di ricerche su internet e ho letto alcuni tutorial. L'approccio che sto prendendo in considerazione al momento è un servizio Windows che utilizza socket per monitorare la porta. Se la mia comprensione è corretta, ho bisogno di un socket per ascoltare le connessioni dai client, e ogni volta che qualcuno cerca di connettersi con la porta mi verrà passato un altro socket con cui comunicare con loro? Suona bene alle orecchie più esperte?

Sto progettando di utilizzare la comunicazione asincrona, ma sulle domande di progettazione più grandi è se utilizzare la filettatura o meno. Il threading non è qualcosa con cui ho giocato davvero, e sono consapevole di una serie di insidie: condizioni di gara e problemi di debug sono solo due.

Se evito i thread, so che devo fornire un oggetto che funge da identificatore per una determinata conversazione. Stavo pensando GUID per questo - qualche opinione?

Grazie in anticipo per eventuali risposte ...

Martin

+0

Probabilmente avrete bisogno di thread (o pool di thread) per servire la richiesta dai client. Almeno, questa era la mia esperienza quando lavoravo con la scoperta dei servizi. – jasonco

+0

No, non lo facciamo. A partire dall'implementazione del socket SP1 .net framework 2.0 basato su porte di copletion IO. E questo approccio MOLTO PIÙ efficiente dell'utilizzo di un thread per una connessione in ingresso. Non abbiamo bisogno di eventi Thread Pool. (vedi la mia risposta per maggiori informazioni). –

risposta

8

A partire da .NET Framework 2.0 SP1 ci sono alcuni mutamenti nelle librerie di socket relativi ai socket asincroni.

Tutto il multithreading utilizzato sotto il cofano. Non è necessario utilizzare il multithreading manualmente (non è necessario utilizzare esplicitamente ThreadPool). Tutto quello che facciamo - utilizzando BeginAcceptSocket per iniziare ad accettare nuove connessioni e usare SocketAsyncEventArgs dopo aver accettato una nuova connessione.

Breve implementazione:

//In constructor or in method Start 
var tcpServer = new TcpListener(IPAddress.Any, port); 
tcpServer.Start(); 
tcpServer.BeginAcceptSocket(EndAcceptSocket, tcpServer); 

//In EndAcceptSocket 
Socket sock= lister.EndAcceptSocket(asyncResult); 
var e = new SocketAsyncEventArgs(); 
e.Completed += ReceiveCompleted; //some data receive handle 
e.SetBuffer(new byte[SocketBufferSize], 0, SocketBufferSize); 
if (!sock.ReceiveAsync(e)) 
{//IO operation finished syncronously 
    //handle received data 
    ReceiveCompleted(sock, e); 
}//IO operation finished syncronously 
//Add sock to internal storage 

piena attuazione:

using System; 
using System.Collections.Generic; 
using System.Net; 
using System.Net.Sockets; 
using System.Runtime.InteropServices; 

namespace Ample 
{ 
    public class IPEndPointEventArgs : EventArgs 
    { 
     public IPEndPointEventArgs(IPEndPoint ipEndPoint) 
     { 
      IPEndPoint = ipEndPoint; 
     } 

     public IPEndPoint IPEndPoint { get; private set; } 
    } 

    public class DataReceivedEventArgs : EventArgs 
    { 
     public DataReceivedEventArgs(byte[] data, IPEndPoint ipEndPoint) 
     { 
      Data = data; 
      IPEndPoint = ipEndPoint; 
     } 

     public byte[] Data { get; private set; } 
     public IPEndPoint IPEndPoint { get; private set; } 

    } 
    /// <summary> 
    /// TcpListner wrapper 
    /// Encapsulates asyncronous communications using TCP/IP. 
    /// </summary> 
    public sealed class TcpServer : IDisposable 
    { 
     //---------------------------------------------------------------------- 
     //Construction, Destruction 
     //---------------------------------------------------------------------- 
     /// <summary> 
     /// Creating server socket 
     /// </summary> 
     /// <param name="port">Server port number</param> 
     public TcpServer(int port) 
     { 
      connectedSockets = new Dictionary<IPEndPoint, Socket>(); 
      tcpServer = new TcpListener(IPAddress.Any, port); 
      tcpServer.Start(); 
      tcpServer.BeginAcceptSocket(EndAcceptSocket, tcpServer); 
     } 
     ~TcpServer() 
     { 
      DisposeImpl(false); 
     } 
     public void Dispose() 
     { 
      DisposeImpl(true); 
     } 

     //---------------------------------------------------------------------- 
     //Public Methods 
     //---------------------------------------------------------------------- 

     public void SendData(byte[] data, IPEndPoint endPoint) 
     { 
      Socket sock; 
      lock (syncHandle) 
      { 
       if (!connectedSockets.ContainsKey(endPoint)) 
        return; 
       sock = connectedSockets[endPoint]; 
      } 
      sock.Send(data); 
     } 

     //---------------------------------------------------------------------- 
     //Events 
     //---------------------------------------------------------------------- 
     public event EventHandler<IPEndPointEventArgs> SocketConnected; 
     public event EventHandler<IPEndPointEventArgs> SocketDisconnected; 
     public event EventHandler<DataReceivedEventArgs> DataReceived; 

     //---------------------------------------------------------------------- 
     //Private Functions 
     //---------------------------------------------------------------------- 
     #region Private Functions 
     //Обработка нового соединения 
     private void Connected(Socket socket) 
     { 
      var endPoint = (IPEndPoint)socket.RemoteEndPoint; 

      lock (connectedSocketsSyncHandle) 
      { 
       if (connectedSockets.ContainsKey(endPoint)) 
       { 
        theLog.Log.DebugFormat("TcpServer.Connected: Socket already connected! Removing from local storage! EndPoint: {0}", endPoint); 
        connectedSockets[endPoint].Close(); 
       } 

       SetDesiredKeepAlive(socket); 
       connectedSockets[endPoint] = socket; 
      } 

      OnSocketConnected(endPoint); 
     } 

     private static void SetDesiredKeepAlive(Socket socket) 
     { 
      socket.SetSocketOption(SocketOptionLevel.Socket, SocketOptionName.KeepAlive, true); 
      const uint time = 10000; 
      const uint interval = 20000; 
      SetKeepAlive(socket, true, time, interval); 
     } 
     static void SetKeepAlive(Socket s, bool on, uint time, uint interval) 
     { 
      /* the native structure 
      struct tcp_keepalive { 
      ULONG onoff; 
      ULONG keepalivetime; 
      ULONG keepaliveinterval; 
      }; 
      */ 

      // marshal the equivalent of the native structure into a byte array 
      uint dummy = 0; 
      var inOptionValues = new byte[Marshal.SizeOf(dummy) * 3]; 
      BitConverter.GetBytes((uint)(on ? 1 : 0)).CopyTo(inOptionValues, 0); 
      BitConverter.GetBytes((uint)time).CopyTo(inOptionValues, Marshal.SizeOf(dummy)); 
      BitConverter.GetBytes((uint)interval).CopyTo(inOptionValues, Marshal.SizeOf(dummy) * 2); 
      // of course there are other ways to marshal up this byte array, this is just one way 

      // call WSAIoctl via IOControl 
      int ignore = s.IOControl(IOControlCode.KeepAliveValues, inOptionValues, null); 

     } 
     //socket disconnected handler 
     private void Disconnect(Socket socket) 
     { 
      var endPoint = (IPEndPoint)socket.RemoteEndPoint; 

      lock (connectedSocketsSyncHandle) 
      { 
       connectedSockets.Remove(endPoint); 
      } 

      socket.Close(); 

      OnSocketDisconnected(endPoint); 
     } 

     private void ReceiveData(byte[] data, IPEndPoint endPoint) 
     { 
      OnDataReceived(data, endPoint); 
     } 

     private void EndAcceptSocket(IAsyncResult asyncResult) 
     { 
      var lister = (TcpListener)asyncResult.AsyncState; 
      theLog.Log.Debug("TcpServer.EndAcceptSocket"); 
      if (disposed) 
      { 
       theLog.Log.Debug("TcpServer.EndAcceptSocket: tcp server already disposed!"); 
       return; 
      } 

      try 
      { 
       Socket sock; 
       try 
       { 
        sock = lister.EndAcceptSocket(asyncResult); 
        theLog.Log.DebugFormat("TcpServer.EndAcceptSocket: remote end point: {0}", sock.RemoteEndPoint); 
        Connected(sock); 
       } 
       finally 
       { 
        //EndAcceptSocket can failes, but in any case we want to accept 
new connections 
        lister.BeginAcceptSocket(EndAcceptSocket, lister); 
       } 

       //we can use this only from .net framework 2.0 SP1 and higher 
       var e = new SocketAsyncEventArgs(); 
       e.Completed += ReceiveCompleted; 
       e.SetBuffer(new byte[SocketBufferSize], 0, SocketBufferSize); 
       BeginReceiveAsync(sock, e); 

      } 
      catch (SocketException ex) 
      { 
       theLog.Log.Error("TcpServer.EndAcceptSocket: failes!", ex); 
      } 
      catch (Exception ex) 
      { 
       theLog.Log.Error("TcpServer.EndAcceptSocket: failes!", ex); 
      } 
     } 

     private void BeginReceiveAsync(Socket sock, SocketAsyncEventArgs e) 
     { 
      if (!sock.ReceiveAsync(e)) 
      {//IO operation finished syncronously 
       //handle received data 
       ReceiveCompleted(sock, e); 
      }//IO operation finished syncronously 
     } 

     void ReceiveCompleted(object sender, SocketAsyncEventArgs e) 
     { 
      var sock = (Socket)sender; 
      if (!sock.Connected) 
       Disconnect(sock); 
      try 
      { 

       int size = e.BytesTransferred; 
       if (size == 0) 
       { 
        //this implementation based on IO Completion ports, and in this case 
        //receiving zero bytes mean socket disconnection 
        Disconnect(sock); 
       } 
       else 
       { 
        var buf = new byte[size]; 
        Array.Copy(e.Buffer, buf, size); 
        ReceiveData(buf, (IPEndPoint)sock.RemoteEndPoint); 
        BeginReceiveAsync(sock, e); 
       } 
      } 
      catch (SocketException ex) 
      { 
       //We can't truly handle this excpetion here, but unhandled 
       //exception caused process termination. 
       //You can add new event to notify observer 
       theLog.Log.Error("TcpServer: receive data error!", ex); 
      } 
      catch (Exception ex) 
      { 
       theLog.Log.Error("TcpServer: receive data error!", ex); 
      } 
     } 

     private void DisposeImpl(bool manualDispose) 
     { 
      if (manualDispose) 
      { 
       //We should manually close all connected sockets 
       Exception error = null; 
       try 
       { 
        if (tcpServer != null) 
        { 
         disposed = true; 
         tcpServer.Stop(); 
        } 
       } 
       catch (Exception ex) 
       { 
        theLog.Log.Error("TcpServer: tcpServer.Stop() failes!", ex); 
        error = ex; 
       } 

       try 
       { 
        foreach (var sock in connectedSockets.Values) 
        { 
         sock.Close(); 
        } 
       } 
       catch (SocketException ex) 
       { 
        //During one socket disconnected we can faced exception 
        theLog.Log.Error("TcpServer: close accepted socket failes!", ex); 
        error = ex; 
       } 
       if (error != null) 
        throw error; 
      } 
     } 


     private void OnSocketConnected(IPEndPoint ipEndPoint) 
     { 
      var handler = SocketConnected; 
      if (handler != null) 
       handler(this, new IPEndPointEventArgs(ipEndPoint)); 
     } 

     private void OnSocketDisconnected(IPEndPoint ipEndPoint) 
     { 
      var handler = SocketDisconnected; 
      if (handler != null) 
       handler(this, new IPEndPointEventArgs(ipEndPoint)); 
     } 
     private void OnDataReceived(byte[] data, IPEndPoint ipEndPoint) 
     { 
      var handler = DataReceived; 
      if (handler != null) 
       handler(this, new DataReceivedEventArgs(data, ipEndPoint)); 
     } 

     #endregion Private Functions 

     //---------------------------------------------------------------------- 
     //Private Fields 
     //---------------------------------------------------------------------- 
     #region Private Fields 
     private const int SocketBufferSize = 1024; 
     private readonly TcpListener tcpServer; 
     private bool disposed; 
     private readonly Dictionary<IPEndPoint, Socket> connectedSockets; 
     private readonly object connectedSocketsSyncHandle = new object(); 
     #endregion Private Fields 
    } 
} 
+0

Grazie per questo Sergey - Sto leggendo la tua fonte ora per fare i conti con esso. Sono uno di quei tipi a cui piace capire le cose piuttosto che copiare copia incolla ... –

+0

Penso che questo sia il modo in cui andrò, usando la porta di I/O. Grazie per aver fornito il codice completo, ma non voglio semplicemente tagliare e incollare. Come ho detto prima, mi piace capire perché qualcosa funzioni, quindi implementerò la mia classe server usando il tuo come riferimento. Saluti! –

+0

Per ulteriori informazioni sull'API Winsocks (e utilizzando le porte di completamento I/O) ho consigliato questo libro: "Programmazione di rete per Microsoft Windows", Seconda edizione (uno dei migliori libri sulla programmazione di rete in Windows) e Windows tramite C/C++ di Jeffrey Richter (uno dei migliori libri su Win32, mulithreading e porte di completamento IO). Sono felice di poterti aiutare! –

3

è sorprendentemente semplice per fare un server multi-threaded. Guarda questo esempio.

class Server 
{ 
    private Socket socket; 
    private List<Socket> connections; 
    private volatile Boolean endAccept; 

    // glossing over some code. 


    /// <summary></summary> 
    public void Accept() 
    { 
     EventHandler<SocketAsyncEventArgs> completed = null; 
     SocketAsyncEventArgs args = null; 

     completed = new EventHandler<SocketAsyncEventArgs>((s, e) => 
     { 
      if (e.SocketError != SocketError.Success) 
      { 
       // handle 
      } 
      else 
      { 
       connections.Add(e.AcceptSocket); 
       ThreadPool.QueueUserWorkItem(AcceptNewClient, e.AcceptSocket); 
      } 

      e.AcceptSocket = null; 
      if (endAccept) 
      { 
       args.Dispose(); 
      } 
      else if (!socket.AcceptAsync(args)) 
      { 
       completed(socket, args); 
      } 
     }); 

     args = new SocketAsyncEventArgs(); 
     args.Completed += completed; 

     if (!socket.AcceptAsync(args)) 
     { 
      completed(socket, args); 
     } 
    } 

    public void AcceptNewClient(Object state) 
    { 
     var socket = (Socket)state; 
     // proccess   
    }   
} 
+0

Grazie per la risposta ChoasPandion ... Sfortunatamente sto uscendo di casa ora, ma darò un'occhiata a questo più tardi. Funzioni lamda eh? Sto ancora imparando la sintassi ... –

+0

Fammi sapere se qualcosa non ha senso e mi dilungherò su di esso. – ChaosPandion

+0

Possiamo invece utilizzare BeginAcceptSocket. –

0

Bene, la # sintassi del C non è fresco nella mia mente ora, ma non credo che è di molto diverso dallo standard Posix.

Ciò che si può fare è quando si crea il socket di ascolto è possibile stipulare un valore per il backlog (numero massimo di connessioni simultanee per quel server) e creare un thread pull con le stesse dimensioni. I pool di thread sono più facili da utilizzare rispetto a quelli tradizionali. Il TCP si accoda per tutte le connessioni sopra il parametro del backlog.

+0

Grazie per l'input Andres ... –

1

Un po 'di consigli da parte del ragazzo che si occupa principalmente di rete mobile: fai i compiti con una regolare connessione di rete, preferibilmente sul localhost. Questo ti farà risparmiare un sacco di tempo durante i test e ti manterrà sano di mente finché non capirai l'approccio che funziona meglio per te.

Come per un'implementazione particolare, utilizzo sempre socket sincronizzati (è necessario configurare i timeout per non rimanere bloccati se qualcosa andrà storto) e tutto viene eseguito in thread separati sincronizzati con l'aiuto di eventi. È molto più semplice di quanto pensi. Ecco alcuni link utili per iniziare:

+0

Cheers - Aggiungo alla lista delle cose da guardare ... Chi ha pensato che sarebbe stato così divertente, eh? –

1

Sto scrivendo la stessa applicazione in questo momento e usare soluzione di questo tipo:

http://clutch-inc.com/blog/?p=4

E ' è stato testato in questo momento e funziona perfettamente. È importante rendere questo servizio solo per ricevere e archiviare messaggi (da qualche parte) senza altro lavoro. Sto usando NServiceBus per il salvataggio dei messaggi. L'altro servizio prende i messaggi dalla coda e fa il resto.

+0

Grazie Dario - Vedrò anche quello. –