Приложения 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(); Поделитесь этой записью или добавьте в закладки | Полезные публикации |