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

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

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

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

Назад Вперед

8.8. Общение в реальном времени

Приложение обеспечивает возможность общения в реальном времени через сеть Internet (on-line chat). Оно может выполнять роль клиента или сервера. Здесь демонстрируется использование классов Socket и ServerSocket.

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

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

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

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

Все необходимые теоретические сведения уже были изложены в двух предыдущих разделах нашей библиотеки примеров. Однако здесь мы сделаем еще пару замечаний.

Прежде всего обратим ваше внимание на необходимость выполнения задачи получение и отображения сообщений от удаленного партнера в отдельном потоке выполнения. В противном случае вы сможете "говорить" только по очереди.

Еще одно замечание касается применения символов UNICODE.

Для того чтобы помимо латинских символов в сообщениях можно было использовать кириллицу или символы других алфавитов, следует передавать данные как символы UNICODE.

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

Главное окно нашего приложения JavaChat показано на рис. 1.

pic1.gif (3522 bytes)

Рис. 1. Главное окно приложения Chat

Вы должны запустить два экземпляра этого приложения на одной или разных рабочих станциях сети (например, сети Интернет или корпоративной сети с протоколом TCP/IP). Один экземпляр будет при запуске выполнять роль сервера, другой - клиента. После инициализации приложения становятся равноправными.

Если нажать кнопку Start Server, приложение перейдет в режим сервера. При этом в заголовке появится слово SERVER, а кнопки Start Client и Start Server будут заблокированы (рис. 2).

pic2.gif (4706 bytes)

Рис. 2. Приложение в режиме сервера

В окне многострочного редактора текста, играющего в нашей программе роль консоли, появляется строка "Server started. Connecting…".

Аналогично, если нажать кнопку Start Client, приложение будет играть роль клиента. Заметим, однако, что перед тем как это делать, следует записать доменное имя или адрес IP сервера в поле, расположенное слева от кнопки Send. Если этого не сделать, программа клиента будет искать сервер на той же рабочей станции, где она сама находится.

Окно нашей программы в режиме клиента показано на рис. 3.

pic3.gif (4566 bytes)

Рис. 3. Приложение в режиме клиента

После запуска клиента и установки соединения в окнах сервера и клиента появляется сообщение Connected. После этого кнопка Send разблокируется и вы можете обмениваться сообщениями.

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

Принятые сообщения отмечаются  стрелочкой.

Рассмотрим наиболее важные фрагменты исходного текста приложения.

Главный класс приложения JavaChat

Главный класс нашего приложения создает окно класса FrameWindow, определенного нами на базе класса Frame;

import java.awt.*;
import java.awt.event.*;
import java.net.*;
import java.io.*;
 
public class JavaChat
{
  public static void main(String args[])
  {
    FrameWindow frame;
    frame =  new FrameWindow(
      "Java on-line chat");
    frame.setVisible(true);
  }
}

Созданное окно отображается методом setVisible.

Класс FrameWindow

Класс FrameWindow создан на базе класса Frame:

class FrameWindow extends Frame
  implements ActionListener, WindowListener
{
  . . .
}

Он реализует интерфейсы ActionListener и WindowListener, как и многие наши приложения.

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

TextArea taConsole;
TextField tfCommand;
Button btStartServer;
Button btStartClient;
Button btExit;
Button btSend;

Это многострочный редактор текста taConsole, однострочное поле ввода сообщений tfCommand, а также четыре кнопки, предназначенные для запуска приложения в различных режимах, для завершения его работы и для передачи сообщений.

Поле sThread хранит ссылку на поток выполнения, принимающий сообщения от удаленного пользователя:

ServerThread sThread = null;

В поле dos хранится ссылка на выходной поток, применяемый для отправки сообщений:

DataOutputStream dos;

Поле serverMode хранит признак работы приложения в серверном режиме:

boolean serverMode = true;

Для клиентского режима в это поле записывается значение false.

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

В задачу конструктора класса FrameWindow входит создание всех необходимых компонент (полей редактирования и кнопок), а также добавление этих компонент в главное окно программы.

Компоненты добавляются в режиме размещения компонент GridBagLayout.

Метод actionPerformed класса FrameWindow

Когда пользователь нажимает кнопку Start Server, метод actionPerformed блокирует кнопки Start Server и Start Client, изменяет заголовок окна и устанавливает значение true в поле serverMode:

if(e.getSource().equals(btStartServer))
{
  btStartClient.setEnabled(false);
  btStartServer.setEnabled(false);
  setTitle("Java on-line chat (SERVER)");
      
  serverMode = true;
  sThread = 
    new ServerThread(this, serverMode);
  sThread.start();
}

Затем метод actionPerformed запускает поток выполнения ServerThread, читающий входные сообщения.

Режим клиента запускается аналогичным образом после того как пользователь нажимает кнопку Start Client:

else if(e.getSource().equals(btStartClient))
{
  btStartClient.setEnabled(false);
  btStartServer.setEnabled(false);
  setTitle("Java on-line chat (CLIENT)");
      
  serverMode = false;
  sThread = 
    new ServerThread(this, serverMode);
  sThread.start();
}

Отличие лишь только в том, что в режиме клиента поле serverMode инициализируется значением false.

При отправке сообщения с помощью кнопки Send метод actionPerformed извлекает ссылку на выходной поток, созданный в классе ServerThread после установки соединения:

else if(e.getSource().equals(btSend))
{
  dos = sThread.getDataOutputStream();
  . . .
}

Далее мы получаем передаваемую строку из однострочного редактора, выводим ее в окне программы методом consolePrint и посылаем удаленному партнеру при помощи метода sendString:

String szSended = tfCommand.getText(); 
consolePrint("\n:" + szSended);
       
try
{
  sendString(dos, szSended);
}  
catch(Exception ex)
{
  System.out.println(ex.toString());
}
Метод consolePrint класса FrameWindow

Этот метод просто добавляет переданную ему строку в окно многострочного редактора текста, расположенного в окне приложения:

void consolePrint(String s)
{
  taConsole.append(s);
}
Метод sendString класса FrameWindow

Для передачи текстовых сообщений удаленному партнеру мы применяем метод sendString:

static void sendString(DataOutputStream dos,
  String s)
  throws IOException
{
  dos.writeChars(s + "\n");
  dos.flush();
}

Строка передается методом writeChars как последовательность символов UNICODE.

Класс ServerThread

Класс ServerThread создан на базе класса Thread:

class ServerThread extends Thread
{
  . . .
}
Поля класса ServerThread

Поле serverMode хранит текущий режим приложения (сервер или клиент):

boolean serverMode;

В поле ss находится ссылка на объект класса ServerSocket, создаваемый в режиме сервера при установке соединения:

ServerSocket ss = null;

Поле s хранит ссылку на сокет, применяемый для приема данных:

Socket s = null;

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

InputStream is;
static DataInputStream dis;
  
OutputStream os;
DataOutputStream dos;

И, наконец, поле frame хранит ссылку на родительский класс главного окна приложения:

FrameWindow frame;
Конструктор класса ServerThread

В качестве параметров конструктору класса ServerThread передается ссылка на родительское окно и режим работы приложения (сервер или клиент):

public ServerThread(FrameWindow fr,
  boolean bMode)
{
  serverMode = bMode;
  frame = fr;
}

Конструктор сохраняет полученные значения в соответствующих полях.

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

Этот метод просто возвращает ссылку на форматированный выходной поток данных, с использованием которого следует передавать сообщения удаленному партнеру:

public DataOutputStream getDataOutputStream()
{
  return dos;
}
Метод run класса ServerThread

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

Когда приложение работает в режиме сервера, метод run выводит соответствующее сообщение на "консоль" программы, создает объект класса ServerSocket и ожидает соединения:

if(serverMode)
{
  frame.consolePrint(
    "Server started. Connecting...");
      
  try
  {
    ss = new ServerSocket(9998);
    s = ss.accept();  
    frame.consolePrint("\nConnected.");
    frame.btSend.setEnabled(true);
  }  
  catch(Exception ex)
  {
    System.out.println(ex.toString());
    stop(); 
  }
  . . .
}

После установки соединения метод run снимает блокировку с кнопки Send, разрешая передачу сообщений.

В режиме клиента метод run также выдает сообщение, но другое:

else  
{
  frame.consolePrint(
    "Client started. Connecting...");
    
  String szServerURL = 
    frame.tfCommand.getText();
    
  try
  {
    s = new Socket(szServerURL, 9998);
    frame.consolePrint("\nConnected.");
    frame.btSend.setEnabled(true);
  }
  catch(Exception ex)
  {
    stop(); 
  }
  . . .
}

Далее он извлекает адрес сервера из однострочного поля редактирования tfCommand и создает сокет для указанного адреса и порта с номером 9998.

На следующем этапе и сервер и клиент получают входные и выходные потоки, связанные с сокетом, а затем запускают цикл приема и отображения сообщений, передаваемых удаленным корреспондентом:

try
{
  is = s.getInputStream();
  dis = new DataInputStream(is);
  os = s.getOutputStream();  
  dos = new DataOutputStream(os);
    
  while(true)
  {
    String szStr = recvString(dis);
    frame.consolePrint("\n--->" + szStr);
  }
}
catch(Exception ex)
{
  stop(); 
}

Прием сообщений выполняется методом recvString, о котором мы расскажем ниже.

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

Этот метод читает сообщение от удаленного корреспондента по символам:

static String recvString(InputStream is)
  throws IOException
{
  String szBuf = "";
  char ch = dis.readChar();

  while (ch >= 0 && ch != '\n')
  {
    szBuf += ch;
    ch = dis.readChar();
  }
  return szBuf;
}

Обратите внимание, что здесь используется метод readChar, читающий символы UNICODE.


Назад Вперед

[Назад]