Upload
dima-dzuba
View
89
Download
7
Embed Size (px)
DESCRIPTION
Основы TCP/IP
Citation preview
МАИ, каф 806, Сети ЭВМ
Программируем сокеты
1
МАИ, каф 806, Сети ЭВМ2
Сокеты
Сокет – специальный дескриптор файла, для взаимодействия с сетью (термин произошел из
UNIX).
Работает напрямую с транспортным уровнем стека TCP/IP.
Два типа сокетов:
Сервис с соединением (на базе TCP).
Сервис без соединения (на базе UDP).
a.
МАИ, каф 806, Сети ЭВМ
Сокеты с соединением
протокол TCP
Сервис с соединением
Гарантированная доставка
Сохраняет последовательность переданных сообщений
Управление диалогом (можно передавать и получать данные
тогда когда нам удобно)
Не очень быстрый
Сокеты Беркли могут работать в одном из двух режимов:
блокирующем или неблокирующем. Блокирующий сокет не
возвращает контроль пока не отошлет (или пока не получит)
все данные, указанные для операции.
3
МАИ, каф 806, Сети ЭВМ
Потоки
В общем случае при передаче данных в сеть (и соответственно
при приеме) мы будем работать с сетью как с файлами.
Мы будем писать и считывать данные «поточным» образом. Т.е.
сеть для нас будет своего рода очередью.
Важно понимать, что поскольку данные передаются пакетами, то
перед отправкой происходит группировка данных. Таким образом,
данные отправляются в сеть не сразу, а в некоторый (случайный
для верхнего слоя протокола) момент.
Можно управлять отправкой данных принудительно отправив
буфер в сеть.
4
МАИ, каф 806, Сети ЭВМ5
Сокеты с соединением.
Клиентское приложение Серверное приложение
Клиентский сокет
Поток получения данных
Поток отправки данных
Серверный сокет
Клиентский сокет
Поток отправки данных
Поток получения данных
Виртуальный диалог
Данные соединения
Служебные данные
Пользовательские данные
Пользовательские данные
МАИ, каф 806, Сети ЭВМ
В терминах C#
В момент работы
метода accept
происходит
«зависание» сервера в
ожидании входящего
соединения.
6
МАИ, каф 806, Сети ЭВМ
Базовые классы
005_Address
System.Net.IPAddress
http://msdn.microsoft.com/ru-
ru/library/system.net.ipaddress.aspx
Конструкторы
• Byte[]
• Int64
Статические свойства
• Any
• Broadcast
• Loopback
• None
Методы
• Parse (статический).
• TryParse (статический)
• ToString
IPAddress test1 =
IPAddress.Parse("192.168.1.1");
IPAddress test2 = IPAddress.Loopback;
IPAddress test3 = IPAddress.Broadcast;
IPAddress test4 = IPAddress.Any;
IPAddress test5 = IPAddress.None;
IPHostEntry ihe =
Dns.GetHostEntry(Dns.GetHostName());
7
МАИ, каф 806, Сети ЭВМ
Базовые классы
006_EndPoint
System.Net.IPEndPoint
http://msdn.microsoft.com/ru-ru/library/system.net.ipendpoint.aspx
Конструкторы
• IPEndPoint(IPAddress, Int32)
Статические свойства
• MaxPort
• MinPort
Свойства
• Address
• AddessFamily
• Port
IPHostEntry ihe =
Dns.GetHostEntry(Dns.GetHostName());
IPAddress test1 = ihe.AddressList[0];
IPEndPoint ie = new IPEndPoint(test1,
8000);
8
МАИ, каф 806, Сети ЭВМ9
Сокеты с соединением. С#.Базовые классы
1. using System;
2. using System.Net;
3. namespace _06_EndPoint
4. {
5. class Program
6. {
7. static void Main(string[] args)
8. {
9. IPAddress test1 = IPAddress.Parse("192.168.1.1");
10. IPEndPoint ie = new IPEndPoint(test1, 8000);
11. Console.WriteLine("The IPEndPoint is: {0}",ie.ToString());
12. Console.WriteLine("The AddressFamily is: {0}",ie.AddressFamily);
13. Console.WriteLine("The address is: {0}, and the port is: {1}\n", ie.Address, ie.Port);
14. Console.WriteLine("The min port number is: {0}",IPEndPoint.MinPort);
15. Console.WriteLine("The max port number is: {0}\n",IPEndPoint.MaxPort);
16. ie.Port = 80;
17. Console.WriteLine("The changed IPEndPoint value is: {0}", ie.ToString());
18. SocketAddress sa = ie.Serialize();
19. Console.WriteLine("The SocketAddress is: {0}", sa.ToString());
20. }
21. }
22.}
МАИ, каф 806, Сети ЭВМ10
Сокеты с соединением. С#. Сервер.
Базовые классы
System.Net.IPEndPoint
System.Net.IPAddress
System.Net.Sockets.Socket
Сценарий
Создание точки подсоединения
Создание сокета
Связывание точки подсоединения и сокета
Ожидание соединения
Соединение с клиентом
Отправка/передача данных
Завершение сеанса связи с клиентом
Завершение ожидания подсоединения.
МАИ, каф 806, Сети ЭВМ11
Сокеты с соединением. С#. Сервер.
немного UML
sd C# Сервер
Программа Сервер
:IPEndPoint
:Socket
:Socket
loop Последовательность определяется прикладным протоколом
loop Пока реаботает сервер
bind()
listen()
Accept()
Socket
Receive()
Send()
Shutdown()
Close()
Close()
МАИ, каф 806, Сети ЭВМ12
Сокеты с соединением. С#. Клиент.
Базовые классы
System.Net.IPEndPoint
System.Net.IPAddress
System.Net.Sockets.Socket
Сценарий
Создание точки подсоединения
Создание сокета
Связывание точки подсоединения и сокета
Соединение с сервером
Отправка/передача данных
Завершение сеанса связи с сервером
МАИ, каф 806, Сети ЭВМ13
Сокеты с соединением. С#. Клиент
немного UML
sd С# Клиент
Программа Клиент
:IPEndPoint
:Socket
loop Последовательность определяется прикладным протоколом
Connect()
Send()
Receive()
Shutdown()
Close()
МАИ, каф 806, Сети ЭВМ
Пример использование TCP Socket [1/2]
.NET Framework
private static void OnBeginAccept(IAsyncResult ar)
{
Socket listener = (Socket)ar.AsyncState;
using (Socket client = listener.EndAccept(ar))
{
byte[] buffer = new byte[_bufferSize];
client.Receive(buffer);
buffer = Encoding.UTF8.GetBytes(string.Format("Hello, {0}",
Encoding.UTF8.GetString(buffer)));
client.Send(buffer);
}
}
14
МАИ, каф 806, Сети ЭВМ
Пример использование TCP Socket [2/2]
.NET Framework
static void Main(string[] args)
{
EndPoint endPoint = new IPEndPoint(IPAddress.Loopback, 8320);
using (Socket listener = new Socket(endPoint.AddressFamily,SocketType.Stream, ProtocolType.Tcp))
using (Socket client = new Socket(endPoint.AddressFamily,SocketType.Stream, ProtocolType.Tcp))
{
listener.Bind(endPoint);
listener.Listen(1);
listener.BeginAccept(new AsyncCallback(OnBeginAccept), listener);
client.Connect(endPoint);
Console.Write("Введите сообщение: ");
string request = Console.ReadLine();
int count = client.Send(Encoding.UTF8.GetBytes(request));
byte[] buffer = new byte[_bufferSize];
client.Receive(buffer);
string response = Encoding.UTF8.GetString(buffer);
int index = response.IndexOf('\0');
Console.WriteLine(string.Format("Ответ сервера: {0}",
response.Remove(index)));
}
}
15
МАИ, каф 806, Сети ЭВМ
Сокеты и безопасность
NegotiateStream - Клиент
static void Main(string[] args)
{
TcpClient client = new TcpClient();
client.Connect("localhost", 4242);
NegotiateStream kerb = new NegotiateStream(client.GetStream());
kerb.AuthenticateAsClient(CredentialCache.DefaultNetworkCredentials, @"sts\ddzuba",
ProtectionLevel.EncryptAndSign,
System.Security.Principal.TokenImpersonationLevel.Impersonation);
StreamWriter writer = new StreamWriter(kerb);
writer.WriteLine("Hello kerberized Server");
}
16
МАИ, каф 806, Сети ЭВМ
Сокеты и безопасность
NegotiateStream - Сервер
static void Main(string[] args)
{
TcpListener listener = new TcpListener(4242);
listener.Start();
TcpClient client = listener.AcceptTcpClient();
NegotiateStream kerb = new NegotiateStream(client.GetStream());
kerb.AuthenticateAsServer(CredentialCache.DefaultNetworkCredentials,
ProtectionLevel.EncryptAndSign,
TokenImpersonationLevel.Impersonation);
Console.WriteLine("Client Identity: {0}", kerb.RemoteIdentity.Name);
WindowsPrincipal principal = new WindowsPrincipal((WindowsIdentity)kerb.RemoteIdentity);
Console.WriteLine("Is Admin? : {0}", principal.IsInRole(WindowsBuiltInRole.Administrator));
Thread.CurrentPrincipal = principal;
DoSomethingOnlyUsersCanDo();
}
[PrincipalPermission(SecurityAction.Demand, Role=@"Пользователи")]
static void DoSomethingOnlyUsersCanDo()
{ Console.WriteLine("Users only"); }
17
МАИ, каф 806, Сети ЭВМ
Сокеты без соединения
протокол UDP
Сервис без соединения (т.е. пересылаем
автономные пакеты)
Не гарантированная доставка.
Не гарантированная последовательность.
Очень быстрый
Пример использования: при авторизации
клиентов для работы с интернет (популярный
протокол RADIUS реализован поверх UDP)
18
МАИ, каф 806, Сети ЭВМ19
Сокеты без соединения. C#. Сервер
Протокол UDP
1. using System;
2. using System.Net;
3. using System.Net.Sockets;
4. using System.Collections.Generic;
5. using System.Text;
6. namespace _08_UDPClient
7. {
8. class Program
9. {
10. static void Main(string[] args)
11. {
12. int recv;
13. byte[] data = new byte[1024];
14. IPEndPoint ipep = new IPEndPoint(IPAddress.Any, 9050);
15. Socket newsock = new Socket(AddressFamily.InterNetwork,SocketType.Dgram, ProtocolType.Udp);
16. newsock.Bind(ipep);
17. Console.WriteLine("Waiting for a client...");
18. IPEndPoint sender = new IPEndPoint(IPAddress.Any, 0);
19. EndPoint Remote = (EndPoint)(sender);
20. recv = newsock.ReceiveFrom(data, ref Remote);
21. Console.WriteLine("Message received from {0}:", Remote.ToString());
22. Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));
23. string welcome = "Welcome to my test server";
24. data = Encoding.ASCII.GetBytes(welcome);
25. newsock.SendTo(data, data.Length, SocketFlags.None, Remote);
26. while (true) {
27. data = new byte[1024];
28. recv = newsock.ReceiveFrom(data, ref Remote);
29. Console.WriteLine(Encoding.ASCII.GetString(data, 0, recv));
30. newsock.SendTo(data, recv, SocketFlags.None, Remote);
31. }
32. }
33. }
34. }
МАИ, каф 806, Сети ЭВМ20
Сокеты без соединения. C#.
Broadcast сообщения
1. using System;
2. using System.Collections.Generic;
3. using System.Text;
4. using System.Net;
5. using System.Net.Sockets;
6. namespace _09_Broadcst
7. {
8. class Program
9. {
10. static void Main(string[] args)
11. {
12. Socket sock = new Socket(AddressFamily.InterNetwork, SocketType.Dgram,ProtocolType.Udp);
13. IPEndPoint iep1 = new IPEndPoint(IPAddress.Broadcast, 9050);
14. IPEndPoint iep2 = new IPEndPoint(IPAddress.Parse("192.168.1.255"), 9050);
15. string hostname = Dns.GetHostName();
16. byte[] data = Encoding.ASCII.GetBytes(hostname);
17. sock.SetSocketOption(SocketOptionLevel.Socket,SocketOptionName.Broadcast, 1);
18. sock.SendTo(data, iep1);
19. sock.SendTo(data, iep2);
20. sock.Close();
21. }
22. }
23.}
МАИ, каф 806, Сети ЭВМ
Как передать данные на другой компьютер?
Протокол данных TCP и UDP предоставляют сервис по передаче данных
в виде последовательности байт.
Как передать сложные структуры данных?
Необходима реализация дополнительного слоя, предоставляющего
сервис по преобразованию данных из нужного формата в поток байт и
обратно.
Как это сделать наиболее просто?
21
МАИ, каф 806, Сети ЭВМ
Как передать объект на другую машину?
сериализация в .NET
В отличие от приложений на неуправляемом коде, приложения .NET Framework не
обязательно выполняются в виде отдельных процессов, а могут существовать в пределах
одного процесса операционной системы в своих собственных областях, называемых
доменами приложения. Такие области можно рассматривать как некоторые логические
процессы виртуальной машины CLR.
При передаче между доменами приложений некоторого объекта для его класса должна быть
определена процедура сериализации, которая позволяет сохранить состояние объекта в
некотором внешнем хранилище (например, в файле, или в сообщении транспортного
протокола) при помощи потоков ввода-вывода, и процедура десериализации, создающая
копию объекта по сохраненному состоянию
22
МАИ, каф 806, Сети ЭВМ
А что если объект сложный?
граф объектов
Задача сериализации объекта, включающего только поля из элементарных типов значений
(наследников класса System.ValueType) и строк, не представляет принципиальных
трудностей. Для такого объекта в ходе сериализации в поток записываются сами значения
всех полей объекта. Однако в общем случае объект содержит ссылки на другие объекты,
которые, в свою очередь, могут ссылаться друг на друга, образуя так называемый граф
объектов (object graph).
23
public class SampleClass
{
public SampleClass fieldA = null;
public SampleClass fieldB = null;
}
...
SampleClass root = new SampleClass();
SampleClass child1 = new SampleClass();
SampleClass child2 = new SampleClass();
root.fieldA = child1;
root.fieldB = child2;
child1.fieldA = child2;
МАИ, каф 806, Сети ЭВМ
Просто пронумеруем объекты
почти как ссылки в C++
ObjectID ?В ходе сериализации объектам должны быть поставлены в соответствие некоторые
идентификаторы, и в хранилище отдельно сохраняется список объектов, отмеченный
идентификаторами, а при сериализации вместо ссылок записываются идентификаторы
ссылаемых объектов.
Виртуальный адрес!В программе роль идентификатора объекта выполняет его адрес, но вместо него обычно
удобнее назначить некоторые идентификаторы в процедуре сериализации для более легкого
чтения человеком полученного образа.
Hash-map …В ходе сериализации нужно ввести список адресов уже записанных объектов, как для
ведения списка идентификаторов, так и для обнаружения возможных циклов при обходе
графа методом в глубину.
24
МАИ, каф 806, Сети ЭВМ
.Net Framework
чем нам поможет?
В .NET Framework выделяется три различных независимых класса форматирования:
XmlSerializer
SoapFormatter
BinaryFormatter.
Технологии
технология веб служб ASP.NET использует XmlSerializer;
технология Remoting использует SoapFormatter, BinaryFormatter или созданный
пользователем класс;
при работе с сообщениями MSMQ используется XmlSerializer
(через XMLMessageFormatter), или BinaryFormatter (через BinaryMessageFormatter), или
созданный пользователем класс;
технология Enterprise Services основана на Remoting и использует BinaryFormatter.
25
МАИ, каф 806, Сети ЭВМ
System.Xml.XmlSerializer
Класс XmlSerializer реализует открытый текстовый метод сериализации, использующий XML в качестве базового формата
хранения и схемы XML для спецификации документа с результатом сериализации.
Начиная с .NET Framework 2.0 XmlSerializer позволяет сериализовать публичные классы, имеющие конструктор без параметров
типа public и отвечающие одному из следующих требований:
Класс реализует интерфейс IXMLSerializable. В этом случае XmlSerializer просто использует при сериализации методы
класса GetSchema, ReadXml, WriteXml.
Класс реализует интерфейс System.Collections.IEnumerable , но не реализует ICollection и содержит публичный
метод Add c единственным параметром, имеющим тип, совпадающий с типом результата
свойства IEnumerator.Current метода GetEnumerator сериализуемого объекта. Такой класс сериализуется через вызовы
класса IEnumerator, возвращаемого методом GetEnumerator, а его публичные поля и свойства не сериализуются.
Класс реализует интерфейс System.Collections.ICollection, но не реализует IEnumerable. Для такого класса
осуществляется сериализация только свойства Item и публичных полей, реализующих интерфейс ICollection. Другие
публичные поля и свойства не сериализуются.
Класс реализует интерфейсы ICollection и IEnumerable, имеет публичное индексированное свойство Item c целым индексом
и публичное целое свойство Count. Тип, принимаемый методом Add, должен быть типом свойства Item или одним из его
предков. Другие публичные поля и свойства не сериализуются.
Класс не реализует ни один из интерфейсов IXMLSerializer, IEnumerable, ICollection и имеет
атрибут System.SerializableAttribute. В этом случае будут сериализованы публичные свойства и поля класса с учетом
специальных атрибутов, управляющих процессом сериализации.
Подлежащие сериализации публичные свойства должны иметь реализацию обоих методов, get и set. Кроме того, если класс
не использует собственную процедуру сериализации, то он не должен иметь свойств или полей типа интерфейс или
многомерных массивов, вместо них следует использовать вложенные массивы.
26
МАИ, каф 806, Сети ЭВМ
Работа с XSD схемами
В состав .NET Framework входит утилита xsd.exe, позволяющая выполнять три основные
задачи:
создание частичного (partial) описания класса на С# по схеме XSD;
создание схемы XSD по классу С#;
создание схемы XSD по образцу XML-файла (правильность зависит от полноты
образца).
Формат: xsd.exe assembly /type:type_name
Таким образом, при использовании для обмена данными между компонентами
класса XmlSerializer можно как создать схему по сериализуемому классу и представить ее в
качестве спецификации передаваемых данных, так и сгенерировать на языке C# код
описания публичных свойств класса из XSD-схемы.
27
МАИ, каф 806, Сети ЭВМ
XmlSerializer
итого
Особенностью класса XMLSerializer является то, что в ходе работы он создает сборки с кодом
сериализации для каждого обрабатываемого класса при вызове его конструктора.
Если такая задержка нежелательна (например, программа не совершает повторяющейся
сериализации одного и того же класса, но желает при этом осуществлять операции
максимально быстро), то при помощи утилиты sgen.exe можно заранее создать такие сборки
и затем подключить их к проекту.
Резюмируя краткое описание XMLSerializer, следует отметить, что несмотря на отдельные
сложности его применения к некоторым классам, его использование позволяет создать
открытое взаимодействия удаленных компонент со спецификацией сериализуемых классов в
виде схемы XSD.
28
МАИ, каф 806, Сети ЭВМ
System.Runtime.Serialization.Formatters.Soap.SoapFormatter
System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
Класс форматирования BinaryFormatter реализует двоичный закрытый метод сериализации.
Класс SoapFormatter – текстовый и открытый, основанный на спецификации кодирования
SOAP-RPC
Оба указанных класса в простейшем случае при сериализации сохраняют все поля класса (но
не его свойства), вне зависимости от их видимости.
Поля, имеющие атрибут System.NonSerializeAttribute, игнорируются.
Класс должен иметь атрибут System.SerializableAttribute.
Если же обрабатываемый класс реализует интерфейс ISerializable:
он сериализуется вызовом GetObjectData(SerializationInfo info, StreamingContext context)
десериализация таких классов осуществляется вызовом
конструктора ISerializable(SerializationInfo info, StreamingContext context)
29
МАИ, каф 806, Сети ЭВМ31
Сокеты с соединением
на примере JAVA
Носитель
Информации
java.io.OutputStream
Буферwrite Байт
java.io.InputStream
read Байт
Байт Байт
flush
МАИ, каф 806, Сети ЭВМ32
Носитель Информации
Потоки в Java
Паттерн Decorator
Базовые интерфейсы
InputStream
OutputStream
Потоки-декораторы
BufferedInputStream
BufferedOutputStream
DataInputStream
DataOutputStream
ObjectInputStream
ObjectOutputStream
PrintStream
Простые потоки
FileInputStream
FileOutputStream
ByteArrayInputStream
ByteArrayOutputStream
Носитель Информации
Поток-Модификатор
Метод Доступа потока-декоратора
Метод Доступа потока
Метод Доступа потока-декоратора
Метод приложения пользователя
МАИ, каф 806, Сети ЭВМ33
Сокеты с соединением Java.
Базовые классы
java.net.InetAddress
Конструкторы – нет
Статические методы
• getLocalHost
• getByName
• getAllByName
Пример
1. import java.net.*;
2. class InetAddressTest
3. {
4. public static void main(String args[]) throws UnknownHostException
5. {
6. InetAddress Address = InetAddress.getLocalHost();
7. System.out.println(Address);
8. Address = InetAddress.getByName("www.soshnikov.com");
9. System.out.println(Address);
10. InetAddress SW[] = InetAddress.getAllByName("www.mai.ru");
11. for (int i=0; i<SW.length; i++)
12. System.out.println (SW[i]) ;
13. }
14. }
МАИ, каф 806, Сети ЭВМ34
Сокеты с соединением. Java. Сервер.
Базовые классы
java.net.Socket
java.net.ServerSocket
java.io.InputStream
java.io.OutputStream
Сценарий
Создание сокета
Ожидание соединения
Соединение с клиентом
Создание объектов-потоков
Отправка/передача данных
Удаление объектов-потоков
Завершение сеанса связи с клиентом
Завершение ожидания подсоединения.
МАИ, каф 806, Сети ЭВМ35
Сокеты с соединением. Java. Сервер.
1. import java.net.*;
2. import java.io.*;
3. public class ServerExample
4. {
5. public static void main(String argv[])
6. {
7. try{
8. ServerSocket server= new ServerSocket(2345);
9. while(true)
10. {
11. Socket client = server.accept();
12. OutputStream os = client.getOutputStream();
13. os.write("Hello World!".getBytes());
14. os.flush();
15. client.close();
16. }
17. } catch(IOException e)
18. {
19. }
20. }
21. }
МАИ, каф 806, Сети ЭВМ36
Сокеты с соединением. Java. Клиент.
Базовые классы
java.net.Socket
java.net.InetAddress
java.io.InputStream
java.io.OutputStream
Сценарий
Создание адреса
Создание сокета
Соединение с сервером
Создание объектов-потоков
Отправка/передача данных
Удаление объектов-потоков
Завершение сеанса связи с сервером
МАИ, каф 806, Сети ЭВМ37
Сокеты с соединением. Java. Клиент.
1. import java.net.*;
2. import java.io.*;
3. public class ClientExample
4. {
5. public static void main(String argv[])
6. {
7. try{
8. Socket client = new Socket(InetAddress.getLocalHost(),2048);
9. ObjectOutputStream os = new ObjectOutputStream(client.getOutputStream());
10. ObjectInputStream is = new ObjectInputStream(client.getInputStream());
11. os.write(“Hello server!”);
12. Object result = is.readObject();
13. os.flush();
14. client.close();
15. }
16. } catch(Exception e)
17. {
18. }
19. }
20. }
МАИ, каф 806, Сети ЭВМ38
Сокеты без соединения.Java
протокол UDP
Классы
java.net.DatagramSocket
java.net.DatagramPacket
Пример:
1. byte buffer[] = new byte[1024];
2. DatagramSocket ds = new DatagramSocket(1111);
3. ds.send(new DatagramPacket( buffer, buffer.length, InetAddress.getLocalHost(), 2222));
4. DatagramPacket p = new DatagramPacket(buffer, buffer.length);
5. ds.receive(p);
6. System.out.println(new String(p.getData(),0,0,p.getLength())) ;
МАИ, каф 806, Сети ЭВМ
Библиотека java.nio
неблокирующие сокеты и цикличный буфер
Классы
ServerSocketChannel
SocketChannel
Использование
ByteBuffer buffer = ByteBuffer.allocate(4096);
ServerSocketChannel ssc = ServerSocketChannel.open();
ssc.socket().bind(new InetSocketAddress(8080));
ssc.configureBlocking(false);
…
buffer.rewind();
sc.read(buffer)
39
МАИ, каф 806, Сети ЭВМ
Немного о проблемах написания высокопроизводительных
серверов
Большое количество клиентов, и большое количество запросов от клиентов требуют от
сервера эффективности обработки запросов.
Какие задачи выполняет сервер?
Получает запрос от клиента
Читает данные из сети
Выполняет вычисления
Отправляет данные клиенту
Создавая много потоков (Thread) мы потребляем много ресурсов, что приводит к деградации
производительности.
Основные вопросы:
Размер пула потоков?
Один поток на каждого клиента?
Один поток на всех клиентов и много потоков обработчиков?
40