2010-01-24 26 views
7

HI everyone,red TCPIP con C#

Voy a escribir un código que debe escuchar los mensajes TCPIP procedentes de teléfonos móviles GSM a través de GPRS. En el transcurso del tiempo, veo que esto se ejecuta en un Servidor Virtual Privado, y bien podría estar procesando múltiples mensajes cada segundo.

Soy un poco virgen de la programación en red, así que investigué un poco en Internet y leí algunos tutoriales. El enfoque que estoy considerando en este momento es un servicio de Windows que use sockets para monitorear el puerto. Si mi comprensión es correcta, necesito un socket para escuchar las conexiones de los clientes, y cada vez que alguien intente conectarse con el puerto, ¿se me pasará otro socket para comunicarme con ellos? ¿Esto suena correcto para oídos más experimentados?

Estoy planeando usar comunicación asíncrona, pero una de las cuestiones de diseño más grandes es si usar el enhebrado o no. Enhebrar no es algo con lo que realmente haya jugado, y soy consciente de una serie de riesgos: las condiciones de carrera y los problemas de depuración son solo dos.

Si evito los hilos, sé que tengo que proporcionar un objeto que actúa como identificador para una conversación en particular. Estaba pensando GUIDs para esto, ¿alguna opinión?

Gracias de antemano por cualquier respuesta ...

Martin

+0

Probablemente necesitará hilos (o grupos de hilos) para la solicitud de servicio de los clientes. Al menos, esa fue mi experiencia al trabajar con el descubrimiento de servicios. – jasonco

+0

No, no es así. A partir de la implementación de socket .net framework 2.0 SP1 basada en puertos de cople IO. Y este enfoque MUCHO MÁS eficiente que usar un hilo para una conexión entrante. No necesitamos eventos Thread Pool. (ver mi respuesta para más información). –

Respuesta

8

A partir de .NET Framework 2.0 SP1 hay algunas changings en las bibliotecas de socket relacionados con enchufes asíncronos.

Todos los multi hilos utilizados debajo del capó. No es necesario utilizar el subprocesamiento de forma manual (no es necesario usar incluso ThreadPool explícitamente). Todo lo que hacemos - usando BeginAcceptSocket para comenzar a aceptar nuevas conexiones, y usando SocketAsyncEventArgs después de aceptar una nueva conexión.

aplicación corta:

//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 

aplicación completa:

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

Gracias por este Sergey - Estoy leyendo su fuente ahora para entenderlo. Soy uno de esos tipos a los que les gusta entender las cosas en lugar de simplemente copiar pegar ... –

+0

Creo que esta es la forma en que voy a ir - usando el puerto de terminación IO. Gracias por proporcionar el código completo, pero no voy a cortar y pegar simplemente. Como dije antes, me gusta entender por qué algo funciona, así que implementaré mi propia clase de servidor usando la suya para referencia. ¡Salud! –

+0

Para obtener más información sobre Winsocks API (y uso de puertos de terminación IO), recomendé este libro: "Programación de red para Microsoft Windows", Segunda edición (uno de los mejores libros sobre programación de red en Windows) y Windows vía C/C++ de Jeffrey Richter (uno de los mejores libros sobre Win32, mulithreading y puertos de terminación de E/S). ¡Me alegro de poder ayudar! –

3

Es sorprendentemente fácil de hacer que un servidor multi-hilo. Mira este ejemplo.

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

Gracias por la respuesta ChoasPandion ... Lamentablemente estoy de camino a la salida de la casa, pero lo veré más adelante. Funciones de Lamda eh? Aún estoy entendiendo la sintaxis ... –

+0

Solo avíseme si algo no tiene sentido y lo ampliaré. – ChaosPandion

+0

Podemos usar BeginAcceptSocket en su lugar. –

0

Bueno, la sintaxis de C# no es fresco en mi mente ahora, pero no creo que es muy diferente del estándar POSIX.

Lo que puede hacer es cuando crea su socket de escucha puede estipular un valor para la acumulación (número máximo de conexiones simultáneas para ese servidor) y crear una extracción de hilo con el mismo tamaño. Los grupos de subprocesos son más fáciles de usar que los tradicionales. El TCP le pone en cola todas las conexiones encima del parámetro de retraso acumulado.

+0

Gracias por la entrada de Andres ... –

1

Un poco de asesoramiento del tipo que se ocupa principalmente de las redes móviles: haga su tarea con una conexión de red normal, preferiblemente en el host local. Esto le ahorrará mucho tiempo durante las pruebas y lo mantendrá sano hasta que descubra cuál es el enfoque que mejor funciona para usted.

En cuanto a una implementación particular, siempre voy con enchufes sincronizados (necesitará configurar tiempos de espera para no atascarse si algo va mal) y todo se ejecuta en hilos separados que se sincronizan con la ayuda de eventos. Es mucho más simple de lo que crees. He aquí algunos enlaces útiles para empezar:

+0

Saludos - Añadiré a la lista de cosas para mirar ... ¿Quién pensó que esto sería tan divertido, eh? –

1

estoy escribiendo la misma aplicación en este momento y utilizar la solución de la siguiente manera:

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

¡Es ha sido probado en este momento y funciona perfectamente. Es importante hacer este servicio solo para recibir y almacenar mensajes (en algún lugar) sin otro trabajo. Estoy usando NServiceBus para guardar mensajes. Otro servicio toma mensajes de la cola y hace el resto.

+0

Gracias Darío - Voy a investigar ese también. –