Я работаю над чем-то похожим уже неделю или две, так что, надеюсь, я смогу вам немного помочь.
Если вы сосредоточены на простом коде, я бы рекомендовал использовать классы TcpClient и TcpListener. Они оба значительно облегчают работу с розетками. Хотя они существуют с .NET Framework 1.1, они были обновлены и по-прежнему являются лучшим выбором для вас.
С точки зрения того, как использовать .NET Framework 4.0 при написании упрощенного кода, задачи приходят на первое место. Они делают написание асинхронного кода гораздо менее болезненным, и после выхода C # 5 будет намного легче перенести ваш код ( новый асинхронный код и ожидание ключевых слов ). Вот пример того, как Задачи могут упростить ваш код:
Вместо использования tcpListener.BeginAcceptTcpClient(AsyncCallback callback, object state);
и предоставления метода обратного вызова, который будет вызывать EndAcceptTcpClient();
и, при необходимости, приведение вашего объекта состояния, C # 4 позволяет вам использовать замыкания, лямбды и задачи, чтобы сделать этот процесс намного более читабельным и масштабируемым. Вот пример:
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 очень полезен, поскольку Microsoft предоставила много перегрузок, которые могут упростить обычные асинхронные операции. Вот еще один пример:
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);
}
State - это пользовательский класс, который обычно содержит только экземпляр TcpClient, данные (байтовый массив) и, возможно, считанные байты.
Как видите, ContinueWith можно использовать для замены большого количества громоздких try-catches
, которые до сих пор были неизбежным злом.
В начале своего поста вы упомянули, что не хотите создавать поток для каждого соединения или создавать очень долго выполняемые задачи, и я подумал, что на этом я остановлюсь. Лично я не вижу проблемы с созданием потока для каждого соединения.
Однако вы должны быть осторожны с использованием Tasks (абстракция над ThreadPool) для длительных операций. ThreadPool полезен, потому что затраты на создание нового потока не пренебрежимо малы, и для коротких задач, таких как чтение или запись данных и обработка соединения с клиентом, задачи предпочтительнее.
Вы должны помнить, что ThreadPool является общим ресурсом со специализированной функцией (избегая лишних затрат, тратя больше времени на создание потока, чем на его фактическое использование). Поскольку он является общим, если вы использовали поток, другой ресурс не может, и это может быстро привести к голоданию пула потоков и сценариям deadlock .