Опубликован: 10.10.2010 | Уровень: специалист | Доступ: платный
Лекция 4:

Пример использования API java.net

< Лекция 3 || Лекция 4: 1234 || Лекция 5 >

Второй пример

Рассмотренный в предыдущем примере способ взаимодействия имеет очень существенный недостаток, который происходит от выбранного нами протокола взаимодействия между клиентом и сервером (что еще раз подчеркивает важность правильного выбора протокола). Этот недостаток состоит в том, что наш протокол полностью не защищен от ошибок приложения. Предположим, например, что наш клиент при передаче операции заведения новой карты вместо того, чтобы передать три параметра (код операции, имя клиента и номер карты), случайно передал четыре - код операции, имя клиента, фамилия клиента и номер карты (такое возможно в случае ошибки программиста, реализующего приложение-клиент, или в случае появления новой, расширенной версии приложения). Поскольку сервер ничего об этом не знает, он считает из потока три параметра, а четвертый (номер карты) будет интерпретирован как код следующей операции. Таким образом, использование явной передачи параметров методов может приводить к трудноуловимым ошибкам.

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

Мы могли бы модифицировать протокол из предыдущего примера, введя в него разделители команд или предусмотрев первоначальный обмен клиента и сервера метаданными, описывающими передаваемые объекты - существует масса способов достичь желаемого результата. Однако, поскольку приложение написано на языке Java, разумно воспользоваться средствами, уже встроенными в эту платформу. Речь идет о сериализации объектов.

В самом деле, рассмотрим еще раз нашу задачу. Все, что необходимо, - реализовать механизм обмена структурами данных (или объектами) между клиентом и сервером. И встроенный механизм сериализации в этом случае нам идеально подходит. Мы будем передавать по сети не отдельные "поля", а целые объекты.

Первое, что необходимо сделать, - выделить эти "транспортные" объекты. В нашем случае таких объектов будет два - объект "карта" и объект "операция над картой". Поскольку в нашем примере рассматривается система, выполняющая очень небольшое число действий, все операции системы можно свести к двум типам - операции, в которых аргументом является карта целиком, и операции, в которых в качестве аргумента выступает начисление/списание средств с карты. К первому типу относятся операции заведения новой карты и операция получения баланса карты (баланс мы будем считать ее атрибутом), ко второму - операции пополнения баланса и расчета.

Класс CardOperation

Первый класс, который мы реализуем, - класс "операция над картой" (пример 4.4).

1  package com.asw.net.ex2;
2  import java.util.*;
3  import java.io.*;
4  
5  
6  public class CardOperation implements Serializable {
7  public CardOperation(String card,double amount,Date operationDate){
8    this.card = card;
9    this.amount = amount;
10    this.operationDate = operationDate;
11  }
12  public String card;
13  public double amount;
14  public Date operationDate;
15  }
Листинг 4.4. Транспортный класс CardOperation

Первое, что стоит отметить: класс CardOperation объявлен реализующим интерфейс Serializable.Этот интерфейс является "тэгирующим" - он не содержит полей или методов и служит только для того, чтобы сообщить о том, что данный класс сериализуем.

Класс содержит три поля, которые и составляют его полезное наполнение - номер карты, сумма начисляемых/списываемых средств и дата операции.

Класс Card

Второй транспортный класс представляет собой представление карты и выглядит следующим образом (пример 4.5).

1  package com.asw.net.ex2;
2  import java.io.Serializable;
3  import java.util.*;
4  
5  public class Card implements Serializable{
6  public Card(String person, Date createDate, String cardNumber,double balance){
7  this.person = person;
8  this.createDate = createDate;
9  this.cardNumber = cardNumber;
10  this.balance = balance;
11  }
12  public String person;
13  public transient Date createDate;
14  public String cardNumber;
15  public double balance;
16  public String toString(){
17  return "Card: cardNumber="+cardNumber+"\tBalance="+balance+"\tPerson="+person+"\tCreateDate="+createDate+"";
18  }
19  }
Листинг 4.5. Транспортный класс Card

Поскольку его тоже предстоит передавать по сети, он также объявлен как сериализуемый и содержит следующие поля: ФИО клиента, дату заведения карты, номер карты и текущий баланс. Для удобства вывода на экран перекрыт метод toString(), который распечатывает значения всех полей класса.

Перейдем теперь к серверным классам.

Класс BillingService

Первый из серверных классов - BillingService (пример 4.6).

1  package com.asw.net.ex2;
2  import java.net.*;
3  import java.util.Hashtable;
4  import java.io.*;
5  
6  public class BillingService extends Thread{
7  private int serverPort = 7896;
8  private ServerSocket ss;
9  private  Hashtable hash;
10  
11  public static void main(String[] args) {
12    BillingService bs = new BillingService();
13    bs.start();
14  }
15  
16  public BillingService(){
17    hash = new Hashtable();
18  }
19  
20  public void run(){
21    try {
22      ss = new ServerSocket(serverPort);
23      System.out.println("Server started");
24      while(true){
25        System.out.println("new client waiting...");
26        Socket s = ss.accept();
27        System.out.println("Client accepted");
28        BillingClientService bcs = new BillingClientService(this,s);
29        System.out.println("bcs created");
30        bcs.start();
31      }
32    } catch (IOException e) {
33      e.printStackTrace();
34    }
35    
36  }
37  
38  public void addNewCard(Card card) {
39    hash.put(card.cardNumber, card);
40  }
41  public void addMoney(String card, double money) {
42    Card c = (Card)hash.get(card);
43    if (c==null) {
44      System.out.println("Bad Card number\n");
45      return;
46    };
47    c.balance+=money;
48    hash.put(card,c);
49  }
50  public Card getCard(String card){
51    return (Card)hash.get(card);
52  }
53  }
Листинг 4.6. Серверный класс BillingService

Несложно заметить, что он очень похож на BillingService из предыдущего примера. И действительно, он точно так же хранит хэш-таблицу карт, имеет методы для обработки соответствующих событий - добавления новой карты, запроса баланса карты и обработки операций изменения баланса. Основной цикл run (строки 21-32) тоже точно такой же - сначала создается серверный сокет, затем ожидаются соединения клиентов. При появлении нового клиента создается экземпляр класса BillingClientService (строка 28), который и занимается всем дальнейшим обслуживанием клиента.

Однако есть и отличие - теперь в качестве элемента хранения в хэш-таблице используются экземпляры класса Card (ключом по-прежнему выступает ее номер), а метод getCard,заменивший метод getCardBalance,возвращает не скалярное значение, а экземпляр класса Card.

< Лекция 3 || Лекция 4: 1234 || Лекция 5 >
Алмаз Мурзабеков
Алмаз Мурзабеков
Прохожу курс "Построение распределенных систем на Java" в третьей лекции где описывается TCPServer вылетает эта ошибка
"Connection cannot be resolved to a type"


Java version 1.7.0_05
Александр Хвостов
Александр Хвостов
Россия
Максим Лютов
Максим Лютов
Россия, СПб, Политех, 2012