Итак, пришло время того самого момента, для чего мы все это затеяли — сделать соединение между клиентом и сервером.
В этой части мы создадим сервер при помощи библиотеки Netty, сделаем библиотеку на AS3 и проверим её. Чтобы понять процесс остановимся на простой реализации — клиент будет отправлять серверу сообщение, а сервер будет отправлять его обратно (это называется echo-сервер).
Делать мы это будем путем расширения кода, написанного в предыдущих частях.
Создаем java-socket сервер.
Создание сервера на Netty – процесс очень простой. Достаточно раз понять архитектуру этой библиотеки. Подробно с работой этой библиотеки можно познакомиться на сайте проекта.
Создаем новый канал.
Первым делом создаем фабрику для создания и управления Каналами и связанными ресурсами. Она будет обрабатывать все I/O запросы и генерировать ChannelEvent, которые мы уже будем обрабатывать
private static void initServer() {
factory = new NioServerSocketChannelFactory(Executors.newCachedThreadPool(), Executors.newCachedThreadPool());
initGeneralChannelHandler();
}
Дальше создаем и настраиваем сам канал:
private static void initGeneralChannelHandler() {
// Configure the server.
ServerBootstrap bootstrap = new ServerBootstrap(factory);
generalChannelHandler = new GeneralChannelHandler();
// Set up the pipeline factory.
bootstrap.setPipelineFactory(new ChannelPipelineFactory() {
public ChannelPipeline getPipeline() {
return Channels.pipeline(generalChannelHandler);
}
});
//Socket options
bootstrap.setOption("child.tcpNoDelay", true);
bootstrap.setOption("child.keepAlive", true);
// Bind and start to accept incoming connections.
bootstrap.bind(new InetSocketAddress(properties.getInt("server.port")));
}
ServerBootstrap является вспомогательным классом для настройки сервера. Использовать его необязательно, но это удобный стандартный вариант для создания канала. Итак, мы создаем и настраиваем ChannelPipelineFactory: при каждом новом соединении будет вызываться getPipeline() — в нашем случае это наш созданный класс GeneralChannelHandler (о нем будет написано ниже). Также можно указать специфические параметры канала (bootstrap.setOption(«child.*», …)). И последним делом мы указываем порт по которому будет происходить соединение — этот параметр мы берем из файла condif.xml:
<entry key="server.port">9777</entry>
Теперь подробней об GeneralChannelHandler.
GeneralChannelHandler должен быть наследником SimpleChannelHandler. Этот класс является обработчиком практически всех событий, которые могут происходить с сокетами. В нем и происходит основная магия – это мост между логикой сервера и Netty. В нашем примере мы переопределим 2 события:
- Событие получения сообщения: messageReceived
В нем мы будем отправлять в канал клиента то, что получили от него. - Событие появления исключения: exceptionCaught
Будем записывать с лог все ошибки, которые будут генерироваться сервером.
На самом деле это все делается элементарно:
public class GeneralChannelHandler extends SimpleChannelHandler {
private static final Logger logger = Logger.getLogger(GeneralChannelHandler.class);
@Override
public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) throws Exception {
// Send back the received message to the remote peer.
e.getChannel().write(e.getMessage());
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) throws Exception {
// Log the exception
logger.error("Exception", e.getCause());
}
}
Все — теперь у нас есть echo-сервер сервер. Можно подключиться по telnet и убедиться в его работе: любое передаваемое на сервер сообщение будет возвращаться обратно.
Создаем swc-библиотеку.
Для библиотеки я использовал следующую архитектуру, которая мне показалась уместной (JSS – Java Soket Server):
JSSApi – основной класс для работы с нашим java-сервером. Сейчас он просто создает экземпляр класса соединения и делегирует основную работу JSSConnection. Сейчас в этом нет никакого смысла но нейрон, отвечающий за программирование говорит, что должна быть разница между «Соединением» и «Api».
public class JSSApi extends EventDispatcher{
private var _connection:JSSConnection;
public function JSSApi(debug:Boolean = false) {
_connection = new JSSConnection(this, debug);
}
/*---------------------------- Delegated methods ------------------------*/
...
}
JSSConnection – класс, который будет заниматься низкоуровневой работой с сокетами. Код приведен не полностью: я обращу Ваше внимание на метод соединения, отправку и получения данных. Полные исходники можно будет скачать по ссылке в конце статьи.
public class JSSConnection {
public function JSSConnection(dispatcher:IEventDispatcher, debug:Boolean = false) {
_dispatcher = dispatcher;
_debug = debug;
createSocketAndAddHandlers();
}
...
public function connect(host:String, port:int):void {
_host = host;
_port = port;
try {
log("Connect to " + _host + ":" + _port);
_socket.connect(_host, _port);
} catch (error:SecurityError) {
log("Security error: " + error);
_dispatcher.dispatchEvent(new JSSEvent(JSSEvent.SECURITY_ERROR, error.toString()));
} catch (error:Error) {
log("Connection error: " + error);
throw error;
}
}
...
public function sendRequest(request:String):void {
if (_socket != null && _socket.connected) {
request += "\n";
try {
_socket.writeUTFBytes(request);
_socket.flush();
log("Message sent: " + request.toString());
} catch(error:Error) {
log("Error sending data: " + error);
}
} else {
if (_isConnected) {
dispatchConnectionLost(ClientDisconnectionReason.SOCKED_CONNECTED_FAIL);
} else {
throw new Error("Sending request without connection");
}
}
}
...
private function handleSocketData(event:ProgressEvent):void {
receiveData(_socket.readUTFBytes(_socket.bytesAvailable));
}
private function receiveData(msg:String):void {
log("Message received: " + msg);
_dispatcher.dispatchEvent(new JSSEvent(JSSEvent.RECEIVE_DATA, msg));
}
...
}
Компилируем swc библиотеку для работы с сервером. Теперь осталось убедиться, что наш сервер работает. Для этого создадим тестовый клиент и добавить его в доверенные (добавить в FlashPlayerTrust):
public class Flash_client extends Sprite {
private var _jss:JSSApi;
public function Flash_client() {
_jss = new JSSApi(true);
_jss.addEventListener(JSSEvent.CONNECT_TO_SERVER_SOCKET, handleConnect)
_jss.addEventListener(JSSEvent.RECEIVE_DATA, handleReceiveData)
trace("Connecting...");
_jss.connect("localhost", 9777);
}
private function handleConnect(event:JSSEvent):void {
trace("Connected");
var messageToServer:String = "Hello!";
trace("Sending Data: " + messageToServer);
_jss.sendRequest(messageToServer);
}
private function handleReceiveData(event:JSSEvent):void {
var messageFromServer:String = String(event.data);
trace("Received: " + messageFromServer);
}
}
В результате в консоли клиента мы увидим успешно отработанный сценарий (строки с временем – это служебный trace сгенерированный классом JSSConnection):
[trace] Connecting... [trace] 23:28:08.691 Connect to localhost:9777 [trace] 23:28:08.865 Connected: [Event type="connect" bubbles=false cancelable=false eventPhase=2] [trace] Connected [trace] Sending Data: Hello! [trace] 23:28:08.869 Message sent: Hello! [trace] 23:28:08.874 Message received: Hello! [trace] Received: Hello!
Это, конечно, ещё незавершенная версия даже для такой небольшой функциональности нужно сделать ряд дополнений. Но уже сейчас видна основная структура работы сервера. Если у Вас возникнут сложности – готов помочь и дополнить статью необходимыми данными.
Исходный код.
Полный код проекта всегда можно будет взять из репозитория на Google code. Коммит с приведенной выше версией находится тут.