Клиент web сервер java

Пример простого клиент-серверного приложения на Java

«Клиент-сервер» это очень распространенная и логичная архитектура приложений. Мне кажется, что в наши дни редко можно встретить standalone-клиентское приложение. Поэтому я принял решение рассмотреть пример построения клиент-серверного приложения на Java без привязки к конкретной задаче. Сначала вкратце пробежимся по классовой структуре приложения, потом посмотрим на отдельную реализацию каждого класса. В самом конце статьи я дам ссылку на скачивание архива с готовой структурой приложения. Итак, начнем.

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

Основными компонентами, естественно, являются непосредственно клиент и сервер. Однако, кроме них необходим еще пакет вспомогательных классов, которые, в простейшем случае, будут отвечать за обмен сообщениями между клиентом и сервером. В минимальной комплектации нужны такие классы: MessageReader/MessageWriter(считывает/записывает сообщение в поток на сокете), MessageFactory(содержит идентификаторы всех возможных сообщений), набор сообщений-запросов(Request) и набор сообщений-ответов(Response). Все они будут размещены в пакете «core», который должны иметь у себя и клиент и сервер.

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

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

- client (пакет файлов клиента) Client.java (логика клиента) ClientLauncher.java (запуск клиента) - core (вспомогательные классы) - communication (обмен сообщениями) MessageFactory.java (здесь хранятся все сообщения) MessageReader.java (чтение сообщений из потока) MessageWriter.java (запись сообщений в поток) - requests HandshakeRequest.java (запрос на обмен рукопожатиями) - responses HandshakeResponse.java (ответ на обмен рукопожатиями) IMessage.java (интерфейс для сообщения) Request.java (абстрактный класс "запрос") Response.java (абстрактный класс "ответ") - server (файлы сервера) ClientSession.java (сессия отдельного клиента - обработка запросов этого клиента) Context.java (контекст - общая информация сервера для всех клиентов) Server.java (логика сервера) ServerLauncher.java (запуск сервера) SessionsManager.java (хранит все текущие сессии)

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

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

ClientLauncher.java

public class ClientLauncher < public static void main(String[] args) < try < InetAddress host = InetAddress.getByName(args[0]); int port = Integer.parseInt(args[1]); //System.out.println(id); Client client = new Client(host, port); //Запускаем логику клиента client.start(); >catch (UnknownHostException e) < e.printStackTrace(); >> >

Client.java

public class Client < private final InetAddress host; private final int port; public Client(InetAddress host, int port) < this.host = host; this.port = port; >//Создает сокет, ридер райтер и запускает логику public void start() < //Создаем клиентский сокет try (Socket socket = new Socket(this.host, this.port)) < //Создаем ридер и райтер для обмена сообщениями MessageReader reader = new MessageReader(socket.getInputStream()); MessageWriter writer = new MessageWriter(socket.getOutputStream()); //Шлем серверу первое сообщение "рукопожатие" writer.writeRequest(new HandshakeRequest()); //Получаем ответ UniqueMessage msg = reader.readMessage(); //Проверяем, что это ответ на рукопожатие if(!(msg.message instanceof HandshakeResponse)) < return; >//Запуск логики приложения this.logicStart(); //socket.close(); > catch (IOException e) < e.printStackTrace(); >> public void logicStart() < //Логика приложения //. >>

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

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

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

Читайте также:  Curl url encoding php

Напомню, что в классе ClientSession описан основной алгоритм работы с клиентом, обмен сообщениями, данными и прочее. В классе Context содержится общая информация для всех клиентов сервера, например, пути для сохранения логов.

ServerLauncher.java

public class ServerLauncher < public static void main(String[] args) < Server server = new Server(); server.run(); >>

Server.java

public class Server implements Runnable < private final int port; private Context context; public Server() < this.port = 5000; this.context = new Context(); >@Override public void run() < try < ServerSocket ss = new ServerSocket(this.port); //Цикл ожидания подключений while(!this.context.stopFlag) < System.out.println("Waiting connection on port:" + this.port); //Момент ухода в ожидание подключения Socket clientSocket = ss.accept(); System.out.println("New client connected to server"); //Создается клиентская сессия ClientSession clientSession = new ClientSession(clientSocket, this.context); this.context.getSessionsManger().addSession(clientSession); //Запуск логики работы с клиентом clientSession.start(); >ss.close(); > catch (IOException e) < e.printStackTrace(); >> >

Context.java

//Данные, общие для всех клиентских сессий public class Context < private SessionsManager sessinonsManager; public boolean stopFlag; //Другие важные поля, которые должны знать все клиенты //. public Context() < this.stopFlag = false; this.sessinonsManager = new SessionsManager(); >public SessionsManager getSessionsManger() < return this.sessinonsManager; >>

ClientSession.java

//Основная логика клиента public class ClientSession extends Thread < private final Socket socket; private final MessageReader reader; private final MessageWriter writer; private final Context context; public ClientSession(final Socket socket, final Context context) throws IOException < this.socket = socket; this.reader = new MessageReader(socket.getInputStream()); this.writer = new MessageWriter(socket.getOutputStream()); this.context = context; >public void run() < UniqueMessage msg; try < msg = reader.readMessage(); //Рукопожатие if(msg.message instanceof HandshakeRequest) < if(((HandshakeRequest)msg.message).match()) < writer.writeResponse(new HandshakeResponse(), msg.uniqueId); >> //Обменялись рукопожатиями, начинаем работу this.doWork(); //выход this.socket.close(); > catch (IOException e) < e.printStackTrace(); >> private void doWork() <> >

SessionsManager.java

public class SessionsManager < private final Setsessions = new HashSet(); public SessionsManager() <> public synchronized void addSession(ClientSession session) < sessions.add(session); >public synchronized void removeSession(ClientSession session) < sessions.remove(session); >>

Вспомогательные классы из пакета «core»

Помещу все вспомогательные классы под один кат, название классов в точности соответствует названиям из списка «классовая структура» выше, по нему вы можете определить пакет каждого класса.

public class MessageFactory < //Идентификаторы запросов public static final int REQUEST_HANDSHAKE = 1; //Идентификаторы ответов private static final int RESPONSE_BASE = 0x40000000; public static final int RESPONSE_HANDSHAKE = RESPONSE_BASE + 1; //Ассоциативный массив: класс сообщения =>идентификатор сообщения private static final Map, Integer> idMap = new HashMap, Integer>(); static < idMap.put(HandshakeRequest.class, REQUEST_HANDSHAKE); idMap.put(HandshakeResponse.class, RESPONSE_HANDSHAKE); >private MessageFactory() < >//Создает сообщение по идентификатору public static IMessage createMessage(int messageId) throws IOException < if (messageId >RESPONSE_BASE) < switch (messageId) < case RESPONSE_HANDSHAKE: return new HandshakeResponse(); default: throw new IOException("Unknown message type " + messageId); >> else < switch (messageId) < case REQUEST_HANDSHAKE: return new HandshakeRequest(); default: throw new IOException("Unknown message type " + messageId); >> > public static int getMessageId(final IMessage message) < Integer return id.intValue(); >> public class MessageReader < //Длина заголовка сообщения public static final int HEADER_LENGTH = 12; private final DataInputStream dis; public MessageReader(InputStream is) < this.dis = new DataInputStream(is); >public UniqueMessage readMessage() throws IOException < //Читаем длину пакета из начала int packageLength = dis.readInt(); if (packageLength < HEADER_LENGTH) < throw new IOException("Wrong package length"); >//Считываем сообщение byte[] buf = new byte[packageLength — 4]; dis.readFully(buf); DataInputStream messageIS = new DataInputStream(new ByteArrayInputStream(buf)); int uniqueId = messageIS.readInt(); int message_id = messageIS.readInt(); IMessage message = MessageFactory.createMessage(message_id); message.readExternal(messageIS); System.out.println(«Message » + message.getClass().getName() + » received.»); return new UniqueMessage(message, uniqueId); > public static class UniqueMessage < public final IMessage message; public final int uniqueId; private UniqueMessage(IMessage message, int uniqueId) < this.message = message; this.uniqueId = uniqueId; >> > public class MessageWriter < private static final int INITIAL_BUFFER_SIZE = 128; private final DataOutputStream out; private Integer requestIdCounter = 0; public MessageWriter(OutputStream os) < this.out = new DataOutputStream(os); >private int getNewRequestId() < synchronized (requestIdCounter) < return ++requestIdCounter; >> private void writeMessage(final IMessage message, final int uniqueId) throws IOException < int messageId = MessageFactory.getMessageId(message); ByteArrayOutputStream baos = new ByteArrayOutputStream( INITIAL_BUFFER_SIZE); message.writeExternal(new DataOutputStream(baos)); int messageLength = baos.size() + MessageReader.HEADER_LENGTH; synchronized (out) < out.writeInt(messageLength); out.writeInt(uniqueId); out.writeInt(messageId); baos.writeTo(out); out.flush(); >System.out.println(«Message » + message.getClass().getName() + » sent.»); > public int writeRequest(final Request request) throws IOException < int uniqueId = getNewRequestId(); writeMessage(request, uniqueId); return uniqueId; >public void writeResponse(final Response response, int requestId) throws IOException < writeMessage(response, requestId); >> public class HandshakeRequest extends Request < public static final String HANDSHAKE_STRING = "handshake request"; private String handshake; @Override public void readExternal(DataInputStream dis) throws IOException < handshake = dis.readUTF(); >@Override public void writeExternal(DataOutputStream dos) throws IOException < dos.writeUTF(HANDSHAKE_STRING); >public boolean match() < return HANDSHAKE_STRING.equals(handshake); >> public class HandshakeResponse extends Response < public static final String HANDSHAKE_RESPONSE_STRING = "handshake response"; private String handshake; @Override public void readExternal(DataInputStream dis) throws IOException < handshake = dis.readUTF(); >@Override public void writeExternal(DataOutputStream dos) throws IOException < dos.writeUTF(HANDSHAKE_RESPONSE_STRING); >public boolean match() < return HANDSHAKE_RESPONSE_STRING.equals(handshake); >> public interface IMessage < public void writeExternal(DataOutputStream dos) throws IOException; public void readExternal(DataInputStream dis) throws IOException; >public abstract class Request implements IMessage < >public abstract class Response implements IMessage

Пара слов о сообщениях, классы Request и Response являются абстрактными и играют роль классификаторов сообщения. Благодаря этому очень удобно разграничивать «запросы» от «ответов». В этом примере я привел только одно сообщение — Handshake, которое отвечает за первое «рукопожатие» клиента и сервера. Все последующие сообщения должны быть прописаны в классе MessageFactory по примеру этих двух.

Читайте также:  Java new instance generic

Скачать архив с шаблоном клиент-серверного приложения на Java

Заключение

Главная цель, которую я преследовал при написании этой статьи заключается в том, чтобы дать возможность мне или кому-либо еще за считанные минуты «собрать» готовое клиент-серверное приложение. Кажется, я с этим справился, если будут дополнения или замечания, пишите в комментариях или на почту. А на сегодня у меня все, спасибо за внимание!

Источник

Клиент-сервер на Java

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

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

APPLICATION NUMBER FUNCTIONALITY
1st application Client to server communication (one-way)
2nd application Server to client communication (one-way)
3rd application Server sends file contents to client (two-way, non-continuous)
4th application Chat program (two-way, continuous)

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

Приложение состоит из двух программ. Клиентская программа, работающая на стороне клиента, и серверная программа, работающая на стороне сервера. Клиентская программа WishesClient.java отправляет серверу наилучшие пожелания, а серверная программа WishesServer.java получает сообщение и печатает на своем терминале (мониторе).

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

import java.net.Socket; import java.io.OutputStream; import java.io.DataOutputStream; public class WishesClient < public static void main(String args[]) throws Exception < Socket sock = new Socket("127.0.0.1", 5000); String message1 = "Accept Best Wishes, Serverji"; OutputStream ostream = sock.getOutputStream(); DataOutputStream dos = new DataOutputStream(ostream); dos.writeBytes(message1); dos.close(); ostream.close(); sock.close(); >> Socket sock = new Socket ("127.0.0.1", 5000);

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

OutputStream ostream = sock.getOutputStream ();

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

DataOutputStream dos = new DataOutputStream (ostream); dos.writeBytes (message1);

OutputStream является абстрактным классом; он не может быть использован напрямую. В приведенном выше коде он связан с конкретным классом DataOutputStream. Метод writeBytes() объекта DataOutputStream принимает строковое сообщение и передает его в Socket. Теперь клиентский сокет отправляется на другой сокет на сервере. Когда работа закончится, закройте потоки и сокет. Он освобождает дескрипторы (ссылки), связанные с системными ресурсами.

Читайте также:  Ubuntu install php ext dom

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

  • Socket(“127.0.0.1”, 5000) выдает UnknownHostException
  • getOutputStream() генерирует IOException
  • writeBytes (message1) выдает IOException
  • Все методы close() выдают IOException
  • Серверная программа – WishesServer.java
import java.net.ServerSocket; import java.net.Socket; import java.io.InputStream; import java.io.DataInputStream; public class WishesServer < public static void main(String args[]) throws Exception < ServerSocket sersock = new ServerSocket(5000); System.out.println("server is ready"); // message to know the server is running Socket sock = sersock.accept(); InputStream istream = sock.getInputStream(); DataInputStream dstream = new DataInputStream(istream); String message2 = dstream.readLine(); System.out.println(message2); dstream .close(); istream.close(); sock.close(); sersock.close(); >>

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

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

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

Socket sock = sersock.accept ();

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

InputStream istream = sock.getInputStream();

Метод getInputStream() объекта Socket возвращает объект InputStream, и это отправная точка серверной программы. Сервер использует входной поток при получении сообщения.

DataInputStream dstream = new DataInputStream (istream);

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

String message2 = dstream.readLine();

Метод readLine() объекта DataInputStream читает строку сообщения из сокета и возвращает ее. Это сообщение печатается на консоли.

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

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

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

Это приложение и следующее – только односторонняя связь, отправляющая или получающая. Но второй набор (после следующего) приложений является двусторонним, когда клиент и сервер могут отправлять и получать (оба). Для тестирования на выделенных серверах, можно обратиться сюда https://www.mixtelecom.ru/arenda-serverov.html

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

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

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

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

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

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

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

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

Видим, что вы не нашли ответ на свой вопрос.

Напишите комментарий, что можно добавить к статье, какой информации не хватает.

Источник

Оцените статью