Сетевые приложения |
Приложения SocketServ и SocketClientВ качестве примера мы приведем исходные тексты двух приложений Java, работающих с потоковыми сокетами. Одно из этих приложений называется SocketServ и выполняет роль сервера, второе называется SocketClient и служит клиентом. Приложение SocketServ выводит на консоль строку "Socket Server Application" и затем переходит в состояние ожидания соединения с клиентским приложением SocketClient. Приложение SocketClient устанавливает соединение с сервером SocketServ, используя потоковый сокет с номером 9999 (этот номер выбран нами произвольно). Далее клиентское приложение выводит на свою консоль приглашение для ввода строк. Введенные строки отображаются на консоли и передаются серверному приложению. Сервер, получив строку, отображает ее в своем окне и посылает обратно клиенту. Клиент выводит полученную от сервера строку на консоли. Когда пользователь вводит строку "quit", цикл ввода и передачи строк завершается. Весь процесс показан на рис. 3 и 4. Рис. 3. Окно клиентского приложения Рис. 4. Окно серверного приложения Здесь в окне клиентского приложения мы ввели несколько строк, причем последняя строка была строкой "quit", завершившая работу приложений. Исходный текст серверного приложения SocketServИсходный текст серверного приложения SocketServ приведен в листинге 3. Листинг 3. Файл SocketServ.java import java.io.*; import java.net.*; import java.util.*; public class SocketServ { public static void main(String args[]) { byte bKbdInput[] = new byte[256]; ServerSocket ss; Socket s; InputStream is; OutputStream os; try { System.out.println( "Socket Server Application"); } catch(Exception ioe) { System.out.println(ioe.toString()); } try { 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 приведен в листинге 4. Листинг 4. Файл SocketClient.java import java.io.*; import java.net.*; import java.util.*; public class SocketClient { 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) { str = new String(bKbdInput, 0); StringTokenizer st; st = new StringTokenizer( str, "\r\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, "\r\n"); str = new String( (String)st.nextElement()); System.out.println(">> " + str); 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(); |