2011-10-04 14 views
8

Quiero escribir un servidor de socket simple, sin embargo, me gustaría que fuera escalable verticalmente, por ejemplo, no creando un hilo por conexión o tareas muy largas, que pueden consumir todos los hilos.¿Cómo escribo un servidor de socket escalable usando C# 4.0?

El servidor recibe una solicitud que contiene una consulta y transmite un resultado arbitrariamente grande.

Me gustaría la manera idiomática de hacer esto utilizando técnicas y bibliotecas que están disponibles en C# 4, con énfasis en el código simple, en lugar del rendimiento en bruto.

Reapertura Un servidor de socket es una parte útil de un sistema escalable. Si quiere escalar horizontalmente, hay diferentes técnicas. Probablemente no pueda responder esta pregunta si nunca ha creado un servidor de socket.

Respuesta

15

He estado trabajando en algo similar por una semana o dos ahora, así que espero poder ayudarte un poco.

Si su atención se centra en el código simple, recomendaría utilizar las clases TcpClient y TcpListener. Ambos hacen que los enchufes sean mucho más fáciles de trabajar. Si bien han existido desde .NET Framework 1.1, se han actualizado y siguen siendo su mejor opción.

En términos de cómo utilizar .NET Framework 4.0 para escribir código simplista, las tareas son lo primero que se le viene a la mente. Hacen que escribir código asíncrono sea mucho menos doloroso y será mucho más fácil migrar su código una vez que C# 5 salga (new async and await keywords). Aquí está un ejemplo de cómo las tareas pueden simplificar el código:

En lugar de utilizar tcpListener.BeginAcceptTcpClient(AsyncCallback callback, object state); y proporcionar un método de devolución de llamada que exigiría EndAcceptTcpClient(); y opcionalmente emitir su objeto de estado, C# 4 le permite utilizar dispositivos de cierre, lambdas, y tareas a realizar este proceso es mucho más legible y escalable. Aquí está un ejemplo:

private void AcceptClient(TcpListener tcpListener) 
{ 
    Task<TcpClient> acceptTcpClientTask = Task.Factory.FromAsync<TcpClient>(tcpListener.BeginAcceptTcpClient, tcpListener.EndAcceptTcpClient, tcpListener); 

    // This allows us to accept another connection without a loop. 
    // Because we are within the ThreadPool, this does not cause a stack overflow. 
    acceptTcpClientTask.ContinueWith(task => { OnAcceptConnection(task.Result); AcceptClient(tcpListener); }, TaskContinuationOptions.OnlyOnRanToCompletion); 
} 

private void OnAcceptConnection(TcpClient tcpClient) 
{ 
    string authority = tcpClient.Client.RemoteEndPoint.ToString(); // Format is: IP:PORT 

    // Start a new Task to handle client-server communication 
} 

FromAsync es muy útil ya que Microsoft ha proporcionado muchas sobrecargas que pueden simplificar las operaciones asincrónicas comunes. He aquí otro ejemplo:

private void Read(State state) 
{ 
    // The int return value is the amount of bytes read accessible through the Task's Result property. 
    Task<int> readTask = Task<int>.Factory.FromAsync(state.NetworkStream.BeginRead, state.NetworkStream.EndRead, state.Data, state.BytesRead, state.Data.Length - state.BytesRead, state, TaskCreationOptions.AttachedToParent); 

    readTask.ContinueWith(ReadPacket, TaskContinuationOptions.OnlyOnRanToCompletion); 
    readTask.ContinueWith(ReadPacketError, TaskContinuationOptions.OnlyOnFaulted); 
} 

Estado es sólo una clase definida por el usuario que por lo general sólo contiene la instancia TcpClient, los datos (matriz de bytes), y tal vez los bytes leídos también.

Como puede ver, ContinueWith se puede utilizar para reemplazar una gran cantidad de engorroso try-catches que hasta ahora era un mal necesario.

Al comienzo de su publicación mencionó que no quería crear un hilo por conexión o crear tareas de ejecución muy larga y pensé que abordaría eso en este punto. Personalmente, no veo el problema de crear un hilo para cada conexión.

Lo que debe tener cuidado con, sin embargo, es utilizar tareas (una abstracción sobre el ThreadPool) para las operaciones de larga ejecución. ThreadPool es útil porque la sobrecarga de crear un nuevo subproceso no es despreciable y para tareas cortas como leer o escribir datos y manejar una conexión de cliente, se prefieren las tareas.

Debe recordar que ThreadPool es un recurso compartido con una función especializada (evitando la sobrecarga de pasar más tiempo creando un hilo que realmente usarlo).Debido a que es compartido, si usó un hilo, otro recurso no puede y esto puede llevar rápidamente a la inanición del grupo de hilos y al escenario deadlock.

+0

Gracias - ¡esto es exactamente lo que estaba buscando! –

+5

¿Qué tal 1 millón de conexiones al usar un hilo por conexión? – Shift

+0

Luego debe usar los hilos del grupo de subprocesos correctamente. – vtortola

Cuestiones relacionadas