Электронная библиотека книг Александра Фролова и Григория Фролова.
Shop2You.ru Создайте свой интернет-магазин
Библиотека
Братьев
Фроловых
[Назад]

Библиотека примеров приложений Java

Оглавление
Работа с классом InetAddress
Работа с классом URL
Просмотр файлов сервера Web
Копирование файлов сервера Web
Контрольная сумма аплета
Потоковые сокеты - сервер
Потоковые сокеты - клиент
Общение в реальном времени
Широковещатель-
ные сообщения

Аплет и расширение сервера Web

Назад Вперед

8.6. Потоковые сокеты - сервер

Прмер представляет собой автономное приложение Java, выполняющее функции простейшего сервера. Сервер получает текстовые строки от клиентского приложения с помощью потоковых сокетов, отображает их на консоли и отправляет обратно клиенту. Демонстрируется использование классов ServerSocket и Socket.

Исходный текст примера

Архив проекта для Java WorkShop 2.0

Немного теории

С помощью сокетов вы можете организовать передачу данных между двумя приложениями, работающими на одном или разных узлах сети. В процессе создания канала передачи данных одно из этих приложений выполняет роль сервера, а другое - роль клиента.

После того как канал будет создан, приложения становятся равноправными - они могут передавать друг другу данные симметричным образом. Однако логика работы серверного приложения обычно отличается от логики работы клиентского приложения. Как правило, серверное приложение принимает и исполняет команды, передаваемые серверным приложениям, возвращая клиентскому приложению результат выполнения этих команд.

В этом разделе мы покажем, как создать серверное приложение, работающее с сокетами.

Первое, что должно сделать серверное приложение, это создать объект класса ServerSocket, указав конструктору номер используемого порта:

ServerSocket ss;
ss = new ServerSocket(8000);

Объект класса ServerSocket вовсе не является сокетом, а предназначен всего лишь для установки канала связи с клиентским приложением. Далее создается сокет класса Socket, пригодный для передачи данных.

Установка канала связи с клиентским приложением выполняется при помощи метода accept, определенного в классе ServerSocket:

Socket s;
s = ss.accept();

Метод accept приостанавливает работу вызвавшего потока до тех пор, пока клиентское приложение не установит канал связи с сервером. Если ваше приложение однопоточное, его работа будет блокирована до момента установки канала связи. Избежать полной блокировки приложения можно, если выполнять создание канала передачи данных в отдельном потоке.

Как только канал будет создан, сокет сервера становится пригодным для образования входного и выходного потока класса InputStream и OutputStream, соответственно:

InputStream is;
OutputStream os;
is = s.getInputStream();
os = s.getOutputStream();

Эти потоки применяются таким же образом, что и потоки, связанные с файлами.

Описание примера

Серверное приложение SocketServer, описанное здесь, предназначено для совместной работы с клиентским приложением SocketClient, исходный текст которого приведен в следующем разделе нашей библиотеки примеров.

Сразу после запуска серверное приложение выводит на консоль номер порта, применяемого для подключения клиентов, а затем отображает сообщение об ожидании соединения:

* Socket Server *
Local Port: 9999
Waiting connection...

После запуска клиентского приложения SocketClient сервер выводит на консоль строку Connected и переходит в цикл обработки команд, передаваемых клиентом. Обработка заключается в отображении команд на консоли:

Connected.
Hello from client!
12345

Все принятые от клиента строки передаются обратно клиенту в слегка измененном виде - строка команды заключается в символы "*".

Если от клиента приходит строка quit, серверное приложение завершает свою работу.

Рассмотрим исходный текст сервера.

Метод main

Получив управление, метод main создает новый объект класса ServerSocket, передавая соответствующему конструктору номер порта 9999:

ServerSocket ss = null;
try
{
  ss = new ServerSocket(9999);
}  
catch(Exception ex)
{
  System.out.println(ex.toString());
  System.exit(0);
}

Мы выбрали этот порт произвольно, в предположении что он не используется никакими стандартными сетевыми приложениями.

При необходимости методом getLocalPort, определенным в классе ServerSocket, приложение может извлечь номер порта. Именно так и поступает наша программа, отображая этот номер на консоли:

int nPort = ss.getLocalPort();
    
System.out.println(
  "Local Port: " + nPort);

Далее метод main приступает к ожиданию соединения со стороны клиента. Так как эта процедура может заблокировать работу приложения, ее лучше выполнять в рамках отдельного потока выполнения. Мы создаем поток класса ServerThread, образованный на базе класса Thread, и запускаем его на выполнение методом start:

ServerThread sThread = null;
    
sThread = new ServerThread(ss);
sThread.start();

Далее метод main приступает к ожиданию установления соединения с клиентом. Он выводит на консоль соответствующее сообщение и вызывает метод join:

System.out.println(
  "Waiting connection...");
      
try
{
  sThread.join();
}  
catch(InterruptedException ex)
{
  System.out.println(ex.toString());
}

Теперь вся дальнейшая работа выполняется в классе ServerThread. Если клиент завершил работу с сервером, поток класса ServerThread тоже завершается. При этом метод main закрывает объект ss класса ServerSocket, а потом прекращает свою работу:

try
{
  ss.close();
}  
catch(Exception ex)
{
  System.out.println(ex.toString());
}

Класс ServerThread

Как мы уже говорили, класс ServerThread создан нами на базе класса Thread и предназначен для обмена данными с клиентским приложением:

class ServerThread extends Thread
{
  ServerSocket ss = null;
  Socket s = null;
  . . .
}

В поле ss класса ServerSocket хранится ссылка на соответствующий объект, переданная конструктору класса методом main. Поле s класса Socket предназначено для записи ссылки на сокет, применяемый для обмена данными с клиентом.

Конструктор класса ServerThread

Конструктор класса ServerThread просто сохраняет переданный ему параметр в поле ss:

public ServerThread(ServerSocket sSocket)
{
  ss = sSocket;
}
Метод run класса ServerThread

Этот метод работает в рамках отдельного потока и после установки соединения занимается обменом данными с клиентом.

Первым делом метод run вызывает метод accept для объекта ss, переданного конструктору класса:

try
{
  s = ss.accept();  
}
catch(Exception ex)
{
  stop(); 
}

Метод accept блокирует работу потока (но не всего приложения) до тех пор, пока клиент не установит соединение с сервером. После установки соединения метод accept возвращает сокет, на котором можно передавать данные клиенту. Этот сокет записывается в поле s.

Далее метод run открывает входной и выходной потоки:

InputStream is;
OutputStream os;
try
{
  is = s.getInputStream();
  os = s.getOutputStream(); 
  . . .
}

Эти потоки можно использовать для обмена данными с клиентом.

Прием команд, их обработка и передача результата клиенту выполняется в цикле:

while(true)
{
  String szStr = recvString(is);
  sendString(os, "* " + szStr + " *");
  os.flush();
        
  System.out.println(szStr);
        
  if(szStr.equals("quit"))
    break;    
}

Данные от клиента принимаются из потока is методом recvString, определенным в нашем приложении. После того как к принятой строке будет добавлен небольшой префикс и суффикс в виде символа "*", строка отсылается обратно клиенту методом sendString, также определенным в нашем приложении.

Затем метод run сбрасывает буфер потока os, выводит принятую строку на консоль и проверяет, не равна ли она строке "quit", завершающей работу цикла.

Когда цикл завершает свою работу, метод run закрывает потоки:

is.close();
os.close();

Он также закрывает сокет s и объект ss класса ServerSocket:

try
{
  s.close();    
  ss.close();
}
catch(Exception ex)
{
  stop(); 
}
Метод sendString класса ServerThread

Этот метод выполняет побайтную запись строки s в поток os класса OutputStream:

static void sendString(OutputStream os,
  String s)
  throws IOException
{
  for(int i = 0; i < s.length(); i++)
  {
    os.write((byte)s.charAt(i));
  }
  os.write('\n');
  os.flush();
}

Логика работы метода очень проста. Метод в цикле извлекает из строки очередной символ и записывает его в выходной поток. После записи всех символов мы добавляем символ новой строки "\n", который в данном случае используется как разделитель, и сбрасываем буфер выходного потока.

Метод recvString класса ServerThread

Этот метод выполняет посимвольное чтение из входного потока is класса InputStream. Он возвращает строку, сформированную из прочитанных символов:

static String recvString(InputStream is)
  throws IOException
{
  String szBuf = "";
  int ch = is.read();

  while (ch >= 0 && ch != '\n')
  {
    szBuf += (char)ch;
    ch = is.read();
  }
  return szBuf;
}

Строка считается прочитанной полностью, если из входного потока был извлечен символ "\n".


Назад Вперед

[Назад]