Описание исходного текста серверного приложения 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();