Сокеты
Сокет - это один конец двустороннего канала связи между двумя программами, работающими в сети. Соединяя вместе два сокета, можно передавать данные между разными процессами (локальными или удаленными). Реализация сокетов обеспечивает инкапсуляцию протоколов сетевого и транспортного уровней.
Первоначально сокеты были разработаны для UNIX в Калифорнийском университете в Беркли. В UNIX обеспечивающий связь метод ввода-вывода следует алгоритму open/read/write/close. Прежде чем ресурс использовать, его нужно открыть, задав соответствующие разрешения и другие параметры. Как только ресурс открыт, из него можно считывать или в него записывать данные. После использования ресурса пользователь должен вызывать метод Close(), чтобы подать сигнал операционной системе о завершении его работы с этим ресурсом.
Когда в операционную систему UNIX были добавлены средства межпроцессного взаимодействия (Inter-Process Communication, IPC) и сетевого обмена, был заимствован привычный шаблон ввода-вывода. Все ресурсы, открытые для связи, в UNIX и Windows идентифицируются дескрипторами. Эти дескрипторы, или описатели (handles) , могут указывать на файл, память или какой-либо другой канал связи, а фактически указывают на внутреннюю структуру данных, используемую операционной системой. Сокет, будучи таким же ресурсом, тоже представляется дескриптором. Следовательно, для сокетов жизнь дескриптора можно разделить на три фазы: открыть (создать) сокет, получить из сокета или отправить сокету и в конце концов закрыть сокет.
Интерфейс IPC для взаимодействия между разными процессами построен поверх методов ввода-вывода. Они облегчают для сокетов отправку и получение данных. Каждый целевой объект задается адресом сокета, следовательно, этот адрес можно указать в клиенте, чтобы установить соединение с целью.
Типы сокетов
Существуют два основных типа сокетов - потоковые сокеты и дейтаграммные.
Потоковые сокеты (stream socket)
Потоковый сокет - это сокет с установленным соединением, состоящий из потока байтов, который может быть двунаправленным, т, е. через эту конечную точку приложение может и передавать, и получать данные.
Потоковый сокет гарантирует исправление ошибок, обрабатывает доставку и сохраняет последовательность данных. На него можно положиться в доставке упорядоченных, сдублированных данных. Потоковый сокет также подходит для передачи больших объемов данных, поскольку накладные расходы, связанные с установлением отдельного соединения для каждого отправляемого сообщения, может оказаться неприемлемым для небольших объемов данных. Потоковые сокеты достигают этого уровня качества за счет использования протокола Transmission Control Protocol (TCP) . TCP обеспечивает поступление данных на другую сторону в нужной последовательности и без ошибок.
Для этого типа сокетов путь формируется до начала передачи сообщений. Тем самым гарантируется, что обе участвующие во взаимодействии стороны принимают и отвечают. Если приложение отправляет получателю два сообщения, то гарантируется, что эти сообщения будут получены в той же последовательности.
Однако, отдельные сообщения могут дробиться на пакеты, и способа определить границы записей не существует. При использовании TCP этот протокол берет на себя разбиение передаваемых данных на пакеты соответствующего размера, отправку их в сеть и сборку их на другой стороне. Приложение знает только, что оно отправляет на уровень TCP определенное число байтов и другая сторона получает эти байты. В свою очередь TCP эффективно разбивает эти данные на пакеты подходящего размера, получает эти пакеты на другой стороне, выделяет из них данные и объединяет их вместе.
Потоки базируются на явных соединениях: сокет А запрашивает соединение с сокетом В, а сокет В либо соглашается с запросом на установление соединения, либо отвергает его.
Если данные должны гарантированно доставляться другой стороне или размер их велик, потоковые сокеты предпочтительнее дейтаграммных. Следовательно, если надежность связи между двумя приложениями имеет первостепенное значение, выбирайте потоковые сокеты.
Сервер электронной почты представляет пример приложения, которое должно доставлять содержание в правильном порядке, без дублирования и пропусков. Потоковый сокет рассчитывает, что TCP обеспечит доставку сообщений по их назначениям.
Дейтаграммные сокеты (datagram socket)
Дейтаграммные сокеты иногда называют сокетами без организации соединений, т. е. никакого явного соединения между ними не устанавливается - сообщение отправляется указанному сокету и, соответственно, может получаться от указанного сокета.
Потоковые сокеты по сравнению с дейтаграммными действительно дают более надежный метод, но для некоторых приложений накладные расходы, связанные с установкой явного соединения, неприемлемы (например, сервер времени суток, обеспечивающий синхронизацию времени для своих клиентов). В конце концов на установление надежного соединения с сервером требуется время, которое просто вносит задержки в обслуживание, и задача серверного приложения не выполняется. Для сокращения накладных расходов нужно использовать дейтаграммные сокеты.
Использование дейтаграммных сокетов требует, чтобы передачей данных от клиента к серверу занимался User Datagram Protocol (UDP) . В этом протоколе на размер сообщений налагаются некоторые ограничения, и в отличие от потоковых сокетов, умеющих надежно отправлять сообщения серверу-адресату, дейтаграммные сокеты надежность не обеспечивают. Если данные затерялись где-то в сети, сервер не сообщит об ошибках.
Кроме двух рассмотренных типов существует также обобщенная форма сокетов, которую называют необрабатываемыми или сырыми.
Сырые сокеты (raw socket)
Главная цель использования сырых сокетов состоит в обходе механизма, с помощью которого компьютер обрабатывает TCP/IP. Это достигается обеспечением специальной реализации стека TCP/IP, замещающей механизм, предоставленный стеком TCP/IP в ядре - пакет непосредственно передается приложению и, следовательно, обрабатывается гораздо эффективнее, чем при проходе через главный стек протоколов клиента.
По определению, сырой сокет - это сокет, который принимает пакеты, обходит уровни TCP и UDP в стеке TCP/IP и отправляет их непосредственно приложению.
При использовании таких сокетов пакет не проходит через фильтр TCP/IP, т.е. никак не обрабатывается, и предстает в своей сырой форме. В таком случае обязанность правильно обработать все данные и выполнить такие действия, как удаление заголовков и разбор полей, ложится на получающее приложение - все равно, что включить в приложение небольшой стек TCP/IP.
Однако нечасто может потребоваться программа, работающая с сырыми сокетами. Если вы не пишете системное программное обеспечение или программу, аналогичную анализатору пакетов, вникать в такие детали не придется. Сырые сокеты главным образом используются при разработке специализированных низкоуровневых протокольных приложений. Например, такие разнообразные утилиты TCP/IP, как trace route, ping или arp, используют сырые сокеты.
Работа с сырыми сокетами требует солидного знания базовых протоколов TCP/UDP/IP.
Порты
Порт определен, чтобы разрешить задачу одновременного взаимодействия с несколькими приложениями. По существу с его помощью расширяется понятие IP-адреса. Компьютер, на котором в одно время выполняется несколько приложений, получая пакет из сети, может идентифицировать целевой процесс, пользуясь уникальным номером порта, определенным при установлении соединения.
Сокет состоит из IP-адреса машины и номера порта, используемого приложением TCP. Поскольку IP-адрес уникален в Интернете, а номера портов уникальны на отдельной машине, номера сокетов также уникальны во всем Интернете. Эта характеристика позволяет процессу общаться через сеть с другим процессом исключительно на основании номера сокета.
За определенными службами номера портов зарезервированы - это широко известные номера портов, например порт 21, использующийся в FTP. Ваше приложение может пользоваться любым номером порта, который не был зарезервирован и пока не занят. Агентство Internet Assigned Numbers Authority (IANA) ведет перечень широко известных номеров портов.
Обычно приложение клиент-сервер, использующее сокеты, состоит из двух разных приложений - клиента, инициирующего соединение с целью (сервером), и сервера, ожидающего соединения от клиента.
Например, на стороне клиента, приложение должно знать адрес цели и номер порта. Отправляя запрос на соединение, клиент пытается установить соединение с сервером:
Если события развиваются удачно, при условии что сервер запущен прежде, чем клиент попытался с ним соединиться, сервер соглашается на соединение. Дав согласие, серверное приложение создает новый сокет для взаимодействия именно с установившим соединение клиентом:
Теперь клиент и сервер могут взаимодействовать между собой, считывая сообщения каждый из своего сокета и, соответственно, записывая сообщения.
Работа с сокетами в.NET
Поддержку сокетов в.NET обеспечивают классы в пространстве имен System.Net.Sockets - начнем с их краткого описания.
Класс | Описание |
---|---|
MulticastOption | Класс MulticastOption устанавливает значение IP-адреса для присоединения к IP-группе или для выхода из нее. |
NetworkStream | Класс NetworkStream реализует базовый класс потока, из которого данные отправляются и в котором они получаются. Это абстракция высокого уровня, представляющая соединение с каналом связи TCP/IP. |
TcpClient | Класс TcpClient строится на классе Socket, чтобы обеспечить TCP-обслуживание на более высоком уровне. TcpClient предоставляет несколько методов для отправки и получения данных через сеть. |
TcpListener | Этот класс также построен на низкоуровневом классе Socket. Его основное назначение - серверные приложения. Он ожидает входящие запросы на соединения от клиентов и уведомляет приложение о любых соединениях. |
UdpClient | UDP - это протокол, не организующий соединение, следовательно, для реализации UDP-обслуживания в.NET требуется другая функциональность. |
SocketException | Это исключение порождается, когда в сокете возникает ошибка. |
Socket | Последний класс в пространстве имен System.Net.Sockets - это сам класс Socket. Он обеспечивает базовую функциональность приложения сокета. |
Класс Socket
Класс Socket играет важную роль в сетевом программировании, обеспечивая функционирование как клиента, так и сервера. Главным образом, вызовы методов этого класса выполняют необходимые проверки, связанные с безопасностью, в том числе проверяют разрешения системы безопасности, после чего они переправляются к аналогам этих методов в Windows Sockets API.
Прежде чем обращаться к примеру использования класса Socket, рассмотрим некоторые важные свойства и методы этого класса:
Свойство или метод | Описание |
---|---|
AddressFamily | Дает семейство адресов сокета - значение из перечисления Socket.AddressFamily. |
Available | Возвращает объем доступных для чтения данных. |
Blocking | Дает или устанавливает значение, показывающее, находится ли сокет в блокирующем режиме. |
Connected | Возвращает значение, информирующее, соединен ли сокет с удаленным хостом. |
LocalEndPoint | Дает локальную конечную точку. |
ProtocolType | Дает тип протокола сокета. |
RemoteEndPoint | Дает удаленную конечную точку сокета. |
SocketType | Дает тип сокета. |
Accept() | Создает новый сокет для обработки входящего запроса на соединение. |
Bind() | Связывает сокет с локальной конечной точкой для ожидания входящих запросов на соединение. |
Close() | Заставляет сокет закрыться. |
Connect() | Устанавливает соединение с удаленным хостом. |
GetSocketOption() | Возвращает значение SocketOption. |
IOControl() | Устанавливает для сокета низкоуровневые режимы работы. Этот метод обеспечивает низкоуровневый доступ к лежащему в основе классу Socket. |
Listen() | Помещает сокет в режим прослушивания (ожидания). Этот метод предназначен только для серверных приложений. |
Receive() | Получает данные от соединенного сокета. |
Poll() | Определяет статус сокета. |
Select() | Проверяет статус одного или нескольких сокетов. |
Send() | Отправляет данные соединенному сокету. |
SetSocketOption() | Устанавливает опцию сокета. |
Shutdown() | Запрещает операции отправки и получения данных на сокете. |
Последнее обновление: 31.10.2015
Протокол UDP не требует установки постоянного подключения, и, возможно, многим покажется легче работать с UDP, чем с TCP. Большинство принципов при работе с UDP те же, что и с TCP.
Вначале создается сокет:
Socket socket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
Если сокет должен получать сообщения, то надо привязать его к локальному адресу и одному из портов с помощью метода Bind:
IPEndPoint localIP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), 5555); socket.Bind(localIP);
После этого можно отправлять и получать сообщения. Для получения сообщений используется метод ReceiveFrom() :
Byte data = new byte; // буфер для получаемых данных //адрес, с которого пришли данные EndPoint remoteIp = new IPEndPoint(IPAddress.Any, 0); int bytes = socket.ReceiveFrom(data, ref remoteIp);
В качестве параметра в метод передается массив байтов, в который надо считать данные, и удаленная точка, с которой приходят эти данные. Метод возвращает количество считанных байтов.
Для отправки данных используется метод SendTo() :
String message = Console.ReadLine(); byte data = Encoding.Unicode.GetBytes(message); EndPoint remotePoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), remotePort); listeningSocket.SendTo(data, remotePoint);
В метод передается массив отправляемых данных, а также адрес, по которому эти данные надо отправить.
Создадим программу UDP-клиента:
Using System; using System.Text; using System.Threading.Tasks; using System.Net; using System.Net.Sockets; namespace SocketUdpClient { class Program { static int localPort; // порт приема сообщений static int remotePort; // порт для отправки сообщений static Socket listeningSocket; static void Main(string args) { Console.Write("Введите порт для приема сообщений: "); localPort = Int32.Parse(Console.ReadLine()); Console.Write("Введите порт для отправки сообщений: "); remotePort = Int32.Parse(Console.ReadLine()); Console.WriteLine("Для отправки сообщений введите сообщение и нажмите Enter"); Console.WriteLine(); try { listeningSocket = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp); Task listeningTask = new Task(Listen); listeningTask.Start(); // отправка сообщений на разные порты while (true) { string message = Console.ReadLine(); byte data = Encoding.Unicode.GetBytes(message); EndPoint remotePoint = new IPEndPoint(IPAddress.Parse("127.0.0.1"), remotePort); listeningSocket.SendTo(data, remotePoint); } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { Close(); } } // поток для приема подключений private static void Listen() { try { //Прослушиваем по адресу IPEndPoint localIP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), localPort); listeningSocket.Bind(localIP); while (true) { // получаем сообщение StringBuilder builder = new StringBuilder(); int bytes = 0; // количество полученных байтов byte data = new byte; // буфер для получаемых данных //адрес, с которого пришли данные EndPoint remoteIp = new IPEndPoint(IPAddress.Any, 0); do { bytes = listeningSocket.ReceiveFrom(data, ref remoteIp); builder.Append(Encoding.Unicode.GetString(data, 0, bytes)); } while (listeningSocket.Available > 0); // получаем данные о подключении IPEndPoint remoteFullIp = remoteIp as IPEndPoint; // выводим сообщение Console.WriteLine("{0}:{1} - {2}", remoteFullIp.Address.ToString(), remoteFullIp.Port, builder.ToString()); } } catch (Exception ex) { Console.WriteLine(ex.Message); } finally { Close(); } } // закрытие сокета private static void Close() { if (listeningSocket != null) { listeningSocket.Shutdown(SocketShutdown.Both); listeningSocket.Close(); listeningSocket = null; } } } }
Вначале пользователь вводит порты для приема данных и для отправки. Предполагается, что два приложения клиента, которые будут между собой взаимодействовать, запущены на одной локальной машине. Если адреса клиентов различаются, то можно предусмотреть и ввода адреса для отправки данных.
После ввода портов запускается задача на прослушивание входящих сообщений. В отличие от tcp-сервера здесь не надо вызывать методы Listen и Accept. В бесконечном цикле мы напрямую можем получить получение данные с помощью метода ReceiveFrom() , который блокирует вызывающий поток, пока не придет очередная порция данных.
Этот метод возвращает через ref-параметр удаленную точку, с которой получены данные:
IPEndPoint remoteFullIp = remoteIp as IPEndPoint;
То есть, не смотря на то, что в данном случае прием и отправка сообщений разграничены и текущий клиент отправляет данные только на введенный вначале порт, но мы вполне можем добавить возможность ответа на сообщения, используя данные полученной удаленной точки (адрес и порт).
В главном потоке происходит отправка сообщений с помощью метода SendTo()
Таким образом, приложение сразу осуществляет функции и сервера, и клиента.
Теперь запустим две копии приложения и введем разные данные для портов. Первый клиент:
Введите порт для приема сообщений: 4004 Введите порт для отправки сообщений: 4005 Для отправки сообщений введите сообщение и нажмите Enter 127.0.0.1:4005 - привет порт 4004 добрый день, порт 4005 чудная погодка
Второй клиент:
Введите порт для приема сообщений: 4005 Введите порт для отправки сообщений: 4004 Для отправки сообщений введите сообщение и нажмите Enter привет порт 4004 127.0.0.1:4004 - добрый день, порт 4005 127.0.0.1:4004 - чудная погодка
Отсюда и "заточка" этого протокола под работу с отдельными документами, преимущественно текстовыми. HTTP в своей работе использует возможности TCP/IP, поэтому рассмотрим возможности, предоставляемые java для работы с последним.
В джаве для этого существует специальный пакет "java.net", содержащий класс java.net.Socket. Socket в переводе означает "гнездо", название это было дано по аналогии с гнёздами на аппаратуре, теми самыми, куда подключают штепсели. Соответственно этой аналогии, можно связать два "гнезда", и передавать между ними данные. Каждое гнездо принадлежит определённому хосту (Host - хозяин, держатель). Каждый хост имеет уникальный IP (Internet Packet) адрес. На данный момент интернет работает по протоколу IPv4, где IP адрес записывается 4 числами от 0 до 255 - например, 127.0.0.1 (подробнее о распределении IP адресов тут - RFC 790 , RFC 1918 , RFC 2365 , о версии IPv6 читайте тут - RFC 2373)
Гнёзда монтируются на порт хоста (port). Порт обозначается числом от 0 до 65535 и логически обозначает место, куда можно пристыковать (bind) сокет. Если порт на этом хосте уже занят каким-то сокетом, то ещё один сокет туда пристыковать уже не получится. Таким образом, после того, как сокет установлен, он имеет вполне определённый адрес, символически записывающийся так :, к примеру - 127.0.0.1:8888 (означает, что сокет занимает порт 8888 на хосте 127.0.0.1)
Для того, чтобы облегчить жизнь, чтобы не использовать неудобозапоминаемый IP адрес, была придумана система DNS (DNS - Domain Name Service). Цель этой системы - сопоставлять IP адресам символьные имена. К примеру, адресу "127.0.0.1" в большинстве компьютеров сопоставленно имя "localhost" (в просторечье - "локалхост").
Локалхост, фактически, означает сам компьютер, на котором выполняется программа, он же - локальный компьютер. Вся работа с локалхостом не требует выхода в сеть и связи с какими-либо другими хостами.
Клиентский сокет
Итак, вернёмся к классу java.net.Socket Наиболее удобно инициализировать его следующим образом:
Public Socket(String host, int port) throws UnknownHostException, IOException В строковой константе host можно указать как IP адрес сервера, так и его DNS имя. При этом программа автоматически выберет свободный порт на локальном компьютере и "привинтит" туда ваш сокет, после чего будет предпринята попытка связаться с другим сокетом, адрес которого указан в параметрах инициализации. При этом могут возникнуть два вида исключений: неизвестный адрес хоста - когда в сети нет компьютера с таким именем или ошибка отсутствия связи с этим сокетом.
Так же полезно знать функцию
Public void setSoTimeout(int timeout) throws SocketException Эта функция устанавливает время ожидания (timeout) для работы с сокетом. Если в течение этого времени никаких действий с сокетом не произведено (имеется ввиду получение и отправка данных), то он самоликвидируется. Время задаётся в секундах, при установке timeout равным 0 сокет становится "вечным".
Для некоторых сетей изменение timeout невозможно или установлено в определённых интервалах (к примеру от 20 до 100 секунд). При попытке установить недопустимый timeout, будет выдано соответственное исключение.
Программа, которая открывает сокет этого типа, будет считаться клиентом, а программа-владелец сокета, к которому вы пытаетесь подключиться, далее будет называться сервером. Фактически, по аналогии гнездо-штепсель, программа-сервер - это и будет гнездо, а клиент как раз является тем самым штепселем.
Сокет сервера
Как установить соединение от клиента к серверу я только что описал, теперь - как сделать сокет, который будет обслуживать сервер. Для этого в джава существует следующий класс: java.net.ServerSocket Наиболее удобным инициализатором для него является следующий:
Public ServerSocket(int port, int backlog, InetAddress bindAddr) throws IOException Как видно, в качестве третьего параметра используется объект ещё одного класса - java.net.InetAddress Этот класс обеспечивает работу с DNS и IP именами, по этому вышеприведённый инициализатор в программах можно использовать так: ServerSocket(port, 0, InetAddress.getByName(host)) throws IOException Для этого типа сокета порт установки указывается прямо, поэтому, при инициализации, может возникнуть исключение, говорящее о том, что данный порт уже используется либо запрещён к использованию политикой безопасности компьютера.
После установки сокета, вызывается функция
Public Socket accept() throws IOException Эта функция погружает программу в ожидание того момента, когда клиент будет присоединяться к сокету сервера. Как только соединение установлено, функция возвратит объект класса Socket для общения с клиентом.
Клиент-сервер через сокеты. Пример
Как пример - простейшая программа, реализующая работу с сокетами.
Со стороны клиента программа работает следующим образом: клиент подсоединяется к серверу, отправляет данные, после чего получает данные от сервера и выводит их.
Со стороны сервера это выглядит следующим образом: сервер устанавливает сокет сервера на порт 3128, после чего ждёт входящих подключений. Приняв новое подключение, сервер передаёт его в отдельный вычислительный поток. В новом потоке сервер принимает от клиента данные, приписывает к ним порядковый номер подключения и отправляет данные обратно к клиенту.
Логическая структура работы программ-примеров
Программа простого TCP/IP клиента
(SampleClient.java) import java. io.* ; import java. net.* ; class SampleClient extends Thread { public static void main(String args) { try { // открываем сокет и коннектимся к localhost:3128 // получаем сокет сервера Socket s = new Socket("localhost" , 3128 ); // берём поток вывода и выводим туда первый аргумент // заданный при вызове, адрес открытого сокета и его порт args[ 0 ] = args[ 0 ] + "\n" + s. getInetAddress() . getHostAddress() + ":" + s. getLocalPort(); s. getOutputStream() . write(args[ 0 ] . getBytes()); // читаем ответ byte buf = new byte [ 64 * 1024 ]; int r = s. getInputStream() . read(buf); String data = new String(buf, 0 , r); // выводим ответ в консоль System. out. println(data); } catch (Exception e) { System. out. println("init error: " + e);} // вывод исключений } }Программа простого TCP/IP сервера
(SampleServer.java) import java. io.* ; import java. net.* ; class SampleServer extends Thread { Socket s; int num; public static void main(String args) { try { int i = 0 ; // счётчик подключений // привинтить сокет на локалхост, порт 3128 ServerSocket server = new ServerSocket(3128 , 0 , InetAddress. getByName("localhost" )); System. out. println("server is started" ); // слушаем порт while (true) { // ждём нового подключения, после чего запускаем обработку клиента // в новый вычислительный поток и увеличиваем счётчик на единичку new SampleServer(i, server. accept()); i++ ; } } catch (Exception e) { System. out. println("init error: " + e);} // вывод исключений } public SampleServer(int num, Socket s) { // копируем данные this. num = num; this. s = s; // и запускаем новый вычислительный поток (см. ф-ю run()) setDaemon(true); setPriority(NORM_PRIORITY); start(); } public void run() { try { // из сокета клиента берём поток входящих данных InputStream is = s. getInputStream(); // и оттуда же - поток данных от сервера к клиенту OutputStream os = s. getOutputStream(); // буффер данных в 64 килобайта byte buf = new byte [ 64 * 1024 ]; // читаем 64кб от клиента, результат - кол-во реально принятых данных int r = is. read(buf); // создаём строку, содержащую полученную от клиента информацию String data = new String(buf, 0 , r); // добавляем данные об адресе сокета: data = "" + num+ ": " + "\n" + data; // выводим данные: os. write(data. getBytes()); // завершаем соединение s. close(); } catch (Exception e) { System. out. println("init error: " + e);} // вывод исключений } }После компиляции, получаем файлы SampleServer.class и SampleClient.class (все программы здесь и далее откомпилированы с помощью JDK v1.4) и запускаем вначале сервер:
Java SampleServer а потом, дождавшись надписи "server is started", и любое количество клиентов: java SampleClient test1 java SampleClient test2 ... java SampleClient testN
Если во время запуска программы-сервера, вместо строки "server is started" выдало строку типа
Init error: java.net.BindException: Address already in use: JVM_Bind то это будет обозначать, что порт 3128 на вашем компьютере уже занят какой-либо программой или запрещён к применению политикой безопасности.
Заметки
Отметим немаловажную особенность сокета сервера: он может принимать подключения сразу от нескольких клиентов одновременно. Теоретически, количество одновременных подключений неограниченно, но практически всё упирается в мощность компьютеров. Кстати, эта проблема конечной мощности компьютеров используется в DOS атаках на серверы: их просто закидывают таким количеством подключений, что компьютеры не справляются с нагрузкой и "падают".
В данном случае я показываю на примере SimpleServer, как нужно обрабатывать сразу несколько одновременных подключений: сокет каждого нового подключения посылается на обработку отдельному вычислительному потоку.
Стоит упомянуть, что абстракцию Socket - ServerSocket и работу с потоками данных используют C/C++, Perl, Python, многие другие языки программирования и API операционных систем, так что многое из сказанного подходит к применению не только для платформы Java .