Создание приложений "клиент-сервер" в Java

Цель занятия

Научиться создавать приложения на основе технологии "клиентсервер". Изучить способ описания потока клиента и потока сервера. Освоить технику передачи данных от сервера клиенту. За подробной информацией следует обращаться в [15].

Краткие теоретические сведения

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

Для установления связи между клиентом и сервером в программе как клиента, так и сервера нужно создать сокетное (socket — розетка (гнездо)) соединение.

В клиенте это делается так:

class clientThread extends Thread {

DataInputStream dis=null;

Socket s=null; public clientThread()

{

try{

s=new Socket("127.0.0.1",2525);

Необходимо объявить переменную типа socket и затем создать соответствующий объект командой:

s=new Socket("127.0.0.1",2525);

Параметрами конструктора являются сетевой адрес "127.0.0.1" и номер порта 2525, задаваемый произвольно.

( Замечание ^

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

Номер порта, устанавливаемый в команде, не должен совпадать с некоторыми стандартными портами, например, с номером 80. Можно узнать сетевое имя и сетевой адрес компьютера Это выполняется таким образом: My Computer (Мой компьютер) | <npa- вая кнопка мыши> | Properties (Свойства) | Network Identification (Сетевая идентификация), а затем по имени компьютера определить его сетевой номер, введя в командной строке меню Start (Пуск) | Run (Выполнить инструкцию):

ping сетевое_имя_компьготера

На сервере создается поток, который также объявляет сокетное соединение:

ServerSocket server;

String amountstring; static int amount=200; public void run()

{

try

{

server= new ServerSocket(2525);

}

catch(Exception e)

{

System.out.println("ERRSOCK+"+e);

}

Небольшое отличие от клиента: используется класс и конструктор ServerSocket.

После создания сокетного соединения нужно дождаться фактического соединения с сервером. Сервер все время активен и прослушивает сокет. Вот как это делается в программе:

while(true)

{

Socket s=null; try

{ s=server.accept(); } catch(Exception e)

{System.out.println("ACCEPTER"+e);}

try

{ (что-то делается с реальным сокетом s)}

Реальное подключение клиента к серверу реализует команда s=server.accept(), которая устанавливает "физический" контакт при наличии сообщения от клиента. Заметим, что приложение сервера "замирает" на команде s=server.accept(), ожидая подключения клиента.

Как только такое соединение установлено, нужно установить канал ввода-вывода между сервером и клиентом. В работе сервер посылает клиенту данные о счете, моделируемые как случайные числа. Значит, сервер в системе ввода-вывода должен записать данные в сокет, а клиент — их прочитать. Вот как это делается на стороне сервера:

class Account extends Thread {

ServerSocket server;

String amountstring; static int amount=200;

public void run()

{

try

{

server= new ServerSocket(2525); // Номер сокета 2525

// выбран произвольно

}

catch(Exception e)

{ System.out.println("ERRSOCK+"+e); }

while(true)

{

Socket s=null; try {

s=server.accept(); // Ожидание соединения с клиентом

catch(Exception e)

{System.out.println("ACCEPTER"+e);}

try

{

PrintStream ps=new PrintStream(s.getOutputStream());

// Класс PrintStream предназначен для текстового вывода int amountcur=((int)(Math.random()*1000));

// Определяется

// случайная величина текущего вклада с учетом знака;

// отрицательный вклад – это снятие части денег со счета if (Math.random()>0.5) amount-=amountcur; // Сумма на счете изменяется случайно else

amount+=amount cu r;

Integer x=new Integer(amount);

amountstring=x.toString(); // Преобразование целого числа

// в строку

ps.println("Account:"+amountstring); // Здесь выполняется

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

ps.flush();

s.close (); // Сокетное соединение закрывается

}

catch(Exception e)

{

System.out.println("PSERROR"+e);

}

}

Поток вывода для сервера создается командой:

PrintStream ps=new PrintStream(s.getOutputStream());

Объект ps будет держать все методы вывода, включая главный:

ps.println("Account:"+amountstring);

На счет добавляется случайная величина:

amount=((int)(Math.random()*1000));

Класс Math содержит метод random() для генерации случайного числа от 0 до 1. Мы видим, что значение Math.random()*l000 не является в общем случае целым числом, поэтому выполняем явное преобразование типа, записав перед Math.random()*iooo ключевое слово (int). Добавление или уменьшение суммы в переменной amount выполняется путем внесения или снятия случайной величины со счета на основании проверки:

int amountcur=((int)(Math.random()*1000));

// Определяется случайная величина текущего вклада с учетом // знака; отрицательный вклад – это снятие части денег // со счета if (Math.random()>0.5) amount-=amountcur; // Сумма на счете изменяется случайно else

amount+=amount cu r ;

Обратим внимание на то, что в конце цикла (заметим, бесконечного) реальное соединение сервера и клиента разрывается:

ps.flush(); // Эта команда выполняет выталкивание // содержимого буфера вывода s.close(); // Сокетное соединение закрывается

Теперь о реализации сервера. Он написан не как апплет, а как приложение Java с главным методом:

public static void main(String args[])

{

serv f=new serv(); f. resize (400, 400) ; f.show();

new Account().start();

}

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

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

public class serv extends Frame {

public boolean handleEvent(Event evt) // Используется

// обработчик событий // ранних версий Java

{

if (evt.id==Event.WINDOW_DESTROY) // Попытка закрыть окно

// приложения

{System.exit(0);} return super.handleEvent(evt);

}

public boolean mouseDown(Event evt,int x,int у)

// Обработчик события от мыши {

new clientThread().start (); // Запуск клиента return (true) ;

}

public static void main(String args[])

{

serv f=new serv(); f. resize (400, 400) ; f.show();

new Account().start();

Разберемся с этим классом. Главный метод main() стартует сразу при запуске приложения. Он создает окно приложения:

serv f=new serv(); f. resize (400, 400) ; f.show();

и запускает поток-сервера:

new Account() .start ();

Переменная f имеет тип serv, который базируется на оконном типе Frame. Наш базовый класс реализует следующие методы. Первый — обработка события закрытия окна:

public boolean handleEvent(Event evt)

{

if (evt.id==Event.WINDOW_DESTROY)

{System.exit(0);} return super.handleEvent(evt);

}

Этот метод используется для анализа и локальной обработки событий, объявляемых как Event evt. Тип события проверяется командой

if (evt.id==Event.WINDOW_DESTROY)

т. e. анализируется событие закрытия окна сервера. Заметим, что сервер следует закрыть явно в программе, иначе он останется активным. Команда закрытия сервера:

System.exit(0);

Второй метод рассматриваемого класса:

public boolean mouseDown(Event evt,int x,int у)

{

new clientThread().start(); return (true) ;

}

реагирует на щелчок мыши в окне сервера. В предыдущих работах мы использовали для перехвата событий от мыши методы интерфейсов ActionListener, MouseListener. В ЭТОЙ работе показан другой вариант, характерный для первых версий Java. По щелчку мыши в окне сервера запускается клиент:

new clientThread().start();

Клиент моделируется своим собственным потоком. И сервер, и клиент в данной программе существуют как отдельные потоки, функционирующие в одном общем приложении. Поток клиента определен следующим образом:

class clientThread extends Thread {

DataInputStream dis=null;

Socket s=null; public clientThread()

{

try

{

s=new Socket("127.0.0.1",2525);

dis= new DataInputStream(s.getInputStream());

}

catch(Exception e)

{ System.out.println("ERROR:"+e); }

}

public void run()

{

while (true)

{

try

{sleep(100); } catch(Exception er)

{System.out.println("BDD"+er); } try {

String msg=dis.readLine(); if(msg==null) break;

catch(Exception e)

{System.out.println("ERRORR+"+e); }

}

}

}

Инициализация клиента выглядит таким образом:

DataInputStream dis=null;

Socket s=null; public clientThread()

{

try

{

s=new Socket("127.0.0.1",2525);

dis= new DataInputStream(s.getInputStream());

}

Клиент пытается получить сокет socket("i27.0.0.i",2525). Это именно тот же сокет, куда пишет данные сервер. Заметим, что клиент должен указать сетевой адрес компьютера, где расположен сервер (этот адрес называется IP-адресом и представляет собой последовательность номеров, разделенных точкой, или сетевое имя; в любом случае адрес указывается как строковый аргумент в кавычках). Клиенту нужна объектная переменная dis для чтения данных из сокета. Клиент читает данные из сокета в своем методе run ():

public void run()

{

while (true)

{

try

{sleep(100); // Клиент выполняет попытку чтения из сокета // каждые 100 миллисекунд

catch(Exception er)

{System.out.println("BDD"+er); } try {

String msg=dis.readLine(); // Здесь клиент пытается

// прочитать строку из сокета

if (msg==null) break;

System.out.println(msg); // Прочитанная строка выводится

// на консоль

}

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

| Листинг 2.20. Клиент и сервер в одном приложении

import java.io.*; import java.net.*; import java.awt.*; import java.lang.*; class clientThread extends Thread { //Потоковый класс клиента DataInputStream dis=null; // Клиент читает данные из сервера

// на базе DataInputStream

Socket s=null; public clientThread()

try

{

s=new Socket("127.0.0.1",2525); // Создаем сокет для

// клиента

dis= new DataInputStream(s.getInputStream());

//dis используется для чтения из сокета клиента

}

catch(Exception e)

{ System.out.println("ERROR:"+e);}

{

while (true)

{

try

{

sleep(100); // Клиент подключается к сокету каждые // 100 миллисекунд

}

catch(Exception er)

{System.out.println("BDD"+er) ; } try {

Stringmsg=dis.readLine(); // Чтение сообщения от сервера if(msg==null) break;

System.out.println(msg); // Вывод прочитанного сообщения

}

catch(Exception e)

{System.out.println("ERRORR+"+e); }

}

}

}

class Account extends Thread { // Потоковый класс сервера ServerSocket server;

String amountstring; static int amount=200; public void run()

{

try

{

server= new ServerSocket(2525); // Сокет сервера

catch(Exception e)

{ System.out.println("ERRSOCK+"+e); }

while(true)

{

Socket s=null; try {

s=server.accept(); // Прослушивает подключение к сокету // клиента

}

catch(Exception e)

{System.out.println("ACCEPTER"+e);}

try

{

PrintStream ps=new PrintStream(s.getOutputStream()); int amountcur=((int)(Math.random()*1000));

// Определяется случайная величина текущего вклада // с учетом знака; отрицательный вклад – это снятие // части денег со счета if (Math.random()>0.5) amount-=amountcur; // Сумма на счете изменяется случайно else

amount+=amount cu r;

Integer x=new Integer(amount); amountstring=x.toString();

ps.println("Account:"+amountstring); // Выводит в сокет

// сообщение для клиента

ps.flush(); s.close () ;

}

catch(Exception e)

{System.out.println("PSERROR"+e); }

public class serv extends Frame { // Форма сервера

public boolean handleEvent(Event evt)

{

if (evt.id==Event.WINDOW_DESTROY)

{System.exit(0);} return super.handleEvent(evt);

}

public boolean mouseDown(Event evt,int x,int y)

{

new clientThread().start(); // Поток клиента порождается

// щелчком мыши в окне сервера

return (true) ;

}

public static void main(String args[])

{

serv f=new serv(); f. resize (400, 400) ; f.show();

new Account().start();

}

}

Замечание

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

Задание

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

1.             Написать приложение для двух клиентов: клиента-мужа и клиента-жены, которые работают с одним счетом. Каждому клиенту соответствует свой поток.

2.             Создать приложение "клиент-сервер" с некоторым визуальным интерфейсом, который выполняет запуск клиента по нажатию кнопки и отражает состояние счета в текстовом поле (см. приведенные далее дополнительные сведения о создании визуального интерфейса для приложений).

3.             Написать приложение "клиент-сервер" как раздельные программы.

Далее помещены дополнительные сведения к настоящему практическому занятию, расширяющие функциональные возможности клиента и сервера. Второе приложение (листинги 2.22 и 2.23) демонстрирует раздельную реализацию клиента и сервера.

Дополнительные сведения

Приведенный далее вариант программы (листинг 2.21) запускает поток клиента по нажатию кнопки (это есть единственное отличие от приведенного ранее приложения "клиент-сервер"). Кнопки и другие визуальные элементы добавляются в конструкторе основного класса приложения, который в общем случае имеет в качестве родительского класса Frame. Вот пример того, как это делается:

public class servcopy extends Frame {

Button b=new Button("ActivateClient"); public servcopy(String title)

{

super(title); setLayout(null) ; add (b) ;

b.setBounds(150,50,100,20);

}

Для прослушивания кнопки в классе servcopy помещен стандартный метод:

public boolean action(Event evt, Object arg)

{ if (evt.target==b)

new clientThread().start(); return (true) ;

Впрочем, можно было бы организовать прослушивание кнопки и уже известным нам интерфейсом ActionListener. Оператор if (evt. target==b) просто выясняет, от какого элемента пришло событие (в нашем примере ь — это имя кнопки).

Листинг 2.21. Запуск клиента по нажатию кнопки

import java.io.*; import java.net.*; import java.awt.*; import java.lang.*;

// Клиент не изменился

class clientThread extends Thread

DataInputStream dis=null;

Socket s=null; public clientThread()

try

{

s=new Socket("127.0.0.1",2525);

dis= new DataInputStream(s.getInputStream());

}

catch(Exception e)

{ System.out.println("ERROR:"+e);}

public void run() while (true) try

{ sleep(100); } catch(Exception er)

{System.out.println("BDD"+er); }

try

{

String msg=dis.readLine(); if(msg==null) break;

System.out.println(msg);

}

catch(Exception e)

{System.out.println("ERRORR+"+e); }

}

}

}

class Account extends Thread {

ServerSocket server;

String amountstring; static int amount=200; public void run()

{

try

{ server= new ServerSocket(2525); } catch(Exception e)

{ System.out.println("ERRSOCK+"+e); }

while(true)

{

Socket s=null; try

{ s=server.accept(); } catch(Exception e)

{System.out.println("ACCEPTER"+e);}

try

PrintStream ps=new PrintStream(s.getOutputStream());

int amountcur=((int)(Math.random()*1000));

// Определяется случайная величина текущего вклада // с учетом знака; отрицательный вклад – это снятие части // денег со счета if (Math.random()>0.5) amount-=amountcur; // Сумма на счете изменяется случайно else

amount+=amount cu r;

Integer x=new Integer(amount); amountstring=x.toString(); ps.println("Account:"+amountstring); ps.flush(); s.close () ;

}

catch(Exception e)

{System.out.println("PSERROR"+e); }

}

}

}

// Основной класс претерпел изменения в связи с добавлением // кнопки

public class servcopy extends Frame {

Button b=new Button("ActivateClient"); // Добавлена кнопка

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

public servcopy(String title)

{

super(title);

setLayout(null) ;

add (b) ;

b.setBounds(150,50,100,20);

public boolean handleEvent(Event evt)

{

if (evt.id==Event.WINDOW_DESTROY)

{System.exit(0);} return super.handleEvent(evt);

}

public boolean action(Event evt, Object arg)

{

if (evt.target==b) new clientThread().start (); // Клиент запускается по

// нажатию кнопки

return (true) ;

}

public static void main(String args[])

{

servcopy f=new servcopy("MyFrame"); f. resize (400, 400) ; f.show();

new Account().start();

}

}

Второй вариант реализации приложений "клиент-сервер" предусматривает их автономность: клиент реализуется в одном приложении, сервер — вдругом.

Приведем пример приложения "клиент-сервер" с раздельными программами для сервера и клиента. Обе программы реализованы как потоки. Поток сервера передает клиенту строку. Поток клиента считывает строку из сокета и отображает ее в текстовом поле. Обратим внимание на то, что сокетное соединение на стороне сервера поддерживается в бесконечном цикле. В потоке клиента сокетное соединение устанавливается всякий раз, как только получено очередное сообщение.

Приложение сервера имеет следующий вид (листинг 2.22).

Листинг 2.22. Отдельное приложение сервера

import java.io.*; import java.net.*; import java.awt.*; import java.util.*; class serverThread extends Thread { // Поток сервера static ServerSocket server; static int n=0; serverThread()

try

{ server=new ServerSocket(2525);} catch(IOException e)

{ System.out.println("ERROR:"+e); }

public void run() // Главный метод потока сервера while(true) n=n+l; // n – номер сообщения

lab4Server.label.setText(""+n); // Номер сообщения заносим

// в текстовое поле

Socket s=null;

Try

{

s=server.accept(); // Соединяемся с клиентом if (s!=null)

lab4Server.label.setText("ACCEPTED SOCKET "+n);

// Подтверждаем соединение

}

}

catch(IOException e)

{ System.out.println("ERROR:"+e); } try {

PrintStream ps=new PrintStream(s.getOutputStream());

int x=(int) (Math.random()*1000);

lab4Server.label.setText("sended"+ n);

ps.println("Hello, from server::"+n);// Эту строку

// отсылаем клиенту

ps.flush();

s.close(); // Разрываем соединение

// сообщение на стороне клиента

}

catch(IOException e)

{ System.out.println("ERROR:"+e); }

}

}

}

public class lab4Server extends Frame // Объявляем главное

// окно сервера

{

static Label label=new Label("INFO");

Button b=new Button("EXIT"); // Кнопка для завершения

// приложения сервера

static int n=0; lab4Server()

{ // Конструктор приложения сервера add(label);

add (b) ;

b.setBounds(10,100,100,20); label.setBounds(10, 60,350, 20);

serverThread sf= new serverThread(); // Объявляем поток

// сервера

sf.start();// Поток сервера запущен

public boolean action(Event evt,Object ob)

if (evt.target instanceof Button)

{ System.exit(0); return false; } return true;

public static void main(String args[])

lab4Server f= new lab4Server(); f.setLayout(null); f.resize(500,400); f.show();

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

Приложение клиента представлено в листинге 2.23.

Листинг 2.23. Отдельное приложение клиента

import java.awt.*; import java.net.*; import java.applet.*;

class ClientThread extends Thread // Поток клиента мало // изменился; в основной класс клиента добавлен метод sh() для // показа принятой от сервера строки {

// Потоковая переменная для чтения строки из сокета: DataInputStream dis=null;

Socket s=null;

String sl="HELLO FROM CLIENT"; static int z=0;

public void run() // Главный метод потока клиента {

while(z<=40)

{

z+=l ; try {

s=new Socket("127.0.0.1",2525); // Клиент подключается к

// серверу

dis=new DataInputStream(s.getInputStream());

}

catch (Exception e)

{

sl="Error in SocInet ::"+e; lab4Client.sh(sl) ;

}    // Обратите внимание на вызов статического метода sh ()

// класса lab4Client

try

{

sleep(1000);

catch(InterruptedException el)

{

s1="sleepERROR"+e1; lab4Client.sh(sl);

}

try

{

sl=dis.readLine(); // Клиент читает строку из сокета if (sl=null)

{

sl="No information transmitted ";

lab4Client.sh(sl); // Вьшод принятой строки в текстовое // поле

continue;

}

lab4Client.sh(sl);

}

catch (IOException e)

{

sl="ERROR IN 10 PROC."+e; lab4Client.sh(sl);

}

}

}

}

// Основной класс приложения клиента public class lab4Client extends Frame {

static Label label=new Label("For Server Info");

// Переменная label используется в статическом методе sh (),

// поэтому объявляется как static lab4Client() // Конструктор клиента {

super("MYCLIENT");

add(label);

setLayout(null) ; label.setBounds(10,100,250, 20);

ClientThread myclient = new ClientThread(); // Объявление

// потока клиента

myclient.start(); // Запуск потока клиента

}

public static void main(String args[])

{

lab4Client f=new lab4Client(); f. resize (400, 400) ; f.show();

}

public static void sh(String msg) // метод sh() объявлен как

// static

{

label.setText(msg);

}

} //end of lab4Client definition

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

Источник: Герман О. B., Герман Ю. О., Программирование на Java и C# для студента. — СПб.: БХВ-Петербург, 2005. — 512 c.: ил.

Вы можете следить за любыми ответами на эту запись через RSS 2.0 ленту. Вы можете оставить ответ, или trackback с вашего собственного сайта.

Оставьте отзыв

XHTML: Вы можете использовать следующие теги: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

 
Rambler's Top100