Приложения 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();
|