Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых

Microsoft Visual J++. Создание приложений и аплетов на языке Java. Часть 2

© Александр Фролов, Григорий Фролов
Том 32, М.: Диалог-МИФИ, 1997, 288 стр.

[Назад] [Содеожание] [Дальше]

Приложения SocketServ и SocketClient

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

Приложение SocketServ выводит на консоль строку “Socket Server Application” и затем переходит в состояние ожидания соединения с клиентским приложением SocketClient.

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

Когда пользователь вводит строку “quit”, цикл ввода и передачи строк завершается.

Весь процесс показан на рис. 3.3.

Рис. 3.3. Передача данных между приложениями SocketClient и SocketServ через потоковый сокет

Здесь в окне клиентского приложения мы ввели несколько строк, причем последняя строка была строкой “quit”, завершившая работу приложений.

Исходный текст серверного приложения SocketServ

Исходный текст серверного приложения SocketServ приведен в листинге 3.5.

Листинг 3.5. Файл SocketServ\SocketServ.java


// =========================================================
// Использование потоковых сокетов.
// Приложение сервера
//
// (C) Фролов А.В, 1997
//
// E-mail: frolov@glas.apc.org
// WWW:    http://www.glasnet.ru/~frolov
//            или
//         http://www.dials.ccas.ru/frolov
// =========================================================
import java.io.*;
import java.net.*;
import java.util.*;

public class SocketServ
{
  // -------------------------------------------------------
  // main
  // Метод, получающий управление при запуске приложения
  // -------------------------------------------------------
  public static void main(String args[])
  {
    // Массив для ввода строки с клавиатуры
    byte bKbdInput[] = new byte[256];

    // Объект класса ServerSocket для создания канала
    ServerSocket ss;

    // Сокет сервера
    Socket s;

    // Входной поток для приема команд от клиента
    InputStream is;

    // Выходной поток для передачи ответа клиенту
    OutputStream os;

    try
    {
      System.out.println("Socket Server Application");
    }
    catch(Exception ioe)
    {
      // При возникновении исключения выводим его описание
      // на консоль
      System.out.println(ioe.toString());
    }
    
    try
    {
      // Создаем объект класса ServerSocket
      ss = new ServerSocket(9999);

      // Ожидаем соединение
      s = ss.accept();

      // Открываем входной поток для приема 
      // команд от клиента
      is = s.getInputStream();

      // Открываем выходной поток для передачи 
      // ответа клиенту
      os = s.getOutputStream();

      // Буфер для чтения команд
      byte buf[] = new byte[512];

      // Размер принятого блока данных
      int lenght;

      // Цикл обработки команд, полученных от клиента
      while(true)
      {
        // Получаем команду
        lenght = is.read(buf);

        // Если входной поток исчерпан, завершаем
        // цикл обработки команд
        if(lenght == -1)
          break;

        // Отображаем принятую команду на консоли сервера

        // Формируем строку из принятого блока
        String str = new String(buf, 0);

        // Обрезаем строку, удаляя символ конца строки
        StringTokenizer st;
        st   = new StringTokenizer(str, "\r\n");
        str = new String((String)st.nextElement());

        // Выводим строку команды на консоль
        System.out.println(">  " + str);

        // Посылаем принятую команду обратно клиенту
        os.write(buf, 0, lenght);

        // Сбрасываем буфер выходного потока
        os.flush();
      }

      // Закрываем входной и выходной потоки
      is.close();
      os.close();

      // Закрываем сокет сервера
      s.close();

      // Закрываем соединение
      ss.close();
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
    
    try
    {
      System.out.println(
        "Press <Enter> to terminate application...");

      System.in.read(bKbdInput);
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
  }
}

Описание исходного текста серверного приложения SocketServ

В методе main, получающем управление сразу после запуска приложения, мы определили несколько переменных.

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

В переменную ss класса ServerSocket будет записана ссылка на объект, предназначенный для установления канала связи через потоковый сокет (но не ссылка на сам сокет):


ServerSocket ss;

Ссылка на сокет, с использованием которого будет происходить передача данных, хранится в переменной с именем s класса Socket:


Socket s;

Кроме того, мы определили переменные is и os, соответственно, классов InputStream и OutputStream:


InputStream is; 
OutputStream os;

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

После отображения на консоли строки названия приложения, метод main создает объект класса ServerSocket, указывая конструктору номер порта 9999:


ss = new ServerSocket(9999);

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

Канал устанавливается методом accept:


s = ss.accept();

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

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

На следующем этапе сервер создает входной и выходной потоки, вызывая для этого методы getInputStream и getOutputStream, соответственно:


is = s.getInputStream();
os = s.getOutputStream();

Далее приложение подготавливает буфер buf для приема данных и определяет переменную length, в которую будет записываться размер принятого блока данных:


byte buf[] = new byte[512];
int lenght;

Теперь все готово для запуска цикла приема и обработки строк от клиентского приложения.

Для чтения строки мы вызываем метод read применительно ко входному потоку:


lenght = is.read(buf);

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

Метод read возвращает размер принятого блока данных или -1, если поток исчерпан. Мы воспользовались этим обстоятельством для завершения цикла приема данных:


if(lenght == -1)
  break;

После завершения приема блока данных мы преобразуем массив в текстовую строку str класса String, удаляя из нее символ перевода строки, и отображаем результат на консоли сервера:


System.out.println(">  " + str);

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


os.write(buf, 0, lenght);

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

Для исключения задержек в передаче данных из-за накопления данных в буфере (при использовании буферизованных потоков) необходимо принудительно сбрасывать содержимое буфреа метдом flush:


os.flush();

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

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

Наше приложение явням образом закрывает входной и выходной потоки данных, сокет, а также объект класса ServerSocket, с использованием которого был создан канал передачи данных:


is.close();
os.close();
s.close();
ss.close();

Исходный текст клиентского приложения SocketClient

Исходный текст клиентского приложения SocketClient приведен в листинге 3.6.

Листинг 3.6. Файл SocketClient\SocketClient.java


// =========================================================
// Использование потоковых сокетов.
// Приложение клиента
//
// (C) Фролов А.В, 1997
//
// E-mail: frolov@glas.apc.org
// WWW:    http://www.glasnet.ru/~frolov
//            или
//         http://www.dials.ccas.ru/frolov
// =========================================================
import java.io.*;
import java.net.*;
import java.util.*;

public class SocketClient
{
  // -------------------------------------------------------
  // main
  // Метод, получающий управление при запуске приложения
  // -------------------------------------------------------
  public static void main(String args[])
  {
    // Массив для ввода строки с клавиатуры
    byte bKbdInput[] = new byte[256];

    // Сокет для связи с сервером
    Socket s;

    // Входной поток для приема данных от сервера
    InputStream is;

    // Выходной поток для передачи данных серверу
    OutputStream os;

    try
    {
      // Выводим строку приглашения
      System.out.println("Socket Client Application" +
        "\nEnter any string or 'quit' to exit...");
    }
    catch(Exception ioe)
    {
      // При возникновении исключения выводим его описание
      // на консоль
      System.out.println(ioe.toString());
    }
    
    try
    {
      // Открываем сокет
      s = new Socket("localhost",9999);

      // Создаем входной поток для приема данных от сервера
      is = s.getInputStream();

      // Создаем выходной поток для передачи данных серверу
      os = s.getOutputStream();

      // Буфер для передачи данных
      byte buf[] = new byte[512];

      // Размер принятого блока данных
      int length;

      // Рабочая строка
      String str;

      // Вводим команды и передаем их серверу
      while(true)
      {
        // Читаем строку команды с клавиатуры
        length = System.in.read(bKbdInput);
        
        // Если строка не пустая, обрабатываем ее
        if(length != 1)
        {
          // Преобразуем строку в формат String
          str = new String(bKbdInput, 0);

          // Обрезаем строку, удаляя символ конца строки
          StringTokenizer st;
          st   = new StringTokenizer(str, "\n");
          str = new String((String)st.nextElement());

          // Выводим передаваемую строку команды 
          // на консоль для контроля
          System.out.println(">  " + str);

          // Записываем строку в выходной поток, 
          // передавая ее таким образом серверу
          os.write(bKbdInput, 0, length);
          
          // Сбрасываем буфер выходного потока
          os.flush();

          // Принимаем ответ сервера
          length = is.read(buf);
          if(length == -1)
            break;

          // Отображаем принятую строку на консоли
          str = new String(buf, 0);
          st   = new StringTokenizer(str, "\n");
          str = new String((String)st.nextElement());
          System.out.println(">> " + str);

          // Если введена строка 'quit', завершаем 
          // работу приложения
          if(str.equals("quit"))
            break;
        }
      }

      // Закрываем входной и выходной потоки
      is.close();
      os.close();

      // Закрываем сокет
      s.close();
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
    
    try
    {
      System.out.println(
        "Press <Enter> to terminate application...");

      System.in.read(bKbdInput);
    }
    catch(Exception ioe)
    {
      System.out.println(ioe.toString());
    }
  }
}

Описание исходного текста клиентского приложения SocketClient

Внутри метода main клиентского приложения SocketClient определены переменные для ввода строки с клавиатуры (массив bKbdInput), сокет s класса Socket для работы с сервером SocketServ, входной поток is и выходной поток os, которые связаны с сокетом s.

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


s = new Socket("localhost",9999);

В процессе отладки мы запускали сервер и клиент на одном и том же узле, поэтому в качестве адреса сервера указана строка “localhost”. Номер порта сервера SocketServ равен 9999, поэтому мы и передали конструктору это значение.

После создания сокета наше клиентское приложение создает входной и выходной потоки, связанные с этим сокетом:


is = s.getInputStream();
os = s.getOutputStream();

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

Этот обмен выполняется в цикле, условием завершения которого является ввод пользователем строки “quit”.

Внутри цикла приложение читает строку с клавиатуры, записывая ее в массив bKbdInput:


length = System.in.read(bKbdInput);

Количество введенных символов сохраняется в переменной length.

Далее если пользователь ввел строку, а не просто нажал на клавишу <Enter>, эта строка отображается на консоли и передается серверу:


os.write(bKbdInput, 0, length);
os.flush();

Сразу после передачи сбрасывается буфер выходного потока.

Далее приложение читает ответ, посылаемый сервером, в буфер buf:


length = is.read(buf);

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

Если сервер закрыл канал, то метод read возвращает значение -1. В этом случае мы прерываем цикл ввода и передачи строк:


if(length == -1)
  break;

Если же ответ сервера принят успешно, принятые данные записываются в строку str, которая отображается на консоли клиента:


System.out.println(">> " + str);

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


is.close();
os.close();
s.close();
[Назад] [Содеожание] [Дальше]