Сети и многопоточность. Создание многопоточного эхо сервера

Основы работы с сетями в Java на примере создания сервера Echo и клиента для этого сервера. Многопоточный сервер, обслуживающий одновременно несколько клиентов. Клиентское приложение с полем для ввода данных для отправки и текстовым полем (JTextArea).

Рубрика Программирование, компьютеры и кибернетика
Вид лабораторная работа
Язык русский
Дата добавления 30.06.2009
Размер файла 57,4 K

Отправить свою хорошую работу в базу знаний просто. Используйте форму, расположенную ниже

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

2

Факультет "Информатика и системы управления"

Методические указания к лабораторной работе

по курсу "Распределенные системы обработки информации"

Сети и многопоточность. Создание многопоточного эхо сервера.

Москва 2004 г.

Цель работы

Получить знания об основах работы с сетями в Java на примере создания сервера Echo и клиента для этого сервера (вместо клиента можно использовать утилиту telnet).

Задания к лабораторной работе

Создать Echo сервер.

1. Создать ЕХО сервер, которые будет получать данные от клиента в виде строк и будет возвращать обратно те же строки, но дописывать к ним в начале «Echo:».

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

3. Для завершения работы клиент должен послать строку «BYE».

Создать клиента для Echo сервера.

1. Клиентское приложение должно иметь поле для ввода данных для отправки.

2. Клиентское приложение должно иметь текстовое поле (JTextArea), в котором должны отображаться отправляемые и получаемые данные.

Контрольные вопросы

1. Почему на сервере желательно выполнять ожидание (вызывать метод java.net.ServerSocket#accept()) подключения клиентов в потоке, отличном от главного?

2. Какие способы какие конструкторы класса java.net.Socket вы знаете? Чем они отличаются (по действиям, которые они выполняют).

Литература

1. Серия «Библиотека профессионала» К. Хорстманн Г.Корнелл «Java 2. том 2 «Тонкости программирования»» «Издательство Вильямс» 2002 г.

2. Sun Java 2 SE API. www.java.sun.com

3. Thinking in Java, 2nd ed. by Bruce Eckel, http://fp.nsk.fio.ru/vova/doc/doc/Java/BE/Contents.html

Сетевое программирование

Одним из больших достижений Java является безболезненное общение в сети. Проектировщики сетевой библиотеки Java сделали ее похожей на чтение и запись файлов, за исключением того, что этот “файл” находится на удаленной машине и удаленная машина может в точности определить, что нужно сделать с информацией, которую Вы посылаете либо требуете. Насколько возможно, внутренние детали сетевого общения абстрагированы и обрабатываются внутри JVM и локальной машины, на которой установлена среда Java. Модель программирования такая же, как Вы используете в файле; в действительности, Вы просто окутываете сетевое соединение (“сокет”) потоковым объектом, и Вы действуете используя те же вызовы методов, что и с другими потоковыми объектами. К тому же, встроенная в Java множественность процессов очень удобна при общении в сети: установка нескольких соединений сразу.

Идентификация машины

Конечно, чтобы убедиться, что соединение установлено с конкретной машиной в сети, должен быть способ уникальной идентификации машины в сети. Раньше при работе в сети было достаточно предоставить уникальные имена для машин внутри локальной сети. Но Java работает в Internet, который требует уникальной идентификации машины с любой другой во всей сети по всему миру. Это достигается с помощью IP (Internet Protocol) адресации, которая может иметь две формы:

1. Обычная DNS (Domain Name System) форма. Мое доменное имя - bruceeckel.com, и если у меня есть компьютер с именем Opus в моем домене, его доменное имя может быть Opus.bruceeckel.com. Это в точности тип имени, который Вы используете при отсылке почты, и очень часто включается в WWW адрес.

2. С другой стороны, Вы можете использовать “четырехточечную” форму, в которой четыре номера разделены точками, например 123.255.28.120.

В обоих случаях IP адрес представляется как 32 битное значение (каждое число из 4-х не может превышать 255), и Вы можете получить специальный объект Java для представления этого номера из формы, представленной выше с помощью метода static InetAddress.getByName() в пакете java.net. Результат - это объект типа InetAddress, который Вы можете использовать для создания “сокета”, как Вы позднее увидите.

Простой пример использования InetAddress.getByName() показывает, что происходит, когда у Вас есть провайдер интернет по коммутируемым соединениям (ISP). Каждый раз, когда Вы дозваниваетесь, Вам присваивается временный IP. Но пока Вы соединены, Ваш IP адрес ничем не отличается от любого другого IP адреса в интернет. Если кто-то подключится к Вашей машине, используя Ваш IP адрес, он сможет подключиться также к Web или FTP серверу, который запущен на Вашей машине. Конечно, сначала необходимо узнать Ваш IP адрес, а т.к. при каждом соединении присваивается новый адрес, как Вы сможете его узнать?

Следующая программа использует InetAddress.getByName( ) для определения Вашего IP адреса. Чтобы использовать его, Вы должны знать имя своего компьютера. В Windows 95/98, зайдите в “Settings”, “Control Panel”, “Network”, а затем выберите страничку “Identification”. “Computer name” это имя, которое необходимо задать в командной строке.

//: c15:WhoAmI.java

// Определяет Ваш сетевой адрес

// когда Вы подключены к Internet.

import java.net.*;

public class WhoAmI {

public static void main(String[] args)

throws Exception {

if(args.length != 1) {

System.err.println(

"Usage: WhoAmI MachineName");

System.exit(1);

}

InetAddress a =

InetAddress.getByName(args[0]);

System.out.println(a);

}

}

В этом случае, машина называется “peppy”. Итак, когда я соединяюсь с моим провайдером, я запускаю программу:

java WhoAmI peppy

Я получаю в ответ сообщение подобное этому (конечно адрес каждый раз новый):

peppy/199.190.87.75

Если я сообщу этот адрес моему другу, и у меня будет Web сервер, запушенный на компьютере, они могут соединиться с ним, зайдя на URL http://199.190.87.75 (только пока я остаюсь в этом сеансе связи). Это иногда может быть удобным способом предоставления информации кому-то другому, либо тестирования конфигурации Web сайта перед тем как опубликовать его на “реальном” сервере.

Серверы и клиенты

Основная задача сети - предоставление двум машинам возможности соединиться и общаться друг с другом. Как только две машины нашли друг друга, они могут иметь отличное двухстороннее общение. Но как они находят друг друга? Это как потеряться в парке развлечений: одна машина должна оставаться на месте и слушать, пока другая машина не скажет, “Эй! Где ты?”

Машина, которая “остается на одном месте” называется сервером, а тот, который ищет, называется клиентом. Это различие важно только когда клиент пытается подключиться к серверу. Как только они соединятся, происходит процесс двухстороннего общения и не важно, что один является сервером, а другой - клиентом.

Итак, работа сервера - слушать соединение, и это выполняется с помощью специального серверного объекта, который Вы создаете. Работа клиента - попытаться создать соединение с сервером, что выполняется с помощью специального клиентского объекта. Как только соединение установлено, Вы увидите, что у клиента и сервера соединение магически превращается в потоковый объект ввода/вывода, и с этого момента Вы можете рассматривать соединение как файл, который Вы можете читать, и в который Вы можете записывать. Т.о., после установления соединения Вы будете использовать уже знакомые Вам команды ввода/вывода. Это одно из отличных расширений сетевой библиотеки Java.

Тестирование программ без наличия сети

По многим причинам, Вам может не быть доступна клиентская машина, серверная машина, и вообще сеть для тестирования Вашей программы. Вы можете выполнить упражнения в классной комнате, либо, Вы можете написать программу, которая недостаточно устойчива для работы в сети. Создатели интернет протокола были осведомлены о таких проблемах, и они создали специальный адрес, называемый localhost, “локальная петля”, который является IP адресом для тестирования без наличия сети. Обычный способ получения этого адреса в Java это:

InetAddress addr = InetAddress.getByName(null);

Если Вы ставите параметр null в метод getByName( ), то, по умолчанию используется localhost. InetAddress - это то, что Вы используете для ссылки на конкретную машину, и Вы должны предоставлять это, перед тем как продолжить дальнейшие действия. Вы не можете манипулировать содержанием InetAddress (но Вы можете распечатать его, как Вы увидите в следующем примере). Единственный способ создать InetAddress - это использовать один из перегруженных статических методов getByName( ) (который Вы обычно используете), getAllByName( ), либо getLocalHost( ).

Вы можете создать адрес локальной петли, установкой строкового параметра localhost:

InetAddress.getByName("localhost");

(присваивание “localhost” конфигурируется в таблице “hosts” на Вашей машине), либо с помощью четырехточечной формы для именования зарезервированного IP адреса для петли:

InetAddress.getByName("127.0.0.1");

Все три формы производят одинаковые результаты.

Порт: уникальное место внутри машины

IP адреса недостаточно для индикации уникального сервера, т.к. много серверов может существовать на одной машине. Каждая машина в IP также содержит порты, и когда Вы устанавливаете клиент или сервер Вы должны выбрать порт, по которому сервер и клиент договорились соединиться; если Вы встречаете кого-то, IP адрес это окрестность и порт это бар.

Порт это не физическое расположение в машине, а программная абстракция (большей частью для бухгалтерского назначения). Клиентская программа знает, как соединиться с машиной по IP адресу, но как соединиться с нужной службой (потенциально одной из многих на этой машине)? Вот где номера портов являются вторым уровнем адресации. Идея в том, что Вы запрашиваете конкретный порт, этим запрашивая службу, ассоциированную с этим портом. Время дня это простой пример службы. Обычно, каждая служба ассоциируется с уникальным номером порта на заданной серверной машине. Необходимо знать заранее, на каком порту запущена необходимая служба.

Системные службы резервируют номера портов с 1 по 1024, так что Вы не должны использовать ни один из портов, который Вы знаете, что он используется. Первый выбор порта для примеров в этой книге это порт номер 8080 (в память почтенного и древнего 8-битного чипа Intel 8080 на моем первом компьютере, с операционной системой CP/M).

Сокеты

Сокет - это программная абстракция, используемая для представления “терминалов” соединений между двумя машинами. Для данного соединения, существует сокет на каждой машине, и Вы можете представить гипотетический “кабель” соединяющий две машины, каждый конец которого вставлен в сокет. Конечно, какое аппаратное обеспечение и кабель между ними неизвестно. Основной смысл абстракции в том, что нам не нужно знать больше, чем необходимо.

В Java, Вы создаете сокет для установления соединения с другой машиной, затем Вы получаете InputStream и OutputStream (либо с помощью соответствующих преобразователей, Reader и Writer) из сокета, который соответствующим образом представляет соединение, как потоковый объект ввода вывода. Есть два класса сокетов, основанных на потоках: ServerSocket - используется сервером, чтобы “слушать” входящие соединения, и Socket - используется клиентом для инициирования соединения. Как только клиент создает соединение по сокету, ServerSocket возвращает (с помощью метода accept( )) соответствующий объект Socket по которому будет происходить связь на стороне сервера. Начиная с этого момента, у Вас появляется соединение Socket к Socket, и Вы считаете эти соединения одинаковыми, потому что они действительно одинаковые. В результате, Вы используете методы getInputStream( ) и getOutputStream( ) для создания соответствующих объектов InputStream и OutputStream из каждого Socket. Они должны быть обернуты внутри буферов и форматирующих классов, как и любой другой потоковый объект.

ServerSocket может показаться еще одним примером запутанной схемы имен в библиотеках Java. Вы можете подумать, что ServerSocket лучше назвать “ServerConnector” либо как-нибудь иначе без слова “Socket” в нем. Вы также можете подумать, что ServerSocket и Socket должны быть оба унаследованы от одного из базовых классов. В самом деле, оба класса содержат несколько методов совместно, но этого недостаточно, чтобы дать им общий базовых класс. Взамен, работа ServerSocket ожидать, пока не подсоединится другая машина, и затем возвратить подлинный Socket. Вот почему ServerSocket кажется немного неправильно названным, т.к. его работа в действительности не быт сокетом, а просто создавать объект Socket, когда кто-то другой к нему подключается.

Однако ServerSocket создает физический “сервер” или слушающий сокет на серверной машине. Этот сокет слушает входящие соединения и затем возвращает “установленный” сокет (с определенными локальными и удаленными конечными точками) посредством метода accept(). В замешательство приводит то, что оба этих сокета (слушающий и установленный) ассоциированы с тем же самым серверным сокетом. Слушающий сокет может допустить только запросы на новое соединение, но не пакеты данных. Итак, пока ServerSocket не имеет смысла программного, зато имеет смысл “физический”.

Когда Вы создаете ServerSocket, Вы задаете для него только номер порта. Вам не нужно задавать IP адрес, т.к. он уже существует на машине. Однако когда Вы создаете Socket, Вы должны задать и IP адрес и номер порта машины, с которой Вы хотите соединиться. (Тем не менее, Socket, который возвращается методом ServerSocket.accept(), уже содержит всю эту информацию.)

Простой пример сервера и клиента

Этот пример показывает простую работу сервера и клиента, используя сокеты. Все, что делает сервер, - это просто ожидание соединения, затем использует Socket полученный из того соединения для создания InputStream и OutputStream. Они конвертируются в Reader и Writer, затем в BufferedReader и PrintWriter. После этого все, что он получает из BufferedReader, он отправляет на PrintWriter пока не получит строку “END,” после чего, он закрывает соединение.

Клиент устанавливает соединение с сервером, затем создает OutputStream и выполняет те же операции, что и сервер. Строки текста посылаются через результирующий PrintWriter. Клиент также создает InputStream (снова, с соответствующими конверсиями и облачениями) чтобы слушать, что говорит сервер (а в нашем случае он возвращает слова назад).

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

Вот сервер:

//: c15:JabberServer.java

// Очень простой сервер, который только

// отображает то, что посылает клиент.

import java.io.*;

import java.net.*;

public class JabberServer {

// Выбираем номер порта за пределами 1-1024:

public static final int PORT = 8080;

public static void main(String[] args)

throws IOException {

ServerSocket s = new ServerSocket(PORT);

System.out.println("Started: " + s);

try {

// Блокируем пока не произойдет соединение:

Socket socket = s.accept();

try {

System.out.println(

"Connection accepted: "+ socket);

BufferedReader in =

new BufferedReader(

new InputStreamReader(

socket.getInputStream()));

// Вывод автоматически обновляется

// классом PrintWriter:

PrintWriter out =

new PrintWriter(

new BufferedWriter(

new OutputStreamWriter(

socket.getOutputStream())),true);

while (true) {

String str = in.readLine();

if (str.equals("END")) break;

System.out.println("Echoing: " + str);

out.println(str);

}

// всегда закрываем оба сокета...

} finally {

System.out.println("closing...");

socket.close();

}

} finally {

s.close();

}

}

} ///:~

Вы видите, что объекту ServerSocket нужен только номер порта, не IP адрес (т.к. он запущен на этой машине!). Когда Вы вызываете метод accept(), метод блокирует выполнение программы, пока какой-нибудь клиент не попробует соединиться. То есть, он ожидает соединение, но другие процессы могут выполняться (см. Главу 14). Когда соединение сделано, accept() возвращает объект Socket представляющий это соединение.

Если конструктор ServerSocket завершается неуспешно, программа просто завершается (обратите внимание, что мы должны считать, что конструктор ServerSocket не оставляет открытых сетевых сокетов, если он завершается неудачно). В этом случае main() выбрасывает исключение IOException и блок try не обязателен. Если конструктор ServerSocket завершается успешно, то остальные вызовы методов должны быть окружены блоками try-finally, чтобы убедиться, что независимо от того, как блок завершит работу, ServerSocket будет корректно закрыт.

Та же логика используется для Socket возвращаемого методом accept(). Если вызов accept() неуспешный, то мы должны считать что Socket не существует и не держит никаких ресурсов, так что он не нуждается в очистке. Но если вызов успешный, следующие объявления должны быть окружены блоками try-finally так что в случае неуспешного вызова Socket будет очищен. Заботиться здесь об этом обязательно, т.к. сокеты используют важные ресурсы, располагающиеся не в памяти, так что Вы должны тщательно очищать их (поскольку в Java нет деструктора, чтобы сделать это за Вас).

И ServerSocket, и Socket, созданные методом accept( ), печатаются в System.out. Это значит, что их методы toString( ) вызываются автоматически. Вот что получается:

ServerSocket[addr=0.0.0.0,PORT=0,localport=8080]

Socket[addr=127.0.0.1,PORT=1077,localport=8080]

Скоро Вы увидите, как они объединяются вместе с тем, что делает клиент.

Следующая часть программы выглядит как программа для открытия файлов для чтения и записи за исключением того, что InputStream и OutputStream создаются из объекта Socket. И объект InputStream и объект OutputStream конвертируются в объекты Reader и Writer, используя классы “конвертеры” InputStreamReader и OutputStreamWriter, соответственно. Вы можете также использовать напрямую классы из Java 1.0 InputStream и OutputStream, но с выводом есть явное преимущество при использовании Writer. Это реализуется с помощью PrintWriter, в котором перегруженный конструктор берет второй аргумент, а boolean - флаг, который индицирует, когда какой автоматически сбрасывает вывод в конце каждого вывода println() (но не print()) выражения. Каждый раз, когды Вы направляете данные в out, его буфер должен сбрасываться так информация передается о сети. Сброс важен для этого конкретного примера, т.к. клиент и сервер ждут строку данных друг от друга, перед тем, как что-то сделать. Если сброса буферов не происходит, информация не будет отправлена по сети, пока буфер полон, что вызовем много проблем в этом примере.

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

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

Бесконечный цикл while читает строки из BufferedReader in и записывает информацию вSystem.out and to the PrintWriter out. Запомните, что in и out могут быть любыми потоками, они просто соединены в сети.

Когда клиент отсылает строку, состоящую из “END,” программа прерывает выполнение цикла и закрывает Socket.

Вот клиент:

//: c15:JabberClient.java

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

// и читает строки, которые посылает сервер

import java.net.*;

import java.io.*;

public class JabberClient {

public static void main(String[] args)

throws IOException {

// Установка параметра в null в getByName()

// возвращает специальный IP address - "Локальную петлю",

// для тестирования на одной машине без наличия сети

InetAddress addr =

InetAddress.getByName(null);

// Альтернативно Вы можете использовать

// адрес или имя:

// InetAddress addr =

// InetAddress.getByName("127.0.0.1");

// InetAddress addr =

// InetAddress.getByName("localhost");

System.out.println("addr = " + addr);

Socket socket =

new Socket(addr, JabberServer.PORT);

// Окружаем все блоками try-finally to make

// чтобы убедиться что сокет закрывается:

try {

System.out.println("socket = " + socket);

BufferedReader in =

new BufferedReader(

new InputStreamReader(

socket.getInputStream()));

// Вывод автоматически сбрасывается

// с помощью PrintWriter:

PrintWriter out =

new PrintWriter(

new BufferedWriter(

new OutputStreamWriter(

socket.getOutputStream())),true);

for(int i = 0; i < 10; i ++) {

out.println("howdy " + i);

String str = in.readLine();

System.out.println(str);

}

out.println("END");

} finally {

System.out.println("closing...");

socket.close();

}

}

} ///:~

В методе main() Вы видите все три пути для возврата IP адреса локальной петли: используя null, localhost, либо явно зарезервированный адрес 127.0.0.1. Конечно, если Вы хотите соединиться с машиной в сети, Вы подставляете IP адрес этой машины. Когда InetAddress addr печатается (с помощью автоматического вызова метода toString()) получается следующий результат:

localhost/127.0.0.1

Подстановкой параметра null в getByName( ), она по умолчанию использует localhost, и это создает специальный адрес 127.0.0.1.

Обратите внимание, что Socket, названный socket, создается и с типом InetAddress и с номером порта. Чтобы понимать, что это значит, когда Вы печатаете один из этих объектов Socket, помните, что соединение с Интернет определяется уникально этими четырьмя элементами данных: clientHost, clientPortNumber, serverHost, и serverPortNumber. Когда сервер запускается, он берет присвоенный ему порт (8080) на localhost (127.0.0.1). Когда клиент приходит, распределяется следующий доступный порт на той же машине, в нашем случае - 1077, который, так случилось, оказался расположен на той же самой машине (127.0.0.1), что и сервер. Теперь, необходимо данные перемещать между клиентом и сервером, каждая сторона должна знать, куда их посылать. Поэтому во время процесса соединения с “известным” сервером, клиент посылает “обратный адрес”, так что сервер знает, куда отсылать его данные. Вот, что Вы видите в примере серверной части:

Socket[addr=127.0.0.1,port=1077,localport=8080]

Это значит, что сервер только что принял соединение с адреса 127.0.0.1 и порта 1077, когда слушал свой локальный порт (8080). На стороне клиента:

Socket[addr=localhost/127.0.0.1,PORT=8080,localport=1077]

это значит, что клиент создал соединение с адресом 127.0.0.1 и портом 8080, используя локальный порт 1077.

Вы увидите, что каждый раз, когда Вы запускаете нового клиента, номер локального порта увеличивается. Отсчет начинается с 1025 (предыдущие являются зарезервированными) и продолжается до того момента, пока Вы не перезагрузите машину, после чего он снова начинается с 1025. (На UNIX машинах, как только достигается максимальное число сокетов, нумерация начинается с самого меньшего доступного номера.)

Как только объект Socket создан, процесс перевода его в BufferedReader и PrintWriter тот же самый, что и в серверной части (снова, в обоих случаях Вы начинаете с Socket). Здесь, клиент инициирует соединение отсылкой строки “howdy” следующее за номером. Обратите внимание, что буфер должен быть снова сброшен (что происходит автоматически по второму аргументу в конструкторе PrintWriter). Если буфер не будет сброшен, все общение зависнет, т.к. строка “howdy” никогда не будет отослана, (буфер не будет достаточно полным, чтобы выполнить отсылку автоматически). Каждая строка, отсылаемая сервером обратно, записывается в System.out для проверки, что все работает правильно. Для прекращения общения отсылается условный знак - строка “END”. Если клиент прерывает соединение, то сервер выбрасывает исключение.

Вы видите, что такая же забота здесь тоже присутствует, чтобы убедиться, что ресурсы представленные Socket корректно освобождаются, с помощью блока try-finally.

Сокеты создают “подписанное” (dedicated) соединение, которое сохраняется, пока не произойдет явный разрыв соединения. (Подписанное соединение может еще быть разорвано неявно, если одна сторона, либо промежуточное соединение, разрушается.) Это значит, что обе стороны заблокированы в общении и соединение постоянно открыто. Кажется, что это просто логический подход к передаче данных, однако это дает дополнительную нагрузку на сеть. Позже в этой главе Вы увидите другой метод передачи данных по сети, в котором соединения являются временными.

Обслуживание нескольких клиентов

JabberServer работает, но он может обслуживать только одного клиента одновременно. В типичном сервере Вы захотите иметь возможность общаться с несколькими клиентами одновременно. Решение - это многопоточность, а в языках, которые напрямую не поддерживают многопоточность, это означает все виды сложностей. В Главе 14 Вы увидели, что многопоточность в Java настолько просто, насколько это возможно, в то время, как многопоточность вообще является сложной темой. Т.к. поддержка нитей в Java является прямой и открытой, то создание сервера, поддерживающего множество клиентов, оказывается относительно простой задачей.

Основная схема это создание единичного объекта ServerSocket в серверной части и вызвать метод accept() для ожидания нового соединения. Когда accept() возвращает управление, Вы берете возвращенный Socket и используете его для создания новой нити(потока), чьей работой является обслуживание этого клиента. Затем Вы вызываете метод accept() снова для ожидания нового клиента.

В следующем коде серверной части Вы увидите, что это выглядит также как и в примере JabberServer.java за исключением того, что все операции по обслуживанию конкретного клиента перемещены внутрь класса нити (потока):

//: c15:MultiJabberServer.java

// Сервер, использующий многопоточность

// для обслуживания любого числа клиентов.

import java.io.*;

import java.net.*;

class ServeOneJabber extends Thread {

private Socket socket;

private BufferedReader in;

private PrintWriter out;

public ServeOneJabber(Socket s)

throws IOException {

socket = s;

in =

new BufferedReader(

new InputStreamReader(

socket.getInputStream()));

// Включение автосброса буферов:

out =

new PrintWriter(

new BufferedWriter(

new OutputStreamWriter(

socket.getOutputStream())), true);

// Если какой либо, указанный выше класс выбросит исключение

// вызывающая процедура ответственна за закрытие сокета

// В противном случае нить(поток) закроет его.

start(); // Вызывает run()

}

public void run() {

try {

while (true) {

String str = in.readLine();

if (str.equals("END")) break;

System.out.println("Echoing: " + str);

out.println(str);

}

System.out.println("closing...");

} catch(IOException e) {

System.err.println("IO Exception");

} finally {

try {

socket.close();

} catch(IOException e) {

System.err.println("Socket not closed");

}

}

}

}

public class MultiJabberServer {

static final int PORT = 8080;

public static void main(String[] args)

throws IOException {

ServerSocket s = new ServerSocket(PORT);

System.out.println("Server Started");

try {

while(true) {

// Останавливает выполнение, до нового соединения:

Socket socket = s.accept();

try {

new ServeOneJabber(socket);

} catch(IOException e) {

// Если неудача - закрываем сокет,

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

socket.close();

}

}

} finally {

s.close();

}

}

} ///:~

Нить ServeOneJabber берет объект Socket, который создается методом accept() в main() каждый раз, когда новый клиент создает соединение. Затем, как раньше, он создает объект BufferedReader и объект PrintWriter с авто-сбросом, используя Socket. Наконец, он вызывает специальный метод объекта Thread - start(), который выполняет инициализации в нити и затем вызывает метод run(). Здесь выполняются те же действия, что и в предыдущем примере: чтение данных из сокета, а затем возврат этих данных обратно, пока не придет специальный сигнал - строка “END”.

Ответственность за очистку сокета должна быть снова тщательно обработана. В этом случае, сокет создается за пределами ServeOneJabber так что ответственность может быть разделена. Если конструктор ServeOneJabber завершится неудачно, он просто вызовет исключение вызывающему методу, который затем очистит нить. Но если конструктор завершится успешно, то объект ServeOneJabber возьмет ответственность за очистку нить на себя, в его методе run().

Посмотрите, насколько простая реализация у MultiJabberServer. Как раньше, ServerSocket создается и вызывается метод accept() для ожидания нового соединения. Но в этот момент возвращаемое значение accept() (объекты Socket) передается в конструктор ServeOneJabber, который создает новую нить для обработки этого соединения. Когда соединение закрывается, нить завершает свою работу.

Если создание ServerSocket прерывается, снова выбрасывается в main(). Но если создание успешное, внешний блок try-finally гарантирует его очистку. Внутренний блок try-catch защищает только от ошибок в конструкторе ServeOneJabber; если конструктор выполняется без ошибок, то нить ServeOneJabber закроет связанный с ней сокет.

Для проверки того, что сервер поддерживает несколько клиентов, следующая программа создает множество клиентов (используя нити) которые подключаются к одному и тому же серверу. Максимальное число нитей определяется переменной final int MAX_THREADS.

//: c15:MultiJabberClient.java

// Клиент для проверки MultiJabberServer

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

import java.net.*;

import java.io.*;

class JabberClientThread extends Thread {

private Socket socket;

private BufferedReader in;

private PrintWriter out;

private static int counter = 0;

private int id = counter++;

private static int threadcount = 0;

public static int threadCount() {

return threadcount;

}

public JabberClientThread(InetAddress addr) {

System.out.println("Making client " + id);

threadcount++;

try {

socket =

new Socket(addr, MultiJabberServer.PORT);

} catch(IOException e) {

System.err.println("Socket failed");

// Если сокет не создался,

// ничего не надо чистить.

}

try {

in =

new BufferedReader(

new InputStreamReader(

socket.getInputStream()));

// Включение авто-очистки буфера:

out =

new PrintWriter(

new BufferedWriter(

new OutputStreamWriter(

socket.getOutputStream())), true);

start();

} catch(IOException e) {

// Сокет должен быть закрыт при появлении любой ошибки

try {

socket.close();

} catch(IOException e2) {

System.err.println("Socket not closed");

}

}

// Иначе сокет будет закрыт

// методом run() у нити.

}

public void run() {

try {

for(int i = 0; i < 25; i++) {

out.println("Client " + id + ": " + i);

String str = in.readLine();

System.out.println(str);

}

out.println("END");

} catch(IOException e) {

System.err.println("IO Exception");

} finally {

// Всегда закрывает его:

try {

socket.close();

} catch(IOException e) {

System.err.println("Socket not closed");

}

threadcount--; // Завершение нити

}

}

}

public class MultiJabberClient {

static final int MAX_THREADS = 40;

public static void main(String[] args)

throws IOException, InterruptedException {

InetAddress addr =

InetAddress.getByName(null);

while(true) {

if(JabberClientThread.threadCount()

< MAX_THREADS)

new JabberClientThread(addr);

Thread.currentThread().sleep(100);

}

}

} ///:~

Конструктор JabberClientThread берет InetAddress и использует его для открытия Socket. Вы возможно уже начинаете видеть шаблон: Socket используется всегда для создания некоторых типов Reader и/или Writer (или InputStream и/или OutputStream) объектов, что является единственным способом, в котором Socket может быть использован. (Вы можете, конечно, написать один-два класса для автоматизации этого процесса, вместо того, чтобы заново все это набирать, если для Вас это сложно.) Итак, start() выполняет инициализации нити и вызывает метод run(). Здесь, сообщения отсылаются серверу, и информация с сервера печатается на экране. Однако, нить имеет ограниченное время жизни и в один прекрасный момент завершается. Обратите внимание, что сокет очищается, если конструктор завершается неуспешно, после того как сокет создается, но перед тем, как конструктор завершится. В противном случае ответственность за вызов метода close() для сокета ложится на метод run().

Переменная threadcount хранит число - сколько объектов JabberClientThread в данный момент существует. Она увеличивается в конструкторе и уменьшается при выходе из метода run() (и это значит, что нить завершается). В методе MultiJabberClient.main() Вы видите, что число нитей проверяется, и если нитей слишком много - новые не создаются. Затем метод засыпает. При этом, некоторые нити будут завершаться и новые смогут быть созданы. Вы можете поэкспериментировать с MAX_THREADS, чтобы посмотреть, когда у Вашей системы появятся проблемы с обслуживанием большого количества соединений.


Подобные документы

  • Описание и установка интегрированной среды разработки Eclipse. Описание языка Java. Описание классов и методов, пакетов, использованных в программе, сервера и клиента. Разработка руководства пользователя для клиента и сервера. Пример работы чата.

    курсовая работа [443,5 K], добавлен 18.05.2015

  • Организация корпоративного файлового сервера, выполняющего функции прокси-сервера на базе ОС Linux. Процесс его реализации. Выбор оптимальной аппаратно-программной платформы. Расчёт сметы затрат на выполнение объёма работ по созданию FTP-сервера.

    дипломная работа [2,0 M], добавлен 06.07.2012

  • Основные характеристики и особенности использования прокси-сервера в локальной сети. Способы выхода в Интернет из локальных сетей. Методы аутентификации прокси-сервером пользователя клиента, авторизация клиента для доступа к определенному контенту.

    курсовая работа [887,9 K], добавлен 21.04.2019

  • Спецификация организации службы Short Message Service. Алгоритм работы сервера и возможность расширения функциональных возможностей. Реализация проекта на языке высокого уровня С++ на платформе Linux. Расчет себестоимости и цены программного продукта.

    дипломная работа [168,6 K], добавлен 19.01.2014

  • История и основные сведения о сервере. Классификация и ресурсы серверов. Важность системы охлаждения для сервера. Выбор компонентов для сборки сервера. Основные неисправности и способы их устранения. Проведение технического обслуживания и ремонта сервера.

    дипломная работа [2,1 M], добавлен 24.06.2015

  • Многопоточный веб-сервер с входным и обрабатывающими модулями. HTTP—протокол передачи гипертекста. Установка и настройка локального веб-сервера "OpenServer". Установка phpMyAdmin, конфигурация PHP. Настройка веб-сервера и виртуальных хостов, модулей.

    курсовая работа [3,2 M], добавлен 08.12.2013

  • Создание баз данных и таблиц. Ограничение доступа для пользователей. Хранимая процедура, доступная всем пользователям. Скрипты для проверки ограничений. Методы обеспечения безопасности сервера базы данных. Чтение, изменение и добавление данных.

    лабораторная работа [1,4 M], добавлен 23.07.2012

  • Языки веб-программирования и методы общения клиента и сервера. Характеристика баз данных и понятие веб-сервера. Инструкция программиста и системные требования, инструкция по установке оборудования. Описание исходных кодов и инструкция пользователя.

    курсовая работа [891,3 K], добавлен 04.08.2009

  • Архитектура и функции СУБД. Инфологическая модель данных "Сущность-связь". Ограничения целостности. Характеристика связей и язык моделирования. Манипулирование реляционными данными. Написание сервера на Java.3 и приложения-клиента на ActoinScript 3.0.

    курсовая работа [935,3 K], добавлен 09.07.2013

  • Система доменных имен. Регистрация доменов и обратное преобразование имен. Схема работы DNS сервера. Конфигурация BIND сервера. Расшифровка полей файлов зон. Программное обеспечение, настройка DNS сервера BIND. Проверка работоспособности системы.

    курсовая работа [1,6 M], добавлен 20.09.2013

Работы в архивах красиво оформлены согласно требованиям ВУЗов и содержат рисунки, диаграммы, формулы и т.д.
PPT, PPTX и PDF-файлы представлены только в архивах.
Рекомендуем скачать работу.