O artigo está dividido nas seguintes seções:
- Introdução
- Exemplo: Servidor TCP/IP
- Exemplo: Cliente TCP/IP
- Arquitetura Básica
- IoService
- IoSession
- IoHandler
- IoFilter
- Filtro ProtocolCodecFilter
- Criando seu próprio mecanismo de serialização usando MINA
Introdução
O MINA (Multi-Purpose Infrastructure for Network Applications) é um framework Java que facilita a criação de aplicações de rede de alto desempenho e escalabilidade, de forma rápida e produtiva. Ele fornece suporte a vários tipos de protocolos, como TCP, UDP, XML, FTP, HTTP, Serial, etc, além de permitir a criação de protocolos personalizados.
O framework foi construído a partir das seguintes premissas:
- Baseado na API NIO (Non-Blocking I/O)
- Orientada a Eventos (Assíncronos)
- Suporte a Múltiplos Protocolos
- Separação de Conceitos
- Baseado em Inversão de Controle
A vantagem de usar a NIO sobre a IO bloqueante é que as operações envolvidas não precisam aguardar por um resultado imediato (por isso são consideradas assíncronas). Ao criar um servidor TCP/IP, por exemplo, usando IO bloqueante, cada conexão deve ser processada por uma thread separada, pois operações de leitura/escrita podem ficar bloqueadas à espera de resposta, enquanto no NIO uma única thread pode lidar, de forma assíncrona, com várias conexões. Isso faz com que o desempenho do servidor seja mais eficiente.
Exemplo: Servidor TCP/IP
Nota: Para rodar todos exemplos do artigo, as seguintes dependências devem ser obtidas:
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
public class TCPServer {
private static final int PORT = 5050;
public static void main( String[] args ) throws IOException {
IoAcceptor acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast( "logger1", new LoggingFilter() );
acceptor.getFilterChain().addLast( "codec1", new ProtocolCodecFilter(
new TextLineCodecFactory( Charset.forName( "UTF-8" ))));
acceptor.setHandler( new ServerHandler() );
acceptor.bind( new InetSocketAddress(PORT) );
}
}
import java.util.Date;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
public class ServerHandler extends IoHandlerAdapter {
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
String str = message.toString();
System.out.println("Message received: " + str);
if (str.trim().equalsIgnoreCase("quit")) {
session.close(true);
return;
}
if (str.trim().equals("data")) {
Date date = new Date();
session.write(date.toString());
} else {
session.write("echo " + str);
}
System.out.println("Message written...");
}
}
A partir dessas duas classes, já temos um servidor TCP/IP (na porta 5050) capaz de processar comandos de texto. Para realizar testes, basta utilizar o aplicativo telnet:
telnet 127.0.0.1 5050
E uma vez conectado, podem ser enviado ao servidor os seguintes comandos:
- data: será retornado a data/hora atual do sistema
- quit: para finalizar a conexão
- qualquer outro comando: volta como echo.
Analisando o Código
IoAcceptor acceptor = new NioSocketAcceptor();
Todas as classes que implementam a interface SocketAcceptor (SocketAcceptor é subinterface de IoAcceptor) atuam sobre o protocolo TCP/IP. Na versão 2.0.7 do MINA, a única classe concreta que implementa essa interface é a NioSocketAcceptor.
acceptor.getFilterChain().addLast( "logger", new LoggingFilter() );
acceptor.getFilterChain().addLast( "codec", new ProtocolCodecFilter(
new TextLineCodecFactory( Charset.forName( "UTF-8" ))));
Nesse código, 2 filtros são adicionados:
- LoggingFilter: Loga todos os eventos associados a conexão
- ProtocolCodecFilter: Converte dados da conexão em objeto e vice-versa
Podemos aplicar filtros ao IoAcceptor, sendo que cada filtro realizará uma atividade em particular. É similar ao conceito de filtros da API de Servlets (padrão Chain Of Responsibility).
acceptor.setHandler( new ServerHandler() );
acceptor.bind( new InetSocketAddress(PORT) );
Na primeira linha estamos configurando um handler (ServerHandler) para o IoAcceptor. Este handler irá tratar todos os eventos gerados pelo MINA. Na classe ServerHandler da Listagem 2, por exemplo, está sendo sobrescrito o método messageReceived (que é disparado quando algum cliente envia dados), para implementar a lógica da aplicação.
Na segunda linha, realizamos o bind com a porta 5050 e iniciamos o processamento das conexões remotas.
Exemplo: Cliente TCP/IP
import java.net.InetSocketAddress;
import java.nio.charset.Charset;
import java.util.Scanner;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.codec.textline.TextLineCodecFactory;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
public class TCPClient {
private static int TIMEOUT = 1000;
private static String HOSTNAME = "127.0.0.1";
private static int PORT = 5050;
public static void main(String[] args) throws Throwable {
NioSocketConnector connector = new NioSocketConnector();
// Configurando
configureConnector(connector);
// Cria sessao
IoSession session = connect(connector);
if (session != null) {
// Envia comandos do teclado
sendCommands(session);
}
// Encerra conexoes
close(connector, session);
}
/*
* Configura NioSocketConnector
*/
public static void configureConnector(final NioSocketConnector connector) {
connector.setConnectTimeoutMillis(TIMEOUT);
connector.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));
connector.getFilterChain().addLast("logger", new LoggingFilter());
connector.setHandler(new ClientHandler());
}
/*
* Conexao com o server
*/
private static IoSession connect(final NioSocketConnector connector)
throws InterruptedException {
IoSession session = null;
try {
ConnectFuture future = connector.connect(new
InetSocketAddress(HOSTNAME, PORT));
future.awaitUninterruptibly();
session = future.getSession();
} catch (RuntimeIoException e) {
System.err.println("Failed to connect.");
e.printStackTrace();
}
return session;
}
/*
* Envia comando do teclado ao servidor
*/
private static void sendCommands(final IoSession session) {
final Scanner scanner = new Scanner(System.in);
String text;
do {
System.out.println("Entre com texto: ");
text = scanner.nextLine();
session.write(text);
} while (!"quit".equalsIgnoreCase(text));
}
/*
* Encerra conexao
*/
private static void close(final NioSocketConnector connector,
final IoSession session) {
if (session != null) {
if (session.isConnected()) {
session.close(false);
session.getCloseFuture().awaitUninterruptibly();
}
}
connector.dispose();
}
}
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
public class ClientHandler extends IoHandlerAdapter {
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
String str = message.toString();
System.out.println("Message received: " + str);
}
}
A aplicação cliente envia comandos digitados no console para a aplicação servidora. Pode ser usado para testar a classe da Listagem 1 (TCPServer).
Analisando o Código
/*
* Configura NioSocketConnector
*/
public static void configureConnector(final NioSocketConnector connector) {
connector.setConnectTimeoutMillis(TIMEOUT);
connector.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));
connector.getFilterChain().addLast("logger", new LoggingFilter());
connector.setHandler(new ClientHandler());
}
Se no lado servidor usamos SocketAcceptor, para clientes TCP/IP usamos a interface SocketConnector, que é implementada pela classe NioSocketConnector.
Assim como no exemplo do TCPServer, podemos configurar vários tipos de filtro, como o “codec” e o “logger” (esses nomes são usados apenas para identificar o filtro, ou seja, poderiam ser “codec1”, “logger1”, “xyz”, etc), além de definir um handler (ClientHandler), que será chamado toda a vez que um evento relevante acontecer (criação da sessão, erros, recebimento de dados, etc).
/*
* Conexao com o server
*/
private static IoSession connect(final NioSocketConnector connector)
throws InterruptedException {
IoSession session = null;
try {
ConnectFuture future = connector
.connect(new InetSocketAddress(HOSTNAME, PORT));
future.awaitUninterruptibly();
session = future.getSession();
} catch (RuntimeIoException e) {
System.err.println("Failed to connect.");
e.printStackTrace();
}
return session;
}
Efetua a conexão com o servidor. Ao invocar o método connect do NioSocketConnector, obtemos uma instância de objeto que implementa a interface ConnectFuture. Como estamos usando NIO, não podemos usar o objeto session antes de ter certeza que a conexão foi estabelecida. Para isso, usamos o método future.awaitUninterruptibly(), que só retornará quando a conexão estiver ok.
Feito isso, obtemos a instância de IoSession (que representa a conexão/sessão do cliente com o servidor), que pode ser usada, entre outras coisas, para enviar dados do servidor.
/*
* Envia comando do teclado ao servidor
*/
private static void sendCommands(final IoSession session) {
final Scanner scanner = new Scanner(System.in);
String text;
do {
System.out.println("Entre com texto: ");
text = scanner.nextLine();
session.write(text);
} while (!"quit".equalsIgnoreCase(text));
}
Aqui, obtemos dados do teclado (modo console) e o enviamos ao servidor através do IoSession.
/*
* Encerra conexao
*/
private static void close(final NioSocketConnector connector, final IoSession session) {
if (session != null) {
if (session.isConnected()) {
session.close(false);
session.getCloseFuture().awaitUninterruptibly();
}
}
connector.dispose();
}
Por fim, ao digitar “quit”, o método close será chamado. Ele irá encerrar a sessão (session.close()), e liberar o connector (connector.dispose()).
Como estamos trabalhando com NIO, a chamada session.close(false) não interromperá imediatamente as operações pendentes, por isso o método session.getCloseFuture().awaitUninterruptibly() é invocado, para aguardar que todas as operações de leitura/escrita em andamento possam encerrar de forma segura.
Se quisermos interromper todos os trabalhos correntes imediatamente, devemos passar true no parâmetro do método close de IoSession.
Estes 2 exemplos (TCPServer / TCPClient) foram analisados de forma bem superficial, apenas para que o leitor se familiarize com a estrutura de um aplicativo MINA típico, pois nos tópicos a seguir iremos explorar mais a fundo sua arquitetura.
Arquitetura Básica
IoService
A interface IoService abstrai todos os serviços de I/O do MINA e é dividida em 2 subgrupos:
- interface IoAcceptor: Utilizado no lado servidor
- interface IoConnector: Utilizado no lado Cliente
As subinterfaces mais conhecidas de IoAcceptor/IoConnector:
- DatagramAcceptor: Lida com Protocolo UDP (lado Servidor)
- DatagramConnector: Lida com Protocolo UDP (lado Cliente)
- SocketAcceptor: Lida com Protocolo TCP (lado Servidor)
- SocketConnector: Lida com Protocolo TCP (lado Cliente)
Por isso, no exemplo do Servidor TCP/IP, utilizamos a classe NioSocketAcceptor (única classe concreta que implementa SocketAcceptor), e no exemplo do Cliente TCP/IP, a classe NioSocketConnector (que implementa SocketConnector).
É a partir de IoService, que configuramos os filtros (IoFilterChain) e o handler (IoHandler).
IoSession
Representa a conexão/sessão entre dois pontos. Por exemplo, num servidor TCP/IP, para cada conexão feita por um cliente, o MINA irá criar uma instância de IoSession representando-o. É a partir dele que podemos enviar/receber dados, configurar atributos para sessão, estatísticas, etc.
O IoSession também é passado como argumento para todos os eventos do handler. Por exemplo, no caso do Servidor, utilizamos o objeto session do método messageReceived para retornar o dado para o cliente, através do método write().
...
public class ServerHandler extends IoHandlerAdapter {
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
String str = message.toString();
System.out.println("Message received: " + str);
if (str.trim().equalsIgnoreCase("quit")) {
session.close(true);
return;
}
if (str.trim().equals("data")) {
Date date = new Date();
session.write(date.toString()); /* Retornando dado ao cliente */
} else {
session.write("echo " + str); /* Retornando dado ao cliente */
}
Para cada sessão, todos os filtros configurados para o IoService serão aplicados. Portanto, se definirmos um filtro de log, o mesmo será aplicado para todas as sessions.
IoHandler
Processa todos os eventos gerados num IoSession. Os eventos suportados são:
- sessionCreated: quando a uma nova conexão é criada
- sessionOpened: quando a conexão é aberta
- sessionClosed: quando a conexão é fechada
- sessionIdle: quando a conexão fica ociosa (tempo configurável)
- exceptionCaught: quando ocorre alguma exceção
- messageReceived: quando uma mensagem é recebida
- messageSent: quando uma mensagem é enviada
Registramos o handler através do método setHandler do IoService. Para quem programa em Swing, é similar a registrar um listener para monitorar eventos de determinado componente. E assim como no Swing, existe uma classe Adapter chamada IoHandlerAdapter, que fornece uma implementação vazia para todos os eventos. Caso o usuário queira escrever alguma lógica, basta selecionar os eventos que ele quer monitorar, criando uma classe que estenda IoHandlerAdapter.
Esse é o conhecido padrão Observer, utilizado para diminuir o acoplamento entre o objeto gerador do evento e os ouvintes. No MINA só podemos configurar um único handler no IoService, por isso ao invés de um addHandler, temos somente setHandler.
O handler deve ser configurado tanto no lado cliente (IoConnector), quando no lado servidor(IoAdapter).
IoFilter
Podemos adicionar vários filtros ao IoService, para diversos propósitos, como: logging, estatística, thread pooling, blacklist, profile, etc.
Nos exemplos do cliente/servidor TCP/IP, usamos 2 filtros:
connector.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));
connector.getFilterChain().addLast("logger", new LoggingFilter());
O filtro LoggingFilter faz o log dos eventos de mensagem, como messageReceived, messageSent, etc. Como usamos log4J nos exemplos, basta apenas configurar um log4j.properties para logar as operações no Console ou num arquivo de log.
Todos os filtros adicionados ao IoService serão executados, ou seja, o objeto IoSession irá passar por toda a cadeia de filtros (Chain of Responsibility).
Podemos criar nossos próprios filtros, para isso basta implementar a interface IoFilter, que possui os seguintes métodos:
- init()
- destroy()
- onPreAdd()
- onPostAdd()
- onPreRemove()
- sessionCreated()
- sessionOpened()
- sessionClosed()
- sessionIdle()
- exceptionCaught()
- messageReceived()
- messageSent()
- filterClose()
- filterWrite()
A API fornece uma classe adapter, IoFilterAdapter, que oferece uma implementação vazia de todos os métodos da interface IoFilter. Para criar um novo filtro, basta então estender a classe IoFilterAdapter e sobrescrever os eventos que desejar.
Iremos analisar o BlacklistFilter da própria API do MINA, que implementa a lógica de bloquear conexões para endereços não-seguros:
public class BlacklistFilter extends IoFilterAdapter {
private final List<Subnet> blacklist =
new CopyOnWriteArrayList<Subnet>();
public void setBlacklist(InetAddress[] addresses) {
if (addresses == null) {
throw new IllegalArgumentException("addresses");
}
blacklist.clear();
for (int i = 0; i < addresses.length; i++) {
InetAddress addr = addresses[i];
block(addr);
}
}
/**
* Blocks the specified endpoint.
*/
public void block(InetAddress address) {
if (address == null) {
throw new IllegalArgumentException("Adress to block can not be null");
}
block(new Subnet(address, 32));
}
/**
* Blocks the specified subnet.
*/
public void block(Subnet subnet) {
if (subnet == null) {
throw new IllegalArgumentException("Subnet can not be null");
}
blacklist.add(subnet);
}
}
Observe que BlacklistFilter estende IoFilterAdapter e internamente cria um List de endereços não-seguros.
@Override
public void sessionCreated(NextFilter nextFilter, IoSession session) {
if (!isBlocked(session)) {
// forward if not blocked
nextFilter.sessionCreated(session);
} else {
blockSession(session);
}
}
Quando uma conexão é feita, o evento sessionCreated é disparado. Se o InetAddress da nova sessão estiver na lista, a sessão será fechada (blockSession(session)) e a conexão recusada.
Portanto, implementar filtros personalizados é uma tarefa relativamente simples em MINA.
Para ver a implementação completa de BlacklistFilter.
Filtro ProtocolCodecFilter
connector.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new TextLineCodecFactory(Charset.forName("UTF-8"))));
O filtro ProtocolCodecFilter é um dos recursos mais interessantes do MINA, pois permite a configuração do tipo de protocolo que o cliente/servidor irão usar para se comunicar. Nos exemplos, usamos o TextLineCodecFatory, que trata mensagens de texto com delimitador no final (quebra de linha é o delimitador default).
Para ficar mais claro como funciona o ProtocolCodecFilter, iremos implementar um protocolo personalizado usando XStream.
Criaremos uma aplicação cliente que transforma um objeto Java num XML usando XStream, e no servidor iremos converter esse XML novamente num objeto Java (serialização de objetos).
Criando seu próprio mecanismo de serialização usando MINA
package br.com.devmedia.entity;
/**
*
* @author marcelo
*/
public class Pessoa {
private String nome;
private int numeroSorte;
public String getNome() {
return nome;
}
public void setNome(String nome) {
this.nome = nome;
}
public int getNumeroSorte() {
return numeroSorte;
}
public void setNumeroSorte(int numeroSorte) {
this.numeroSorte = numeroSorte;
}
@Override
public String toString() {
return "nome = " + getNome() + ", numeroSorte = " + getNumeroSorte();
}
}
Iremos trafegar essa classe do cliente para o servidor. Utilizaremos o XStream para transformar um objeto pessoa, no lado cliente, num XML:
<br.com.devmedia.entity.Pessoa>
<nome>teste</nome>
<numeroSorte>30</numeroSorte>
</br.com.devmedia.entity.Pessoa>
E depois reconvertê-lo para objeto Pessoa no lado servidor. Para isso devemos fornecer um encoder e um decoder para o ProtocolCodecFilter.
O encoder é responsável por transformar um objeto numa mensagem (binária, texto, data-user, etc).
O decoder converte a mensagem num objeto.
A classe ProtocolCodecFilter recebe uma factory, que implementa a interface ProtocolCodecFactory. Forneceremos então nossa própria fábrica, que registrará nossos objetos encoder/decoder personalizados para o MINA.
package br.com.devmedia.codec;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFactory;
import org.apache.mina.filter.codec.ProtocolDecoder;
import org.apache.mina.filter.codec.ProtocolEncoder;
/**
*
* @author marcelo
*/
public class XStreamCodecFactory implements ProtocolCodecFactory {
private ProtocolEncoder encoder;
private ProtocolDecoder decoder;
public XStreamCodecFactory() {
encoder = new XStreamEncoder();
decoder = new XStreamDecoder();
}
@Override
public ProtocolEncoder getEncoder(IoSession is) throws Exception {
return encoder;
}
@Override
public ProtocolDecoder getDecoder(IoSession is) throws Exception {
return decoder;
}
}
A classe XStreamCodecFactory implementa a interface ProtocolCodecFactory, cujos métodos são:
- getEncoder() - retorna o encoder que será utilizado pelo MINA
- getDecoder() - retorna o decoder que será utilizado pelo MINA
Como estamos criando um protocolo personalizado, devemos fornecer nossa própria implementação de encoder/decoder. Para criar um encoder/decoder:
- encoder: devemos estender a classe abstrata ProtocolEncoderAdapter, que implementa a interface ProtocolEncoder, e codificar o método encode()
- decoder: devemos estender a classe abstrata ProtocolDecoderAdapter, que implementa a interface ProtocolDecoder, e codificar o método decode()
Para esse exemplo, foram criadas as classes XStreamEncoder e XStreamDecoder.
package br.com.devmedia.codec;
import br.com.devmedia.entity.Pessoa;
import com.thoughtworks.xstream.XStream;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolEncoderAdapter;
import org.apache.mina.filter.codec.ProtocolEncoderOutput;
import org.apache.mina.filter.codec.textline.LineDelimiter;
import org.apache.mina.filter.codec.textline.TextLineEncoder;
/**
*
* @author marcelo
*/
public class XStreamEncoder extends ProtocolEncoderAdapter {
private final TextLineEncoder textLineEnconder =
new TextLineEncoder(new LineDelimiter("\0"));
@Override
public void encode(IoSession session, Object o,
ProtocolEncoderOutput out) throws Exception {
Pessoa pessoa = (Pessoa) o;
XStream xstream = new XStream();
String xml = xstream.toXML(pessoa);
textLineEnconder.encode(session, xml, out);
}
}
O método encode transforma o objeto Pessoa num XML usando XStream. Uma vez criado o XML, ele é apenas uma String comum, por isso, usando delegação, foi criado um objeto TextLineEncoder (que já sabe tratar o envio de uma String simples) que irá realizar a tarefa de transformação da mensagem propriamente dita.
Note que o delimitador "\0" foi usado para demarcar o fim do XML a ser enviado pelo TextLineEncoder (o XStream adiciona quebras de linha, por isso não é possível usar o delimitador default).
package br.com.devmedia.codec;
import br.com.devmedia.entity.Pessoa;
import com.thoughtworks.xstream.XStream;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolDecoderOutput;
import org.apache.mina.filter.codec.textline.LineDelimiter;
import org.apache.mina.filter.codec.textline.TextLineDecoder;
/**
*
* @author marcelo
*/
public class XStreamDecoder extends TextLineDecoder {
public XStreamDecoder() {
super(new LineDelimiter("\0"));
}
@Override
protected void writeText(IoSession session, String xml, ProtocolDecoderOutput out) {
Pessoa pessoa;
XStream xstream = new XStream();
pessoa = (Pessoa)xstream.fromXML(xml);
out.write(pessoa);
}
}
Aqui não foi possível usar delegação, pois a classe TextLineDecoder não tem nenhum método público que permita mudar o tipo de objeto manipulado. Tenha em mente que um TextLineDecoder só trabalha com objetos Strings e no nosso protocolo, trafegamos objetos Pessoa.
A solução é usar herança e sobrescrever o método writeText. O método writeText é chamado depois que o Decoder processou todos os dados da rede, transformando-os num objeto String e gravando-o no objeto ProtocolDecoderOutput. Como o método é protected, podemos sobrescrevê-lo na subclasse. Dessa forma interceptamos o objeto String (xml), convertemos ele para um objeto Pessoa, e gravamos esse objeto no ProtocolDecoderOutput.
O objeto gravado no ProtocolDecoderOutput (out.write(object)) será passado como argumento para o método messageReceived do IoHandler:
acceptor.setHandler( new IoHandlerAdapter() {
@Override
public void messageReceived(IoSession session, Object message) throws Exception {
Pessoa pessoa = (Pessoa)message; /* Object se refere a Pessoa */
System.out.println("pessoa = " + pessoa.toString());
}
});
As classes TextLineDecoder/TextLineEncoder já possuem toda a lógica de comunicação de mensagens de texto entre cliente e servidor. Para ver como essas classes manipulam os dados que vem da rede:
Por fim, os códigos da classe cliente e servidor, utilizados para serialização de objetos com XStream:
package br.com.devmedia.app;
import br.com.devmedia.codec.XStreamCodecFactory;
import br.com.devmedia.entity.Pessoa;
import java.net.InetSocketAddress;
import java.util.Random;
import java.util.Scanner;
import org.apache.mina.core.RuntimeIoException;
import org.apache.mina.core.future.ConnectFuture;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketConnector;
public class XStreamClient {
private static int TIMEOUT = 1000;
private static String HOSTNAME = "127.0.0.1";
private static int PORT = 5050;
public static void main(String[] args) throws Throwable {
NioSocketConnector connector = new NioSocketConnector();
configureConnector(connector);
IoSession session = connect(connector);
if (session != null) {
// Envia comandos do teclado
sendCommands(session);
}
// Encerra conexoes
close(connector, session);
}
/*
* Configura NioSocketConnector
*/
public static void configureConnector(final NioSocketConnector connector) {
connector.setConnectTimeoutMillis(TIMEOUT);
connector.getFilterChain().addLast("codec",
new ProtocolCodecFilter(new XStreamCodecFactory()));
connector.getFilterChain().addLast("logger", new LoggingFilter());
connector.setHandler(new IoHandlerAdapter());
}
/*
* Conexao com o server
*/
private static IoSession connect(final NioSocketConnector
connector) throws InterruptedException {
IoSession session = null;
try {
ConnectFuture future = connector.connect(new
InetSocketAddress(HOSTNAME, PORT));
future.awaitUninterruptibly();
session = future.getSession();
} catch (RuntimeIoException e) {
System.err.println("Failed to connect.");
e.printStackTrace();
Thread.sleep(5000);
}
return session;
}
/*
* Envia comando do teclado ao servidor
*/
private static void sendCommands(final IoSession session) {
final Scanner scanner = new Scanner(System.in);
String text;
do {
System.out.println("Entre com o nome da pessoa: ");
text = scanner.nextLine();
Pessoa pessoa = new Pessoa();
pessoa.setNome(text);
pessoa.setNumeroSorte(new Random().nextInt(100));
session.write(pessoa);
} while (!"quit".equalsIgnoreCase(text));
}
/*
* Encerra conexao
*/
private static void close(final NioSocketConnector connector,
final IoSession session) {
if (session != null) {
if (session.isConnected()) {
session.close(false);
session.getCloseFuture().awaitUninterruptibly();
}
}
connector.dispose();
}
}
package br.com.devmedia.app;
import br.com.devmedia.codec.XStreamCodecFactory;
import br.com.devmedia.entity.Pessoa;
import java.io.IOException;
import java.net.InetSocketAddress;
import org.apache.mina.core.service.IoAcceptor;
import org.apache.mina.core.service.IoHandlerAdapter;
import org.apache.mina.core.session.IoSession;
import org.apache.mina.filter.codec.ProtocolCodecFilter;
import org.apache.mina.filter.logging.LoggingFilter;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
public class XStreamServer {
private static final int PORT = 5050;
public static void main( String[] args ) throws IOException {
IoAcceptor acceptor = new NioSocketAcceptor();
acceptor.getFilterChain().addLast( "logger1", new LoggingFilter() );
acceptor.getFilterChain().addLast( "codec1", new ProtocolCodecFilter
(new XStreamCodecFactory()));
acceptor.setHandler( new IoHandlerAdapter() {
@Override
public void messageReceived(IoSession session, Object message)
throws Exception {
Pessoa pessoa = (Pessoa)message;
System.out.println("pessoa = " + pessoa.toString());
}
});
acceptor.bind( new InetSocketAddress(PORT) );
}
}
A aplicação cliente se conecta ao servidor, cria um objeto pessoa, lê o nome da pessoa via console (classe Scanner), configura um número da sorte aleatório e o envia para o servidor.
Na própria documentação/fonte do MINA há outros exemplos para aprendizado.
Conclusão
O MINA permite a criação de aplicações de rede robustas, abstraindo boa parte da complexidade do NIO. Sua arquitetura baseada em eventos assíncronos, padrões (Chain of Responsibility / Observer / Adapter / etc) e voltada ao uso de interfaces permite a construção de aplicações modulares e com baixo acoplamento (é muito fácil utilizar o MINA com um framework de injeção de dependências), além de facilitar a construção de testes unitários.
Referências
- Java NIO Tutorial
- MINA 2.0 User Guide
- Conectividade com MINA
- Implementing XML Decoder for Apache MINA
- Dynamic Programming
- RAPID NETWORK APPLICATION DEVELOPMENT WITH APACHE MINA
- Network Application Framework with Java
- The high-performance protocol construction toolkit
- Introduction to MINA
- High-Performance I/O with Java NIO