Клиент посылает через сервер сообщение другому клиенту java

Обновлено: 08.07.2024

Основные компоненты приложения

Рассмотрим классовую структуру всего проекта, а потом перейдем к реализации.

Классовая структура клиент-серверного приложения

Исходный код клиента на Java

Разобраться с клиентом гораздо проще, он по сути своей не делает ничего супер сложного, просто создает сокет и подключается к сервер-сокету с помощью связки host:port. Лаунчер создает объект класса Client и запускает его работу. Исходный код привожу без импортов, ибо любая IDE вам их подключит(те, кто пишет на Java точно знают, что без IDE очень сложно). Кроме того, в конце статьи вы сможете скачать архив с этим проектом.

ClientLauncher.java

Client.java

Исходный код сервера на Java

Задача сервера поднять свой серверный сокет на нужном адресе и ждать новых подключений. Для каждого подключения, которое принято называть клиентской сессией, создается отдельный поток обработки логики работы с клиентом.

ServerLauncher.java

Server.java

Context.java

ClientSession.java

SessionsManager.java

Заключение

Этот учебник знакомит с программированием сокетов Java по протоколу TCP/IP с реальным приложением Клиент/сервер.

1. Обзор

Термин сокет программирование относится к написанию программ, которые выполняются на нескольких компьютерах, в которых все устройства подключены друг к другу с помощью сети.

Существует два протокола связи, которые можно использовать для программирования сокетов: Протокол пользовательских дейтаграмм (UDP) и протокол управления передачей (TCP) .

Основное различие между ними заключается в том, что UDP не имеет соединения, что означает отсутствие сеанса между клиентом и сервером, в то время как TCP ориентирован на соединение, что означает, что сначала должно быть установлено исключительное соединение между клиентом и сервером для осуществления связи.

Этот учебник представляет введение в программирование сокетов по сетям TCP/IP и демонстрирует, как писать клиентские/серверные приложения на Java. UDP не является основным протоколом и как таковой может встречаться нечасто.

2. Настройка проекта

Java предоставляет набор классов и интерфейсов, которые заботятся о деталях низкоуровневой связи между клиентом и сервером.

Нам также нужны java.io пакет, который дает нам входные и выходные потоки для записи и чтения во время общения:

Для простоты мы запустим наши клиентские и серверные программы на одном компьютере. Если бы мы выполняли их на разных сетевых компьютерах, единственное, что изменилось бы, – это IP-адрес, в этом случае мы будем использовать localhost on 127.0.0.1 .

3. Простой Пример

Давайте испачкаем руки в самых основных примерах, связанных с клиентом и сервером . Это будет двустороннее коммуникационное приложение, в котором клиент приветствует сервер, а сервер отвечает.

Давайте создадим серверное приложение в классе с именем GreetServer.java со следующим кодом.

Мы включаем метод main и глобальные переменные, чтобы привлечь внимание к тому, как мы будем запускать все серверы в этой статье. В остальных примерах в статьях мы опустим этот тип более повторяющегося кода:

Давайте также создадим клиент под названием GreetClient.java с этим кодом:

Давайте запустим сервер; в вашей IDE вы делаете это, просто запустив его как Java-приложение.

А теперь давайте отправим приветствие на сервер с помощью модульного теста, который подтверждает, что сервер действительно отправляет приветствие в ответ:

Не волнуйтесь, если вы не совсем понимаете, что здесь происходит, так как этот пример призван дать нам представление о том, чего ожидать в дальнейшем в статье.

В следующих разделах мы проанализируем связь сокетов , используя этот простой пример, и углубимся в детали с большим количеством примеров.

4. Как Работают Сокеты

Мы будем использовать приведенный выше пример, чтобы пройти через различные части этого раздела.

По определению, сокет -это одна конечная точка двустороннего канала связи между двумя программами, запущенными на разных компьютерах в сети. Сокет привязан к номеру порта , чтобы транспортный уровень мог идентифицировать приложение, которому предназначены данные для отправки.

4.1. Сервер

Обычно сервер работает на определенном компьютере в сети и имеет сокет, привязанный к определенному номеру порта. В нашем случае мы используем тот же компьютер, что и клиент, и запустили сервер по порту 6666 :

Сервер просто ждет, слушая сокет, чтобы клиент сделал запрос на подключение. Это происходит на следующем шаге:

Когда код сервера встречает метод accept , он блокируется до тех пор, пока клиент не сделает запрос на подключение к нему.

Если все идет хорошо, сервер принимает соединение. После принятия сервер получает новый сокет clientSocket , привязанный к тому же локальному порту, 6666 , а также имеет свою удаленную конечную точку, установленную на адрес и порт клиента.

Чтобы обеспечить непрерывность связи, нам придется читать из входного потока внутри цикла while и выходить только тогда, когда клиент отправляет запрос на завершение, мы увидим это в действии в следующем разделе.

Для каждого нового клиента серверу требуется новый сокет, возвращаемый вызовом accept . ServerSocket используется для продолжения прослушивания запросов на подключение в соответствии с потребностями подключенных клиентов. Мы еще не допустили этого в нашем первом примере.

4.2. Клиент

Клиент должен знать имя хоста или IP-адрес машины, на которой работает сервер, и номер порта, на котором сервер прослушивает.

Чтобы сделать запрос на подключение, клиент пытается встретиться с сервером на компьютере и порту сервера:

Клиент также должен идентифицировать себя с сервером, чтобы привязаться к локальному номеру порта, назначенному системой, который он будет использовать во время этого соединения. Мы не занимаемся этим сами.

Приведенный выше конструктор создает новый сокет только тогда, когда сервер принял соединение, в противном случае мы получим исключение отказа в соединении. После успешного создания мы можем получить входные и выходные потоки от него для связи с сервером:

5. Непрерывная Связь

Таким образом, это полезно только в запросах ping, но представьте, что мы хотели бы внедрить сервер чата, и, безусловно, потребуется непрерывная обратная связь между сервером и клиентом.

Обратите внимание, что мы добавили условие завершения, при котором цикл while завершается, когда мы получаем символ точки.

Мы запустим Echo Server , используя метод main так же, как мы это сделали для GreetServer . На этот раз мы запускаем его на другом порту, таком как 4444 чтобы избежать путаницы.

Клиент Echo похож на Отличный клиент , поэтому мы можем дублировать код. Мы разделяем их для ясности.

В другом тестовом классе мы создадим тест, чтобы показать, что несколько запросов к EchoServer будут обслуживаться без закрытия сокета сервером. Это верно до тех пор, пока мы отправляем запросы от одного и того же клиента.

Работа с несколькими клиентами-это другой случай, который мы рассмотрим в следующем разделе.

Давайте создадим метод setup для инициирования соединения с сервером:

Мы также создадим метод tearDown для освобождения всех наших ресурсов, это лучшая практика для каждого случая, когда мы используем сетевые ресурсы:

Затем давайте протестируем наш эхо – сервер с помощью нескольких запросов:

Это улучшение по сравнению с первоначальным примером, когда мы общались только один раз, прежде чем сервер закрыл наше соединение; теперь мы посылаем сигнал завершения, чтобы сообщить серверу, когда мы закончим сеанс .

6. Сервер С Несколькими Клиентами

Несмотря на то, что предыдущий пример был улучшением по сравнению с первым, он все еще не является отличным решением. Сервер должен иметь возможность обслуживать множество клиентов и множество запросов одновременно.

Работа с несколькими клиентами-это то, что мы рассмотрим в этом разделе.

Еще одна особенность, которую мы увидим здесь, заключается в том, что один и тот же клиент может отключиться и снова подключиться, не получая исключения отказа в подключении или сброса соединения на сервере. Раньше мы не могли этого сделать.

Это означает, что наш сервер будет более надежным и устойчивым к многочисленным запросам от нескольких клиентов.

Мы сделаем это, чтобы создать новый сокет для каждого нового клиента и обслуживать запросы этого клиента в другом потоке. Количество клиентов, обслуживаемых одновременно, будет равно количеству запущенных потоков.

Основной поток будет выполнять цикл while, когда он прослушивает новые соединения.

Хватит разговоров, давайте создадим еще один сервер под названием EchoMultiServer.java. Внутри него мы создадим класс потока обработчика для управления коммуникациями каждого клиента в его сокете:

Обратите внимание, что теперь мы вызываем accept внутри цикла while . Каждый раз, когда выполняется цикл while , он блокирует вызов accept до тех пор, пока не подключится новый клиент , а затем для этого клиента создается поток обработчика EchoClientHandler .

То, что происходит внутри потока, – это то, что мы ранее делали на сервере Echo , где мы обрабатывали только одного клиента. Таким образом, Echo Multi Server делегирует эту работу EchoClientHandler , чтобы он мог продолжать прослушивать больше клиентов в цикле while .

Давайте запустим наш сервер, используя его основной метод на порту 5555 .

Для ясности мы все равно поместим тесты в новый набор:

Мы могли бы создать столько тестовых случаев, сколько нам заблагорассудится, каждый из которых порождает нового клиента, и сервер будет обслуживать их все.

7. Заключение

В этом уроке мы сосредоточились на введении в программирование сокетов через TCP/IP и написали простое клиент-серверное приложение на Java.

Это первое приложение в односторонней связи. В случае односторонней связи клиент отправляет на сервер, но сервер не отправляет обратно клиенту. При двусторонней связи клиент отправляет на сервер, а сервер отправляет обратно клиенту.

Всего в приложении TCP / IP 4 варианта.

APPLICATION NUMBERFUNCTIONALITY
1st applicationClient to server communication (one-way)
2nd applicationServer to client communication (one-way)
3rd applicationServer sends file contents to client (two-way, non-continuous)
4th applicationChat program (two-way, continuous)

1-е Приложение клиент-сервер

Клиентская программа – WishesClient.java

Конструктор класса Socket принимает два параметра – строку, IP-адрес сервера и целое число, номер порта на сервере, к которому клиент хотел бы подключиться. 127.0.0.1 – это адрес по умолчанию локальной системы в компьютерных сетях.

OutputStream ostream = sock.getOutputStream ();

Метод getOutputStream() класса Socket возвращает объект OutputStream, здесь объект является ostream. Это отправная точка всего общения (программы). Здесь сокет связан с потоками. Потоки способствуют передаче данных.

Ниже приведены исключения в вышеприведенной программе, создаваемые конструктором и различными методами.

  • Socket(“127.0.0.1”, 5000) выдает UnknownHostException
  • getOutputStream() генерирует IOException
  • writeBytes (message1) выдает IOException
  • Все методы close() выдают IOException
  • Серверная программа – WishesServer.java


ServerSocket sersock = новый ServerSocket (5000);

У сервера есть два задания: одно, как и ожидалось, должно связываться, а другое связывает соединение с номером порта 5000. Для связи он использует Socket, а для привязки – ServerSocket.

Связывание – это не что иное, как выделение номера порта клиенту так долго, как ему хотелось бы; Между тем, если какой-либо другой клиент запрашивает номер порта 5000, он не должен выделяться сервером. Когда клиент отключается, порт освобождается и может быть предоставлен другому клиенту сервером.

Socket sock = sersock.accept ();

accept() – это метод класса ServerSocket, используемый сервером для привязки соединения по номеру порта 5000, запрошенного клиентом.

InputStream istream = sock.getInputStream();

DataInputStream dstream = new DataInputStream (istream);

Поскольку InputStream является абстрактным классом, его нельзя использовать напрямую. Он связан с конкретным классом DataInputStream.

String message2 = dstream.readLine();

Примечание. При компиляции этой программы вы получаете предупреждение из-за метода readLine() объекта DataInutStream; но программа выполняется. Чтобы избежать этого предупреждения, в следующей программе используется BufferedReader.

Выполнение клиентских и серверных программ

В одной системе, чтобы действовать как клиент и сервер, откройте два шDOS и обработайте одно как клиент, а другой – как сервер. Из одного приглашения DOS сначала запустите серверную программу, а из другого приглашения DOS запустите клиентскую программу. Вы получаете вывод при запросе сервера DOS.

Для лучшего понимания вопрос-ответ из пакета java.lang.

Сколько существует типов внутренних классов?
Ответ: 4 типа.

Что такое файлы JAR?
Ответ: JAR-файл – это заархивированный файл, сжатый JVM.

Как преобразовать строку в форму типа данных?
Ответ: Преобразование строки в тип данных – байтовое, короткое, целое, длинное, плавающее, двойное, символьное и логическое.

Как преобразовать объект в строку?
Ответ: Объект в строку – toString()

Как сравнить два объекта?
Ответ: Сравнение объектов – hashCode() & equals()

Средняя оценка / 5. Количество голосов:

Спасибо, помогите другим - напишите комментарий, добавьте информации к статье.

Если вы начали читать эту статью, то, скорее всего, имеете какое-то отношение к IT-и понимаете что такое IP-адрес – уникальный адрес, который определяет компьютер в сети.

Но достаточно ли такой адресации для полноценной работы? Предположим, что на некотором компьютере запущены одновременно две программы, которые взаимодействуют с интернетом – получают и/или отсылают какие-то данные. Программы никак между собой не связаны и общаются с разными интернет-сервисами. Но они расположены на одном компьютере, следовательно, имеют один IP-адрес. Если они одновременно должны получить данные от двух разных серверов, как же они определят, кому какие данные предназначались?

Для того, чтобы решить эту проблему, нужно вместе с самими данными передавать информацию о том, какой программе они предназначаются. Эта информация и есть порт.

В нашем случае каждая из программ (точнее, программисты, написавшие их) должна определить, по какому порту она хочет взаимодействовать с сетью. Сервер, в свою очередь, должен тоже знать этот порт и посылать данные именно на него.

Что же собой представляет этот загадочный порт? Вы можете взять отвёртку и перебрать весь компьютер, но портов так и не найдёте. Это просто число, которое передаётся вместе с данными. Теоретически, оно может находиться в диапазоне от 1 до 65535, но порты 1..1024 используются системными программами и занимать их не стоит. Поэтому порт следует выбирать из диапазона 1025..65535.

Планируем

Даже в таких простых программах, как чат, не нужно сразу лезть в IDEи писать что-то невнятное. Для начала стоит осмыслить теорию, с которой мы ознакомились (вы же её не пропустили, правда?) и понять, что она значит в контексте нашей программы.

  • Необходимо два режима работы программы – серверный и клиентский

При этом можно создать либо две разные программы, либо одну и спрашивать пользователя, в каком режиме её запустить. Первый способ лучше тем, что серверу не придётся хранить файлы для клиентской части. С другой стороны, так чтобы проверить работу сервера и клиента придётся качать и запускать две разные программы. Остановимся на втором способе – в одной программе в зависимости от желания пользователя, будем запускать серверный или клиентский режим.

Написание программы

Теперь, когда все подготовительные момент ясны, можно преступить к самому интересному – написанию программы.

Файлы и структура пакетов

Вы, конечно, знаете, что любая программа на Javaначинается с метода main(String[] args). Для большей наглядности не будем добавлять его к другим классам, а создадим отдельный класс Mainи пакет для него – main. В любой программе наверняка будут какие-то константы. Я предпочитаю выносить их в отдельный файл в виде publicstaticполей, поэтому создам класс Constи также добавлю его в пакет main.

Как мы помним, программа должна работать в режиме клиента или сервера. Создадим два соответствующих класса Clientи Server.

В итоге дерево пакетов выглядит так:

Структура каталогов

Выбор режима работы

Для начала нужно выбрать, в каком режиме запускать программу – сервер или клиент. Это нам и нужно первым делом узнать у пользователя, поэтому в метод main(…) пишем следующее:

Здесь всё достаточно просто – спрашиваем, как запускать программу, считываем букву ответа и запускаем соответствующий класс. Стоит пояснить только по поводу класса Scanner – это класс стандартной библиотеки, который облегчает работу с вводом данных из консоли. Он инициализируется стандартным потоком ввода.

Режим клиента

Пойдём от простого к сложному и сначала реализует клиентский режим работы.

Если сервер просто запускается и ждём пользователей, то клиентам приходится проявлять некоторую активность, а именно – подключиться к серверу. Для этого нужно знать его IPи порт подключения. Порт является константой, поэтому зададим его в Const.java:

Constобъявлен как abstract, т.к. содержит только статические данные и создавать его экземпляр ни к чему.

IPдолжен ввести пользователь, поэтому в конструкторе Client пишем:

Теперь у нас есть все необходимые данные – ip, порт, режим работы программы. Можно подключаться к серверу. Сначала создадим сокет:

Любые операции с потоками и сокетами должны выполняться внутри блока try..catch, для того, чтобы обрабатывать ошибки.

Thread– класс Java, который реализует такую незаменимую вещь, как многопоточность. Это возможность программы одновременно выполнять разные наборы действий. Как раз то, что нам сейчас нужно.

В конструкторе создадим объект этого класса и запустим поток:

Итоговый файл Client.java вместе с остальными приведён в конце статьи.

Режим сервера

Сервер, в отличие от клиента, работает не с классом Socket, а с ServerSocket. При создании его объекта программа никуда не подключается, а просто создаётся сервер на порту, переданном в конструктор.

Теперь осталось только создать сервер, который будет принимать подключения, создавать объекты Connectionи добавлять их в массив. В конструкторе класса Server пишем:

Метод server.accept() указывает серверу ожидать подключения. Как только какой-то клиент подключится к серверу, метод вернёт объект Socket, связанный с этим подключением. Дальше создаётся объект Connection, инициализированный этим сокетом и добавляется в массив. Не забываем про try..catchи в конце закрываем все сокеты вместе с потоками методом closeAll();

Исходники

Где-то на середине статьи я вдруг осознал, что худшего учителя найти сложно, поэтому вот вам хотя бы исходники с комментариями. Может там что-то будет понятно. Спойлеров что-то нету, так что лучше выложу на bitbucket.

Читайте также: