В статье Reddwarf для создания Java-сервера на примере онлайн-игры «Камень-ножницы-бумага»: Сервер было описано, как сделать сервер. Но поиграть на таком сервере без клиента невозможно. Поэтому в этой статье попробуем написать простейший клиент и поэкспериментировать с сервером.
Для разработки клиента скачиваем клиентские библиотеки Reddwarf в архиве sgs-client-dist-0.10.2.zip отсюда.
Создаем проект и подключаем к нему все библиотеки из директории lib скачанного архива.
Как ни странно, но попытка написать клиента командной строки привела к более громоздкому и запутанному коду, поэтому клиент будем делать графический на Swing.
Клиентская часть нашей игры будет состоять из двух классов — клиента и GUI.
Клиент реализует интерфейс SimpleClientListener — в него приходят события подключения/отключения, и сообщения от сервера. В качестве реакции на входящие сообщения клиент будет уведомлять об этом GUI. Кроме того, вся информация о протоколе будет содержаться только в этом классе, так что он будет заведовать и отправкой сообщений.
Подключаемся к указанному серверу на порт 62964 — это порт по умолчанию для Reddwarf.
Для аутентификации будем использовать простейший (и, наверное, самый часто используемый в Reddwarf) способ аутентификации — PasswordAuthentication. Поскольку наш сервер не проверяет пароль, пароль оставляем пустым.
Код Client.java полностью
GUI представляет собой простейшую формочку с полями ввода и кнопочками:

Исходный код формы ClientFrame.java можно посмотреть здесь
Код класса Messages.java аналогичен такому же файлу из предыдущей статьи.
Код готов, можно запускать сервер и клиенты и смотреть, что будет.
Запускаем сервер.
Запускаем два клиента:

Логинимся с одного и другого клиента:

Как видим, у обоих игроков 0 очков на счету.
Лог сервера не заставляет себя ждать: поскольку мы первый раз запустили сервер, то в базе нет ни одного игрока, происходит автоматическая регистрация игроков player1 и player2.
Жмем у одного из игроков на Play — сервер автоматически выбирает второго игрока в качестве соперника и начинается бой:

По логу видно, что сервер создал новую битву. Сервер ждет ответов игроков
Игроки отвечают:
Сервер ждет окончания времени битвы и подводит итоги:
На клиенте можно увидеть результаты боя и как изменилось количество очков: выигравший получил 2 очка, проигравший — ноль.

Для того, чтобы убедиться, что данные сохранены — отключимся клиентом, перезапустим сервер и подключимся заново игроком player2.
Подключаемся клиентом: честно заработанные 2 очка сохранились.

Во время перезапуска в логе могут возникать подобные сообщения (со стектрейсом):
Они появляются из-за того, что при старте сервера происходит много одновременных обращений к общим объектам сервера. Поскольку многопоточность в Reddwarf базируется на неблокирующей синхронизации, то конфликтующие транзакции отказываются и производится повторная попытка выполнения действий. Платформа настроена таким образом, что при большом количестве откатов одной и той же транзакции выводится сообщение в лог. При старте сервера эти сообщения не должны вызывать беспокойства, а вот появление подобных сообщений во время работы сервера говорит об узких местах в коде серверной логики.
Исходный код клиента и сервера выложен на code.google.com, его можно закачать с SVN:
А если вы не хотите возиться с исходниками, то скомпилированные версии клиента и сервера можно найти тут: code.google.com/p/reddwarf-rock-paper-scissors-example/downloads/list
Клиент запускается с помощью команды
Для запуска сервера файл deploy.jar необходимо скопировать в директорию sgs-server-dist-0.10.2.1/deploy/ и выполнить команду запуска сервера:
Спасибо за внимание. Буду рад любой конструктивной критике.
Подготовка к работе
Для разработки клиента скачиваем клиентские библиотеки Reddwarf в архиве sgs-client-dist-0.10.2.zip отсюда.
Создаем проект и подключаем к нему все библиотеки из директории lib скачанного архива.
Коротко о клиенте
Как ни странно, но попытка написать клиента командной строки привела к более громоздкому и запутанному коду, поэтому клиент будем делать графический на Swing.
Клиентская часть нашей игры будет состоять из двух классов — клиента и GUI.
Клиент реализует интерфейс SimpleClientListener — в него приходят события подключения/отключения, и сообщения от сервера. В качестве реакции на входящие сообщения клиент будет уведомлять об этом GUI. Кроме того, вся информация о протоколе будет содержаться только в этом классе, так что он будет заведовать и отправкой сообщений.
Подключение и аутентификация
Подключаемся к указанному серверу на порт 62964 — это порт по умолчанию для Reddwarf.
Для аутентификации будем использовать простейший (и, наверное, самый часто используемый в Reddwarf) способ аутентификации — PasswordAuthentication. Поскольку наш сервер не проверяет пароль, пароль оставляем пустым.
Код Client.java полностью
public class Client implements SimpleClientListener {
private SimpleClient simpleClient;
private final String host;
private final String username;
public static final String DEFAULT_PORT = "62964";
private final ClientFrame frame;
public Client(String host, String username, ClientFrame frame) {
this.host = host;
this.username = username;
this.frame = frame;
simpleClient = new SimpleClient(this);
}
@Override
public PasswordAuthentication getPasswordAuthentication() {
return new PasswordAuthentication(username, new char[]{});
}
@Override
public void loggedIn() {
frame.loggedIn();
}
@Override
public void loginFailed(String s) {
frame.setStatus("login failed " + username+": "+s);
}
@Override
public void receivedMessage(ByteBuffer packet) {
String text = Messages.decodeString(packet);
if (text.startsWith("SCORE")) {
frame.setScore(text);
} else if (text.startsWith("BATTLE")) {
frame.startBattle(text);
} else if (text.startsWith("DRAW")) {
frame.setBattleResult(text);
} else if (text.startsWith("WON")) {
frame.setBattleResult(text);
} else if (text.startsWith("LOST")) {
frame.setBattleResult(text);
} else if (text.startsWith("ERROR")) {
frame.setStatus(text);
}
}
public void login() {
try {
Properties connectProps = new Properties();
connectProps.put("host", host);
connectProps.put("port", DEFAULT_PORT);
simpleClient.login(connectProps);
} catch (Exception e) {
e.printStackTrace();
disconnected(false, e.getMessage());
}
}
public void play() {
try {
simpleClient.send(Messages.encodeString("PLAY"));
} catch (IOException e) {
e.printStackTrace();
}
}
public void answer(String text) {
try {
simpleClient.send(Messages.encodeString(text));
} catch (IOException e) {
e.printStackTrace();
}
}
}
Интерфейс пользователя
GUI представляет собой простейшую формочку с полями ввода и кнопочками:

Исходный код формы ClientFrame.java можно посмотреть здесь
Код класса Messages.java аналогичен такому же файлу из предыдущей статьи.
Ура, запускаем!
Код готов, можно запускать сервер и клиенты и смотреть, что будет.
Запускаем сервер.
мар 03, 2012 6:53:29 PM com.sun.sgs.impl.kernel.Kernel <init>
INFO: The Kernel is ready, version: 0.10.2.1
мар 03, 2012 6:53:29 PM com.sun.sgs.impl.service.data.store.DataStoreImpl <init>
INFO: Creating database directory : C:\sgs-server-dist-0.10.2.1\data\dsdb
мар 03, 2012 6:53:29 PM com.sun.sgs.impl.service.watchdog.WatchdogServerImpl registerNode
INFO: node:com.sun.sgs.impl.service.watchdog.NodeImpl[1,health:GREEN,backup:(none)]@black registered
мар 03, 2012 6:53:30 PM hello.reddwarf.server.Server initialize
INFO: Starting new Rock-Paper-Scissors Server. Initialized database.
мар 03, 2012 6:53:30 PM com.sun.sgs.impl.kernel.Kernel startApplication
INFO: RockPaperScissors: application is ready
мар 03, 2012 6:53:30 PM hello.reddwarf.server.OnlineLoggerTask run
INFO: Online: 0
Запускаем два клиента:


Логинимся с одного и другого клиента:


Как видим, у обоих игроков 0 очков на счету.
Лог сервера не заставляет себя ждать: поскольку мы первый раз запустили сервер, то в базе нет ни одного игрока, происходит автоматическая регистрация игроков player1 и player2.
мар 03, 2012 6:53:47 PM hello.reddwarf.server.Server loggedIn
INFO: Client login: player2
мар 03, 2012 6:53:47 PM hello.reddwarf.server.Server loadOrRegister
INFO: Registering new player player2
мар 03, 2012 6:53:48 PM hello.reddwarf.server.Server loggedIn
INFO: Client login: player1
мар 03, 2012 6:53:48 PM hello.reddwarf.server.Server loadOrRegister
INFO: Registering new player player1
мар 03, 2012 6:53:50 PM hello.reddwarf.server.OnlineLoggerTask run
INFO: Online: 2
Жмем у одного из игроков на Play — сервер автоматически выбирает второго игрока в качестве соперника и начинается бой:


По логу видно, что сервер создал новую битву. Сервер ждет ответов игроков
мар 03, 2012 6:53:54 PM hello.reddwarf.server.Player play
INFO: Choosing enemy for player1
мар 03, 2012 6:53:54 PM hello.reddwarf.server.Battle <init>
INFO: Created Battle{2} for {player1} and {player2}
мар 03, 2012 6:53:54 PM hello.reddwarf.server.Battle start
INFO: Started Battle{2}
Игроки отвечают:
мар 03, 2012 6:53:56 PM hello.reddwarf.server.Battle answer
INFO: Battle{2} Player {player1} answer ROCK
мар 03, 2012 6:53:57 PM hello.reddwarf.server.Battle answer
INFO: Battle{2} Player {player2} answer PAPER
Сервер ждет окончания времени битвы и подводит итоги:
мар 03, 2012 6:53:59 PM hello.reddwarf.server.Battle finish
INFO: Battle{2} finished. Answers: {player1}->ROCK {player2}->PAPER
На клиенте можно увидеть результаты боя и как изменилось количество очков: выигравший получил 2 очка, проигравший — ноль.


Для того, чтобы убедиться, что данные сохранены — отключимся клиентом, перезапустим сервер и подключимся заново игроком player2.
java -jar bin/sgs-stop.jar
java -jar bin/sgs-boot.jar
Подключаемся клиентом: честно заработанные 2 очка сохранились.


Во время перезапуска в логе могут возникать подобные сообщения (со стектрейсом):
WARNING: Task has been retried 25 times: com.sun.sgs.impl.service.session.ClientSessionServiceImpl$RemoveNodeSpecificDataTask[owner:app:RockPaperScissors]
WARNING: Task has been retried 25 times: com.sun.sgs.impl.service.channel.ChannelServiceImpl$RemoveChannelServerProxyTask[owner:app:RockPaperScissors]
WARNING: Task has been retried 50 times: com.sun.sgs.impl.service.session.ClientSessionServiceImpl$RemoveNodeSpecificDataTask[owner:app:RockPaperScissors]
WARNING: Task has been retried 50 times: com.sun.sgs.impl.service.channel.ChannelServiceImpl$RemoveChannelServerProxyTask[owner:app:RockPaperScissors]
WARNING: Task has been retried 75 times: com.sun.sgs.impl.service.session.ClientSessionServiceImpl$RemoveNodeSpecificDataTask[owner:app:RockPaperScissors]
WARNING: Task has been retried 75 times: com.sun.sgs.impl.service.channel.ChannelServiceImpl$RemoveChannelServerProxyTask[owner:app:RockPaperScissors]
Они появляются из-за того, что при старте сервера происходит много одновременных обращений к общим объектам сервера. Поскольку многопоточность в Reddwarf базируется на неблокирующей синхронизации, то конфликтующие транзакции отказываются и производится повторная попытка выполнения действий. Платформа настроена таким образом, что при большом количестве откатов одной и той же транзакции выводится сообщение в лог. При старте сервера эти сообщения не должны вызывать беспокойства, а вот появление подобных сообщений во время работы сервера говорит об узких местах в коде серверной логики.
Исходный код клиента и сервера выложен на code.google.com, его можно закачать с SVN:
svn checkout http://reddwarf-rock-paper-scissors-example.googlecode.com/svn/trunk/ reddwarf-rock-paper-scissors-example-read-only
А если вы не хотите возиться с исходниками, то скомпилированные версии клиента и сервера можно найти тут: code.google.com/p/reddwarf-rock-paper-scissors-example/downloads/list
Клиент запускается с помощью команды
java -jar HelloReddwarfClient.jar
Для запуска сервера файл deploy.jar необходимо скопировать в директорию sgs-server-dist-0.10.2.1/deploy/ и выполнить команду запуска сервера:
java -jar bin/sgs-boot.jar
Спасибо за внимание. Буду рад любой конструктивной критике.