Microsoft Visual J++. Создание приложений и аплетов на языке Java. Часть 2© Александр Фролов, Григорий ФроловТом 32, М.: Диалог-МИФИ, 1997, 288 стр. Приложения SocketServ и SocketClientВ качестве примера мы приведем исходные тексты двух приложений Java, работающих с потоковыми сокетами. Одно из этих приложений называется SocketServ и выполняет роль сервера, второе называется SocketClient и служит клиентом. Приложение SocketServ выводит на консоль строку “Socket Server Application” и затем переходит в состояние ожидания соединения с клиентским приложением SocketClient. Приложение SocketClient устанавливает соединение с сервером SocketServ, используя потоковый сокет с номером 9999 (этот номер выбран нами произвольно). Далее клиентское приложение выводит на свою консоль приглашение для ввода строк. Введенные строки отображаются на консоли и передаются серверному приложению. Сервер, получив строку, отображает ее в своем окне и посылает обратно клиенту. Клиент выводит полученную от сервера строку на консоли. Когда пользователь вводит строку “quit”, цикл ввода и передачи строк завершается. Весь процесс показан на рис. 3.3. Рис. 3.3. Передача данных между приложениями SocketClient и SocketServ через потоковый сокет Здесь в окне клиентского приложения мы ввели несколько строк, причем последняя строка была строкой “quit”, завершившая работу приложений. Исходный текст серверного приложения SocketServИсходный текст серверного приложения SocketServ приведен в листинге 3.5. Листинг 3.5. Файл SocketServ\SocketServ.java // ========================================================= // Использование потоковых сокетов. // Приложение сервера // // (C) Фролов А.В, 1997 // // E-mail: frolov@glas.apc.org // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.io.*; import java.net.*; import java.util.*; public class SocketServ { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Объект класса ServerSocket для создания канала ServerSocket ss; // Сокет сервера Socket s; // Входной поток для приема команд от клиента InputStream is; // Выходной поток для передачи ответа клиенту OutputStream os; try { System.out.println("Socket Server Application"); } catch(Exception ioe) { // При возникновении исключения выводим его описание // на консоль System.out.println(ioe.toString()); } try { // Создаем объект класса ServerSocket ss = new ServerSocket(9999); // Ожидаем соединение s = ss.accept(); // Открываем входной поток для приема // команд от клиента is = s.getInputStream(); // Открываем выходной поток для передачи // ответа клиенту os = s.getOutputStream(); // Буфер для чтения команд byte buf[] = new byte[512]; // Размер принятого блока данных int lenght; // Цикл обработки команд, полученных от клиента while(true) { // Получаем команду lenght = is.read(buf); // Если входной поток исчерпан, завершаем // цикл обработки команд if(lenght == -1) break; // Отображаем принятую команду на консоли сервера // Формируем строку из принятого блока String str = new String(buf, 0); // Обрезаем строку, удаляя символ конца строки StringTokenizer st; st = new StringTokenizer(str, "\r\n"); str = new String((String)st.nextElement()); // Выводим строку команды на консоль System.out.println("> " + str); // Посылаем принятую команду обратно клиенту os.write(buf, 0, lenght); // Сбрасываем буфер выходного потока os.flush(); } // Закрываем входной и выходной потоки is.close(); os.close(); // Закрываем сокет сервера s.close(); // Закрываем соединение ss.close(); } catch(Exception ioe) { System.out.println(ioe.toString()); } try { System.out.println( "Press <Enter> to terminate application..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } Описание исходного текста серверного приложения SocketServВ методе main, получающем управление сразу после запуска приложения, мы определили несколько переменных. Массив bKbdInput размером 256 байт предназначен для хранения строк, введенных при помощи клавиатуры. В переменную ss класса ServerSocket будет записана ссылка на объект, предназначенный для установления канала связи через потоковый сокет (но не ссылка на сам сокет): ServerSocket ss; Ссылка на сокет, с использованием которого будет происходить передача данных, хранится в переменной с именем s класса Socket: Socket s; Кроме того, мы определили переменные is и os, соответственно, классов InputStream и OutputStream: InputStream is; OutputStream os; В эти переменные будут записаны ссылки на входной и выходной поток данных, которые связаны с сокетом. После отображения на консоли строки названия приложения, метод main создает объект класса ServerSocket, указывая конструктору номер порта 9999: ss = new ServerSocket(9999); Конструктор возвращает ссылку на объект, с использованием которого можно установить канал передачи данных с клиентом. Канал устанавливается методом accept: s = ss.accept(); Этот метод переводит приложение в состояние ожидания до тех пор, пока не будет установлен канал передачи данных. Метод accept в случае успешного создания канала передачи данных возвращает ссылку на сокет, с применением которого нужно принимать и передавать данные. На следующем этапе сервер создает входной и выходной потоки, вызывая для этого методы getInputStream и getOutputStream, соответственно: is = s.getInputStream(); os = s.getOutputStream(); Далее приложение подготавливает буфер buf для приема данных и определяет переменную length, в которую будет записываться размер принятого блока данных: byte buf[] = new byte[512]; int lenght; Теперь все готово для запуска цикла приема и обработки строк от клиентского приложения. Для чтения строки мы вызываем метод read применительно ко входному потоку: lenght = is.read(buf); Этот метод возвращает управление только после того, как все данные будут прочитаны, блокируя приложение на время своей работы. Если такая блокировка нежелательна, вам следует выполнять обмен данными через сокет в отдельной задаче. Метод read возвращает размер принятого блока данных или -1, если поток исчерпан. Мы воспользовались этим обстоятельством для завершения цикла приема данных: if(lenght == -1) break; После завершения приема блока данных мы преобразуем массив в текстовую строку str класса String, удаляя из нее символ перевода строки, и отображаем результат на консоли сервера: System.out.println("> " + str); Затем полученная строка отправляется обратно клиентскому приложению, для чего вызывается метод write: os.write(buf, 0, lenght); Методу write передается ссылка на массив, смещение начала данных в этом массиве, равное нулю, и размер принятого блока данных. Для исключения задержек в передаче данных из-за накопления данных в буфере (при использовании буферизованных потоков) необходимо принудительно сбрасывать содержимое буфреа метдом flush: os.flush(); И хотя в нашем случае мы не пользуемся буферизованными потоками, мы включили вызов этого метода для примера. Теперь о завершающих действиях после прерывания цикла получения, отображения и передачи строк. Наше приложение явням образом закрывает входной и выходной потоки данных, сокет, а также объект класса ServerSocket, с использованием которого был создан канал передачи данных: is.close(); os.close(); s.close(); ss.close(); Исходный текст клиентского приложения SocketClientИсходный текст клиентского приложения SocketClient приведен в листинге 3.6. Листинг 3.6. Файл SocketClient\SocketClient.java // ========================================================= // Использование потоковых сокетов. // Приложение клиента // // (C) Фролов А.В, 1997 // // E-mail: frolov@glas.apc.org // WWW: http://www.glasnet.ru/~frolov // или // http://www.dials.ccas.ru/frolov // ========================================================= import java.io.*; import java.net.*; import java.util.*; public class SocketClient { // ------------------------------------------------------- // main // Метод, получающий управление при запуске приложения // ------------------------------------------------------- public static void main(String args[]) { // Массив для ввода строки с клавиатуры byte bKbdInput[] = new byte[256]; // Сокет для связи с сервером Socket s; // Входной поток для приема данных от сервера InputStream is; // Выходной поток для передачи данных серверу OutputStream os; try { // Выводим строку приглашения System.out.println("Socket Client Application" + "\nEnter any string or 'quit' to exit..."); } catch(Exception ioe) { // При возникновении исключения выводим его описание // на консоль System.out.println(ioe.toString()); } try { // Открываем сокет s = new Socket("localhost",9999); // Создаем входной поток для приема данных от сервера is = s.getInputStream(); // Создаем выходной поток для передачи данных серверу os = s.getOutputStream(); // Буфер для передачи данных byte buf[] = new byte[512]; // Размер принятого блока данных int length; // Рабочая строка String str; // Вводим команды и передаем их серверу while(true) { // Читаем строку команды с клавиатуры length = System.in.read(bKbdInput); // Если строка не пустая, обрабатываем ее if(length != 1) { // Преобразуем строку в формат String str = new String(bKbdInput, 0); // Обрезаем строку, удаляя символ конца строки StringTokenizer st; st = new StringTokenizer(str, "\n"); str = new String((String)st.nextElement()); // Выводим передаваемую строку команды // на консоль для контроля System.out.println("> " + str); // Записываем строку в выходной поток, // передавая ее таким образом серверу os.write(bKbdInput, 0, length); // Сбрасываем буфер выходного потока os.flush(); // Принимаем ответ сервера length = is.read(buf); if(length == -1) break; // Отображаем принятую строку на консоли str = new String(buf, 0); st = new StringTokenizer(str, "\n"); str = new String((String)st.nextElement()); System.out.println(">> " + str); // Если введена строка 'quit', завершаем // работу приложения if(str.equals("quit")) break; } } // Закрываем входной и выходной потоки is.close(); os.close(); // Закрываем сокет s.close(); } catch(Exception ioe) { System.out.println(ioe.toString()); } try { System.out.println( "Press <Enter> to terminate application..."); System.in.read(bKbdInput); } catch(Exception ioe) { System.out.println(ioe.toString()); } } } Описание исходного текста клиентского приложения SocketClientВнутри метода main клиентского приложения SocketClient определены переменные для ввода строки с клавиатуры (массив bKbdInput), сокет s класса Socket для работы с сервером SocketServ, входной поток is и выходной поток os, которые связаны с сокетом s. После вывода на консоль приглашающей строки клиентское приложение создает сокет, вызывая конструктор класса Socket: s = new Socket("localhost",9999); В процессе отладки мы запускали сервер и клиент на одном и том же узле, поэтому в качестве адреса сервера указана строка “localhost”. Номер порта сервера SocketServ равен 9999, поэтому мы и передали конструктору это значение. После создания сокета наше клиентское приложение создает входной и выходной потоки, связанные с этим сокетом: is = s.getInputStream(); os = s.getOutputStream(); Теперь клиентское приложение готово обмениваться данными с сервером. Этот обмен выполняется в цикле, условием завершения которого является ввод пользователем строки “quit”. Внутри цикла приложение читает строку с клавиатуры, записывая ее в массив bKbdInput: length = System.in.read(bKbdInput); Количество введенных символов сохраняется в переменной length. Далее если пользователь ввел строку, а не просто нажал на клавишу <Enter>, эта строка отображается на консоли и передается серверу: os.write(bKbdInput, 0, length); os.flush(); Сразу после передачи сбрасывается буфер выходного потока. Далее приложение читает ответ, посылаемый сервером, в буфер buf: length = is.read(buf); Напомним, что наш сервер посылает клиенту принятую строку в неизменном виде. Если сервер закрыл канал, то метод read возвращает значение -1. В этом случае мы прерываем цикл ввода и передачи строк: if(length == -1) break; Если же ответ сервера принят успешно, принятые данные записываются в строку str, которая отображается на консоли клиента: System.out.println(">> " + str); Перед завершением своей работы клиент закрывает входной и выходной потоки, а также сокет, на котором выполнялась передача данных: is.close(); os.close(); s.close(); |