Главная > Программирование > Программирования сетевых взаимодействий в Java
<< Предыдущий параграф
Следующий параграф >>
<< Предыдущий параграф Следующий параграф >>
Макеты страниц

3.2. Приложение, реализующее клиент-серверную архитектуру

В данном примере рассматривается упрошенный пример приложения, реализующего клиент-серверные взаимодействия в сети с использованием сокетных соединений. Назначение приложения - обмен текстовыми сообщениями между сервером и клиентом. Переданные сообщения сохраняются в файлах данных, как на стороне сервера, так и на стороне клиента. Обе части приложения имеют похожий графический интерфейс и показаны на рис. 3.1 и рис. 3.2.

Рис. 3.1. Окно сервера

Рис. 3.2. Окно клиента

Рассмотрим серверную часть приложения. Выполним импорт необходимых java-пакетов:

import java.net.*; //классы для работы с сетью

import java.io.*; //классы для взаимодействия с файлами

Объявим класс, реализующий сервер следующим образом:

public class ServerForm extends java.awt.Frame implements Runnable

Класс сервера ServerForm является наследником от стандартного класса Frame и реализует интерфейс Runnable. Реализация интерфейса Runnable необходима, если мы хотим, чтобы класс обеспечивал свою функциональность в параллельно выполняемых подпроцессах. Объявим константу, задающую номер порта, через который будет осуществлятся обмен данными:

public final static int PORT = 220;

Объявим следующие переменные класса ServerForm:

Socket sock = null; //Ссылка на сокетное соединение

BufferedReader in = null; //Буферизованный поток ввода

FileWriter fw = null; //Файловый поток вывода

BufferedWriter out = null;// Буферизованный поток вывода

Thread t = new Thread(this); //Новый подпроцесс выполнения

Буферизованные потоки BufferedWriter и BufferedReader будем использовать для обмена данными между сервером и клиентом. FileWrite для записи полученных сообщений в файл данных. Для того чтобы отделить процесс чтения данных из сети от основного процесса выполнения серверного приложения, нам потребуется новый подпроцесс. Для этого мы и создали объект t класса Thread.

Создадим метод connect(), реализующий сокетное соединение с клиентом.

public void connect() {

В процессе установления сокетных соединений возможно появление различных исключительных ситуаций. Поэтому защитим код от возможных исключений, добавив оператор try и реализуя блок catch (Exception ex).

Создадим серверный сокет и вызовем метод accept() для ожидания соединения со стороны клиента:

ServerSocket ss = new ServerSocket(PORT);

sock = ss.accept();

Получаем потоки ввода-вывода из созданного сокетного соединения:

in = new BufferedReader(new InputStreamReader(sock.getInputStream()));

out = new BufferedWriter(new OutputStreamWriter(sock.getOutputStream()));

Запускаем на исполнение параллельный подпроцесс чтения данных:

t.start();

Открываем файл данных, для записи полученных сообщений:

fw = new FileWriter("messageServ.txt");

Реализуем метод run() интерфейса Runnable.

public void run(){

Объявим строковую переменную, для хранения полученного сообщения:

String indata_;

В процессе получения данных через поток ввода возможно появление исключительных ситуаций. Поэтому защитим код от возможных исключений, добавив оператор try и реализуя блок catch (Exception ex).

Будем читать полученные данные в следующем цикле:

while(true){

Запишем полученное сообщение в строку, прочитав его из потока ввода методом readLine(). Данный метод позволяет читать сообщение в виде строки текста, что является несомненным достоинством:

indata_ = in.readLine();

Отобразим сообщение в компоненте типа List:

list1.add(indata_);

Запишем сообщение в файл:

fw.write(indata_);

Реализуем методы-обработчики события ActionPerformed для компонентов-кнопок «Connect», «Send» и «Close». По щелчку на кнопке «Connect» будет вызван следующий обработчик, в котором производится вызов метода connect():

private void button2ActionPerformed(java.awt.event.ActionEvent evt) {

    connect();

}

По щелчку на кнопке «Send» будет вызван следующий обработчик:

private void button1ActionPerformed(java.awt.event.ActionEvent evt) {

    try {

        //читаем введенную строку сообщения

        String data = textField1.getText();

        //записывем сообщение в выходной поток

        out.write(data);

        out.newLine();

        //очищаем буфер

        out.flush();

    }

    catch(Exception e) {}

}

По щелчку на кнопке «Close» необходимо будет закрыть соединение, все потоки и файлы, вызвав следующий обработчик:

private void button3ActionPerformed(java.awt.event.ActionEvent evt) {

    // Закрываем файл данных, потоки и соединение

    try{

        fw.close();

        in.close();

        out.close();

        sock.close();

    }

    catch(Exception e) {}

}

Полный код серверной части приведен в приложении 3.

Рассмотрим клиентскую часть приложения. Как и в классе сервера выполним импорт java-такетов:

import java.net.*; //классы для работы с сетью

import java.io.*; //классы для взаимодействия с файлами

Объявим класс, реализующий клиента следующим образом:

public class ClientForm extends java.awt.Frame implements Runnable

Класс сервера ClientForm также является наследником от стандартного класса Frame и реализует интерфейс Runnable. Объявим следующие переменные класса ClientForm:

Socket ss = null; //Ссылка на сокетное соединение

BufferedReader in = null; //Буферизованный поток ввода

FileWriter fw = null; //Файловый поток вывода

BufferedWriter out = null;// Буферизованный поток вывода

Буферизованные потоки BufferedWriter и BufferedReader будем использовать для обмена данными между клиентом и сервером. FileWrite для записи полученных сообщений в файл данных. Для того чтобы отделить процесс чтения данных из сети от основного процесса выполнения клиентского приложения, нам потребуется новый подпроцесс. Для этого создадим объект process1 класса Thread.

Thread process1 = new Thread(this); //Новый подпроцесс выполнения

Создадим метод connect() для установления соединения клиента с сервером.

public void connect() {

    try {

        //Устанавливаем соединение с локальным хостом

        InetAddress addr = InetAddress.getLocalHost();

        ss = new Socket(addr, 220);

        //Получаем потоки ввода_вывода

        in = new BufferedReader(new InputStreamReader(ss.getInputStream()));

        out = new BufferedWriter(new OutputStreamWriter(ss.getOutputStream()));

        //Запускаем новый подпроцесс

        process1.start();

        //Открываем файл

        fw = new FileWriter("messageCli.txt");

    }

    catch(Exception e){

        System.out.println("ошибка ");

    }

}

Обратите внимание, что в данном примере мы устанавливаем соединение с локальным хостом, получая его Интернет-адрес с помощью метода getLocalHost() класса InetAddress. В случае установления соединения с удаленным хостом, нам потребовалось бы вызвать метод getByName("наименование хоста"). Также обратите внимание, что, мы открываем сокетное соединение по порту с номером 220, именно с тем номером порта, с которым создавали серверный сокет в серверной части приложения. Реализуем метод run() интерфейса Runnable.

public void run(){

    String s1; //строка для хранения текущего сообщения

    try{

        //В цикле читаем данные из потока

        while(true){

            //записываем данные из потока в строку

            s1 = in.readLine();

            //Отображаем в компоненте List

            list1.add(s1);

            //Записываем в файл

            fw.write(s1);

        }

    }

    catch(Exception e) {}

}

Реализуем методы-обработчики события ActionPerformed для компонентов-кнопок «Connect», «Send» и «Close». По щелчку на кнопке

«Connect» будет вызван следующий обработчик, в котором производится вызов метода connect():

private void button1ActionPerformed(java.awt.event.ActionEvent evt) {

    connect();

}

По щелчку на кнопке «Send» будет вызван следующий обработчик:

private void button2ActionPerformed(java.awt.event.ActionEvent evt) {

    //Получаем строку от пользователя и пересылаем серверу

    try {

        String data = textField1.getText();

        out.write(data);

        out.newLine();

        out.flush();

    }

    catch(Exception e){}

}

По щелчку на кнопке «Close» необходимо будет закрыть соединение, все потоки и файлы, вызвав следующий обработчик:

private void button3ActionPerformed(java.awt.event.ActionEvent evt) {

    //Закрываем файл, потоки и соединение

    try{

        fw.close();

        in.close();

        out.close();

        ss.close();

    }

    catch(Exception e) {}

}

Полный код клиентской части приведен в приложении 2.

<< Предыдущий параграф Следующий параграф >>