.
Java для чайников. Урок 34. Клиент серверное приложение. Продолжение.
Автор megabax   
07.01.2020 г.
В этой статье я постараюсь максимально просто и понятно изложить основы программирования на языке Delphi

Java для чайников. Урок 34. Клиент серверное приложение. Продолжение.

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

Сначала мы подключаем необходимые библиотеки:

import java.io.*;

import java.net.*;

import java.util.*;

 

Обратите внимание, что среди  них есть библиотека для работы с сетью (см.  также Java для чайников. Урок 31. Основы работы с сетью. Класс URL).

Далее, объявляем сам класс и все необходимые поля.

 

public class StockQuoteServer {

    private static final int SERVER_PORT=1701; //порт, который будет прослушиваться сервером

    private static final int MAX_CLIENTS=50; //максимальное количество подключенных клиентов

    private static final File STOCK_QUOTES_FILE=new File("stockquotes.txt"); //файл с данными

    private ServerSocket listenSocket; //сокет

    private String[] stockInfo;

    private Date stockInfoTime;

    private long stockFileMode;

    private boolean keepRunning=true; //флаг, сброс которого приводит к завершению работы сервера

 

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

 

Идем дальше. Метод main, отвечающий за запуск приложения (сервера):

    //запуск приложения

    public static void main(String args[]) {

        StockQuoteServer server = new StockQuoteServer();

        server.serverQuotes(); 

    }

 

Как видим, здесь просто создается экземпляр класса сервер и у него запускается метод serverQuotes.

Для того, чтобы можно было создать объект  класса сервера, у него должен быть конструктор:

 

    //Конструктор загружает данные об акциях, после чего сервер

    //ожидает запросы от клиентов

    public StockQuoteServer()

    {

        if(!loadQuotes()) System.exit(1); //загружает данные, если ошибка - завершает работу

        try

        {

            listenSocket=new ServerSocket(SERVER_PORT, MAX_CLIENTS); //создать сокет

        } 

        catch(IOException except)

        {

            System.err.println("Unable to listen on port "+SERVER_PORT);

            System.exit(1);

        }

    }

 

Что мы делаем в конструкторе? Загружаем котировки, если не получилось, то завершаем работу с программой. Затем создаем сокет (экземпляр класса ServerSocket ), который, как я уже говорил, будет слушать порт. Обратите внимание, что сокет создается исключительно в конструкции try ...catch, иначе компилятор просто не  скомпилирует программу, выдав сообщение об ошибке.

Далее, как загружаем котировки (функция loadQuotes). Для этого мы создаем объект класса DataInputStream, то есть, поток для чтения данных из файла:

 

            //создать поток для чтения данных из файла

            DataInputStream stockInput=new DataInputStream(new FileInputStream(STOCK_QUOTES_FILE));

 

Затем в цикле читаем данные в буфер:

            //считаем каждую строку

            while((fileLine=stockInput.readLine())!=null)

            {

                inputBuffer.append(fileLine.toUpperCase()+"\n"); //поместить строку в буфер

                numSocks++; //увеличить счетчик

            }

 

После чтения надо обязательно закрыть файл (файловый поток):

stockInput.close();

 

Итак, данные из файла у нас прочитаны в буфер. Но они не в том виде, как нам надо, для удобства поиска строки надо записать в массив::

 

        stockInfo=new String[numSocks]; //создадим массив строк для чтения инфы из файла

        String inputString=inputBuffer.toString();

       

        //указатели для создания подстрок

        int stringStart=0; //начало строки

        int stringEnd=0;  //конец строки

               

        for(int index=0; index<numSocks; index++)

        {

            stringEnd=inputString.indexOf("\n",stringStart);

           

            //если \n больше не встречается, то весь остаток inputString

            if(stringEnd==-1)

            {

                stockInfo[index]=inputString.substring(stringStart);

            }

            else

            {

                stockInfo[index]=inputString.substring(stringStart, stringEnd);

            }

           

            stringStart=stringEnd+1;  //передвинем указатель на следующую строку

        }

 

С loadQuotes закончили. Переходим к следующему методу - serverQuotes:

    //Этот метод ожидает обращение от клиента

    public void serverQuotes()

    {

        Socket clientSocket=null;

        try

        {

            while(keepRunning)

            {

                clientSocket=listenSocket.accept(); //присоединить нового клиента

               

                //Если файл данных изменен, загрузить данные повторно

                if(stockFileMode!=STOCK_QUOTES_FILE.lastModified())

                {

                    loadQuotes();

                }

               

                //Создать новый обработчик

                StockQuoteHandler newHandler = new StockQuoteHandler(clientSocket, stockInfo, stockInfoTime);            

                Thread newHandlerThread=new Thread(newHandler);

                newHandlerThread.start();

               

            }

            listenSocket.close();

        } catch(IOException e)

        {

            System.err.println("Failed I/O "+e);

        }

    }

 

Что у нас происходит в этом методе? Мы в цикле слушаем входящие соединения. Как только кто-то соединиться, мы создаем для обработки этого содениения отдельный поток StockQuoteHandler, который и работает с данным соединением:

                //Создать новый обработчик

                StockQuoteHandler newHandler = new StockQuoteHandler(clientSocket, stockInfo, stockInfoTime);            

                Thread newHandlerThread=new Thread(newHandler);

                newHandlerThread.start();

 

Что такое StockQuoteHandler? Это у нас Runnable, который может работать в потоке. В этом классе есть также ссылка на сокет и на потоки обмена данными:

//Класс для обеспечения связи с отдельным клиентом

class StockQuoteHandler implements Runnable

{

    private Socket mySocket=null;

    private PrintStream clientSend=null;

    private DataInputStream clientReceive=null;

    private String[] stockInfo;

    private Date stockInfoTime;

...

 

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

    //конструктор

    public StockQuoteHandler(Socket newSocket, String[] info, Date time)

    {

        mySocket=newSocket;

        stockInfo=info;

        stockInfoTime=time;

    }

 

Реализация самой программы обработки запросов от клиента находиться в методе run. Что в нем происходит? Во-первых, создаються потоки для принятя сообщений от клиента и для отправки сообщений клиенту:

    //поток, реализующий обмен данными

    public void run()

    {

        String nextLine;

        String quoteID;

        String quoteResponse;

        try

        {

            clientSend=new PrintStream(mySocket.getOutputStream());

            clientReceive=new DataInputStream(mySocket.getInputStream());

...

 

Как вы, наверное, поняли, поток для отправки сообщений клиенту называется clientSend, а для принятия clientReceive. И сервер в первую очередь отправляет клиенту приветствие:

 

            clientSend.println("+HELLO "+stockInfoTime);

            clientSend.flush();

 

Ну а дальше мы в цикле принимаем сообщения от клиента и анализируем их:

            //получить строку от клиента и ответить

            while((nextLine=clientReceive.readLine())!=null)

            {

                nextLine=nextLine.toUpperCase();

               

                //команды выхода

                if(nextLine.indexOf("QUIT")==0) break;

               

                //команда STOCK

                else if(nextLine.indexOf("STOCK: ")==0)

                {                  

                    quoteID=nextLine.substring("STOCK: ".length());

                    quoteResponse=getQuote(quoteID);

                    clientSend.println(quoteResponse);

                    clientSend.flush();

                }

               

                //неизвестная команда

                else

                {

                    clientSend.println("-ERR UNKNOWN COMMAND");

                    clientSend.flush();

                }

            }

 

После завершения цикла сервер прощается с клеинтом:

            clientSend.println("+BUY");

            clientSend.flush();

 

Обратите внимание, что вся работа с клиентом ведется в конструкции try .. catch, по окончанию которой мы закрываем потоки и сокет:

        } catch(IOException e)

        {

            System.err.println("Failed I/O "+e);

        } finally

        {

            //закроем потоки и сокет

            try

            {

                if(clientSend!=null) clientSend.close();

                if(clientReceive!=null) clientReceive.close();

                if(mySocket!=null) mySocket.close();

            } catch(IOException e)

            {

                System.err.println("Failed I/O "+e);

            }

        }

 

Итак, с сервером разобрались На следующем уроке будет разбираться с клиентом. А сейчас попробуем что-нибудь добавить в сервер, например, логирование операций с клиентами. Для этого ищем у класса  StockQuoteServer метод serverQuotes и  в блок while(keepRunning) вставляем строки (выделены маркером):

            while(keepRunning)

            {

                Date curdate = new Date();

                System.out.println(curdate+" waiting for client");

                clientSocket=listenSocket.accept(); //присоединить нового клиента

...

...

 

Теперь сервер будет сообщать, что он находиться в режиме ожидания:

 

Java для чайников. Урок 34. Клиент серверное приложение. Продолжение.

 

 

(С) Шуравин Александр

 

 

Последнее обновление ( 07.01.2020 г. )