Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:

Секреты программирования для Internet на Java

.pdf
Скачиваний:
181
Добавлен:
02.05.2014
Размер:
3.59 Mб
Скачать

программировать. Конкретные вопросы, связанные с реализацией этого алгоритма, мы рассмотрим в разделе "Реализация: модуль сетевого интерфейса" этой главы.

Клиент

Возможность использовать язык Java для написания клиента - это, безусловно, большая удача. Мучения при написании многочисленных программ CGI и необходимость передавать данные от одной программы к другой уходят в прошлое. Разработчики Web, использовавшие CGI, часто сталкивались с разнообразными трудностями. Даже если сервер является программой CGI, полезно использовать Java в качестве клиента. В главе 17, "Взаимодействие с CGI: Javaмагазин", показано, как, используя Java, можно связаться с программой CGI.

На самом деле клиент - это просто поток, обрабатывающий приходящие из сети данные. Он действует как диспетчер для входящих пакетов. Клиент раскодирует пакет и вызывает соответствующую программу для работы с этим пакетом. И кроме того, он сам действует как поток, чтобы не возникала необходимость писать другую программу для передачи данных по сети.

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

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

Модуль, специфический для данного проекта

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

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

Использование интерфейса позволяет нам изменять способы реализации расписания, а это очень важно для нашего приложения. На стороне сервера мы сохраним расписания на диске и разрешим хранить их постоянно. На стороне клиента мы будем хранить расписания в оперативной памяти по двум причинам: во-первых, Java не позволяет пользователю обращаться к локальным файлам, и во-вторых, хотелось бы, чтобы доступ к расписаниям был быстрым. Хранить расписания в памяти удобно клиенту, но представьте себе, что будет, если машина сервера начнет хранить в памяти расписание каждого сотрудника! Активный сервер может обслуживать одновременно тысячи людей, и такое количество расписаний очень быстро переполнит память машины. На рис. 19-2 изображена иерархия объектов для классов schedule.

Ⱦɚɧɧɚɹ ɜɟɪɫɢɹ ɤɧɢɝɢ ɜɵɩɭɳɟɧɚ ɷɥɟɤɬɪɨɧɧɵɦ ɢɡɞɚɬɟɥɶɫɬɜɨɦ %RRNV VKRS Ɋɚɫɩɪɨɫɬɪɚɧɟɧɢɟ ɩɪɨɞɚɠɚ ɩɟɪɟɡɚɩɢɫɶ ɞɚɧɧɨɣ ɤɧɢɝɢ ɢɥɢ ɟɟ ɱɚɫɬɟɣ ɁȺɉɊȿɓȿɇɕ Ɉ ɜɫɟɯ ɧɚɪɭɲɟɧɢɹɯ ɩɪɨɫɶɛɚ ɫɨɨɛɳɚɬɶ ɩɨ ɚɞɪɟɫɭ piracy@books-shop.com

Рис. 19.2.

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

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

Независимо от того, как реализовано расписание, мы можем просто вставить его непосредственно в нашу программу - и все заработает! Интерфейс - это мощное средство проектирования и изящный путь для развития открытых систем. Когда стандарт разработан, кто угодно может создать собственную реализацию. В табл. 19-1 приведено описание всех методов schedule.

 

Таблица 19-1. Методы schedule

Метод

Описание

add(scheduleStruct) Добавляет в расписание новую встречу. del(ScheduleStruct) Удаляет встречи из расписания.

find(Date)

Ищет встречи по определенной дате/времени.

getUser()

Возвращает хозяина расписания.

Рассмотрим программу, задающую этот интерфейс:

package ventana.scheduler; import java.io.*;

import java.util.*; import ventana.util.*;

import ventana.scheduler.*; public interface schedule {

// подпрограммы поддержки

public void add(scheduleStruct newSchedule) throws DuplicateException, IOException; /*Описание:

*Добавляет новую встречу в расписание

*Исключения:

*Duplicate Exception - временной интервал недействителен *ServerException - контакт с сервером невозможен *IOException - необрабатываемая ошибка ввода/вывода

*/

public void del(scheduleStruct del) throws NotFoundException, IOException;

/* Описание:

www.books-shop.com

/*Удаляет встречу

*

* Исключения:

*NotFoundException - встреча не существует

*ServerException - контакт с сервером невозможен *IOException - необрабатываемая ошибка ввода/вывода

*/ // подпрограммы поиска/восстановления

public scheduleStruct find(Date date)

throws NotFoundException, IOException; /* Описание:

*Ищет scheduleStrust для определенной даты/времени

*

* Исключения:

*NotFoundException - встреча не существует

*IOException - необрабатываемая ошибка ввода/вывода

*/

public scheduleStruct[] findRange(Date start, Date end) throws IOException;

/* Описание:

*Возвращает все пункты расписания, относящиеся *к определенной дате/времени

*Исключения:

*IOException - необрабатываемая ошибка ввода/вывода

*/

public String getUser(); /* Описание:

*Возвращает хозяина расписания

*/

}

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

Модуль пользовательского интерфейса

Если есть какое-нибудь средство для графической компоновки интерфейса, то создать пользовательский интерфейс будет нетрудно. В противном случае придется формировать проект вручную. Для компоновки интерфейса на Java нужно с помощью какого-нибудь менеджера размещения расположить отдельные части проекта в окне программы. Как правило, все, что вам нужно, можно выполнить с помощью имеющегося в Java набора менеджеров размещения. Их применение необходимо для того, чтобы программа, работающая в такой разнородной среде, как Интернет, правильно меняла размеры экранных элементов. Для данного руководства мы выбрали удобный менеджер PacketLayout, который свободно распространяется по сети его создателем Дэроном Мейером.

СОВЕТ Разработка пользовательского интерфейса займет у вас в два или в три раза больше времени, чем вы ожидаете. Много часов сэкономит вам использование GUI screen builder.

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

Все это очень просто. Простота экрана - это совместное достижение правильно построенной

www.books-shop.com

программы и языка Java. Java не разрешает помещать меню на апплет, поэтому нам придется создать отдельный фрейм для размещения в нем меню. Такой метод постепенно приведет к тому, что ваши апплеты будут похожи на целые приложения, хотя обычно это не так уж плохо. Так, наш проект оказывается ближе к реальному приложению, чем к расширению Web-страницы - например, аниматору или доске объявлений (billboard).

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

Рис. 19.3.

Остальные окна небольшие и содержат только по одной функции. Мы не приводим их здесь; вы можете сами их посмотреть, полистав приложение. Важно помнить, что все окна являются отдельными фреймами, а это палка о двух концах - дает больше свободы действий, но требует больше работы. Во время реализации проекта мы обратим ваше внимание на некоторые детали, которые важно учитывать при работе с фреймами.

Большая картина

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

Рис. 19.4.

www.books-shop.com

Спроектировав систему, мы можем заняться написанием программы. На этой стадии работы может возникнуть необходимость изменить проект (скорее всего такой необходимости не возникнет, но иногда такое случается). Поскольку теперь вы лучше научились предвидеть возможные трудности и приобрели опыт работы с Java и объектно-ориентированным проектом, вы сделаете меньше ошибок и ваш проект в целом будет аккуратнее. Необыкновенно приятно, когда кто-нибудь посмотрит на ваш проект и скажет: "Да, вот это вещь!"

Реализация

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

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

Рассмотрим теперь, как реализовать планировщик. Начнем с обзора каталогов и затем обратимся к самой программе.

Обзор программы

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

Компилируется программа легко. Мы редактируем файл проекта, чтобы изменить местоположение файлов класса. Файл проекта хранит все свои файлы в каталоге ventana. Путь к файлам должен быть записан в CLASSPATH, чтобы имело смысл помещать каталоги туда, где находятся наши классы Java. Модифицировав файл, мы даем команду make. Программа make скомпилирует нашу программу.

Модуль сетевого интерфейса

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

При написании сервера есть одно неприятное ограничение. Скоро ожидается выпуск базы данных API для Java, но к моменту написания книги эта база недоступна. Это означает, что наш сервер должен выполнять такие функции, как блокировка файла или записи. Мы разработали простой механизм блокировки файлов, хотя можно написать гораздо более изящную систему. В идеале сервер должен иметь доступ к базе данных с блокировкой записи. Мы пока не занимались вопросами возврата транзакции (transaction rollback). Частично транзакция может быть внесена в базу данных. Надеемся, что база данных API отчасти разрешит эти проблемы.

Словарик создателя сервера

Приведем некоторые термины, связанные с работой сервера.

Блокировка файла (file locking). Сделать файл доступным только для одного человека в каждый момент времени, чтобы предотвратить повреждение файла.

Блокировка записи (record locking). Сделать запись доступной только для одного человека в каждый момент времени; это лучше блокировки файла, потому что одновременно может совершаться больше транзакций.

Транзакция (transaction). Операция записи в файл.

www.books-shop.com

Возврат транзакции (transaction rollback). Метод восстановления, позволяющий отменить некоторое количество транзакций. Обычно применяется при возникновении ошибок.

Фактически сервер выполняет очень простой цикл действий. Он слушает порт сокета, принимает связь, а затем запускает поток для обработки запроса. Это приложение, а не апплет, поэтому в нем есть метод main. Приведем программу для основного цикла сервера:

public class server {

public static void main(String args[]) throws IOException {

ServerSocket session = new ServerSocket(1666,100); handleRequests handler;

Hashtable users = new Hashtable(); locks schedLocks = new locks();

System.out.println("Waiting for connections"); while(true) {

Socket socket = null; try {

socket = session.accept();

}

catch (SocketException e) {

// обычно тайм-аут, повторите попытку

}

if (socket == null) { continue;

}

System.out.println("Connection made");

//создаем отдельный поток для связи handler = new handleRequests(socket,users, schedLocks);

//установки для нереентерабельных систем handler.setPriority(handler.getPriority() + 1); handler.start();

}

}

}

Сервер использует класс под названием serverSocket. Этот класс слушает порт с помощью метода accept и ждет установления соединения. Мы выбрали порт 1666 и задали время ожидания 100 миллисекунд. Обратите внимание на блок try в методе accept. Если в течение заданного промежутка времени соединение не произошло, генерируется исключение socketException. В нашем случае мы будем продолжать совершать шаги цикла, пока соединение не установится.

После установления соединения мы создадим новый поток для обработки запросов. Мы создали класс handleRequest для обслуживания связей. Поскольку он является расширением класса Thread, его основная программа содержится в методе run. Этот метод будет действовать как диспетчер. Когда приходит пакет, первый байт используется для определения типа пакета. Затем вызывается соответствующий метод для выполнения разбора пакета. Вот и все - кроме анализа и обработки запросов, все готово. Класс handleRequest осуществит все операции по запросам клиента:

class handleRequests extends Thread { DataInputStream in = null; DataOutputStream out = null; Socket socket = null;

String user=null; Hashtable users; locks schedLocks;

handleRequests(Socket s, Hashtable users, locks schedLocks) throws IOException { socket = s;

in = new DataInputStream(

new BufferedInputStream(socket.getInputStream())); out = new DataOutputStream(new BufferedOutputStream(

www.books-shop.com

socket.getOutputStream())); this.users = users; this.schedLocks = schedLocks;

}

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

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

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

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

Последним параметром является объект schedLocks. Этот объект нужен для запирания файла. Нельзя допустить, чтобы два пользователя одновременно изменяли файл. Чтобы избежать этого, программа запирает файл перед использованием и отпирает после того, как работа с ним закончена. Хотя это предохраняет файлы, лучше было бы применять механизм записиблокировки. Вопросы взаимной блокировки и повреждения данных обсуждаются в главе 11, "Многопотоковость".

Создав поток, мы вызываем метод start. После этого система вызывает метод потока run. Метод run нашего класса handleRequest очень прост и действует как диспетчер. Кроме того, он обрабатывает все возникающие исключения. Следующий фрагмент кода демонстрирует анализатор для наших сообщений:

public void run() { byte b;

boolean cont=true; try {

loginRequest(); while(cont) {

b = in.readByte(); switch(b) {

case packetTypes.login : loginParse(); break; case packetTypes.logoff : cont=false; break;

case packetTypes.reqSchedule : reqSchedule(); break;

case packetTypes.addMeeting : addMeeting(); break;

default : System.out.println("Unknown type");

}

}

out.close();

in.close();

socket.close();

www.books-shop.com

}

catch (IOException ignore) {

System.out.println("IO Exception, thread stopped"); stop();

}

catch (Exception e) {

System.out.println("Unknown error, thread stopped"); e.printStackTrace();

stop();

}

finally { users.remove(user);

schedLocks.unlock(user);

}

}

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

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

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

public void loginRequest() throws IOException { System.out.println("Requesting login"); out.writeByte(packetTypes.login); out.flush();

}

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

public void loginParse() throws IOException { byte pakType;

user = in.readLine();

System.out.println("User " + user + " logged in"); users.put(user, this);

}

public void reqSchedule() throws IOException { String user;

Date start, end; scheduleStruct result[]; user = in.readLine();

start = new Date(in.readLong()); end = new Date(in.readLong());

System.out.println("Schedule Request for " + user); System.out.println("Start: " + start.toString()); System.out.println("End: " + end.toString());

try {

schedLocks.lock(user);

serverSchedule schedule = new serverSchedule(user);

www.books-shop.com

result = schedule.findRange(start, end); out.writeByte(packetTypes.getSchedule); out.writeBytes(user + "\n");

if (result == null) { out.writeInt(0); out.flush(); return;

}

out.writeInt(result.length); System.out.println("Recs to send: " + result.length); for(int i=0; i < result.length; i++) {

System.out.println("Meeting: " + result[i].desc); out.writeLong(result[i].start.getTime()); out.writeLong(result[i].end.getTime()); out.writeBytes(result[i].desc + "\n"); out.writeBytes(result[i].descURL + "\n"); out.writeBytes(result[i].minutesURL + "\n"); out.writeBytes(result[i].attending + "\n");

}

out.flush(); System.out.println("Schedule Sent");

}

finally { schedLocks.unlock(user);

}

}

Метод redSchedule - самый большой поставщик данных на сервере. Зная временной промежуток и имя пользователя, он пошлет данные о всех встречах, назначенных на этот отрезок времени. Эта подпрограмма используется клиентом для того, чтобы заполнить локальный объект расписания. Данные считываются с помощью объекта serverSchedule и затем посылаются в формате сообщения.

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

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

public void addNAK(String s) throws IOException { out.writeByte(packetTypes.addNAK); out.writeBytes("AddMeeting: " + s + "\n"); out.flush();

}

public void addACK() throws IOException { out.writeByte(packetTypes.addACK); out.flush();

}

public void scheduleChange(String s, Date start) throws IOException { System.out.println("Schedule changed:" + s); out.writeByte(packetTypes.scheduleChange); out.writeBytes(s + "\n"); out.writeLong(start.getTime()); out.flush();

}

AddNAK, addACK и scheduleChange - это подпрограммы оповещения. NAK (negative acknowledge) сообщает, что новая встреча не добавлена. Она принимает строку, которая выдается пользователю. Типичные ошибки - неправильная дата или время и попытки заполнить время человека, который уже занят. Сообщение ACK (acknowledge) говорит системе, что

www.books-shop.com

добавление новой встречи прошло успешно.

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

public void addMeeting() throws IOException { String line;

String dateLine;

scheduleStruct ss = new scheduleStruct(); dateLine = in.readLine();

line = in.readLine(); try {

ss.start = new Date(dateLine + " " + line); } catch ( IllegalArgumentException e) {

System.out.println("Illegal argument, aborting"); in.readLine();

in.readLine();

in.readLine();

in.readLine();

addNAK("Error converting start date"); return;

}

line = in.readLine(); try {

ss.end = new Date(dateLine + " " + line); } catch (IllegalArgumentException e) {

System.out.println("Illegal argument, aborting"); in.readLine();

in.readLine();

in.readLine();

addNAK("Error converting end date"); return;

}

ss.desc = in.readLine(); ss.attending = in.readLine(); ss.descURL = in.readLine();

// лексический анализ встречи, это текст в скобках int pos;

String st,st2; handleRequests hr; Enumeration e;

st = new String(ss.attending + ","); if (st == null) {

addNAK("Attending field blank"); return;

}

st = st.trim();

while ((pos = st.indexOf(',')) != -1) { st2 = st.substring(0,pos);

System.out.println("Attending: " + st2); st = st.substring(pos + 1); schedLocks.lock(st2);

serverSchedule sched = new serverSchedule(st2); try {

System.out.println("Adding: " + ss); sched.add(ss);

} catch(DuplicateException bad) { addNAK(st2 + " already scheduled");

}

schedLocks.unlock(st2); e = users.elements();

while(e.hasMoreElements()) {

hr = (handleRequests) e.nextElement();

www.books-shop.com