Aos Primórdios do Java: Network, Threads e Socket [1 ed.]

Este curso foi desenvolvido para que você tenha um conhecimento geral de Java alem dos conceitos de rede e multiprocess

168 96 882KB

Portuguese Pages [35] Year 2022

Report DMCA / Copyright

DOWNLOAD PDF FILE

Recommend Papers

Aos Primórdios do Java: Network, Threads e Socket [1 ed.]

  • 0 0 0
  • Like this paper and download? You can publish your own PDF file online for free in a few minutes! Sign Up
File loading please wait...
Citation preview

Este curso foi desenvolvido para que você tenha um conhecimento geral de Java alem dos conceitos de rede e multiprocessamento da linguagem. Ele está dividido nos seguintes módulos: Introdução a Java Network Esta parte visa dar uma visão das classes básicas da linguagem Java para uso em redes. Threads Nesse módulo é apresentada uma introdução à programação multithreading da linguagem Java. Sockets Aqui é apresentado o conceito de sockets, comunicação TCP/IP, como fazer programas tanto do lado cliente como do servidor. É feita também uma breve introdução aos data gramas utilizados para comunicação UDP.

RMI Neste módulo é explicado o RMI que possibilita a execução de métodos que estejam em outros computadores.

Objetivos

Após completar este módulo, o treinando deverá ser capaz de: -Entender o que é Java Network Programming

- Rever conceitos básicos de redes

- Utilizar as classes básicas de acesso a redes para identificacão de máquinas, representação de URLs, codificação/decodificação de URLs e conexão com URLs

Transparência

Java Network Programming

Java oferece inúmeras bibliotecas para conexão e transferência de dados em redes. As principais tarefas são: - Identificar computadores (URL)

-Realizar comunicação entre computadores (Sockets, Data gramas) Disponibilizar serviços Web (Servlets,JSP)

- Acessar dados de Bancos de Dados (JDBC)

- Executar métodos remotos Java (RMI)

- Executar métodos remotos de outras linguagens (CORBA) Notas A programação em rede pode ser uma tarefa bastante árdua já que envolve o conhecimento sólido de hardware e software (sistema operacional,protocolos e suas camadas). Entretanto, o ambiente Java torna transparente para o programador os detalhes internos do ambiente possibilitando uma programação mais segura e mais voltada para a aplicação. Com as classes de rede fornecidas pelo Java é possível, entre outras coisas: a) Recuperar a identificação de computadores;

b) Fazer downloads com ou sem rastreamento;

c)Realizar a comunicação entre computadores (sockets): conexão, transferência de dados, etc. Esta comunicação é feita de maneira bastante simples: o fluxo de informações é tratado como um arquivo (stream); d) Disponibilizar serviços em páginas na Web através de Servidores Java (servlets,JSP, JSF, entre outras mais atuais); e) Acessar bases de dados através do JDBC, ou algum framework de persistência, como é mais utilizado hoje em dia. f) Utilizar código de outra linguagem e de outras plataformas (CORBA). Conceitos Básicos Rede: conjunto de computadores e outros dispositivos que trocam informações.

Camadas: A informação que trafega em uma rede é enviada e recebida através de camadas de protocolos. Portas: é um número que indica um determinado serviço de comunicação que um computador pode oferecer

URL: Uniform Resource Locator é uma referência única para um determinado recurso na rede.

Esta seção tem como intuito apresentar de maneira rápida e simplificada os principais conceitos necessários para a programação em redes com Java. Para uma explanação mais aprofundada você deverá consultar livros específicos sobre redes e Internet. Redes Conjunto de computadores e outros dispositivos que podem trocar informações entre si. Os elementos da rede podem estar conectados através de cabos (par trançado, fibra ótica) ou mesmo através de linhas telefônicas e rádio, hoje em dia é mais comum as redes sem fio como WIFI ou bluetooth As redes são todas baseadas em pacotes, ou seja todos os dados que trafegam pela rede são separados em subconjuntos e cada um destes pacotes é manipulado separadamente. A vantagem deste método é que diversos computadores podem partilhar da rede ao mesmo tempo. Para que não haja confusão na identificação do destino e da origem de um pacote faz-se necessário um conjunto de regras bem definidas. Este conjunto de regras tem o nome de protocolo. Camadas de uma Rede Existem diversas demandas de comunicação diferentes dentro de uma rede. Por exemplo, uma placa de rede apenas envia e recebe pacotes, não tendo a necessidade de reuni-los ou entender o que significam. Já determinados aplicativos devem ter certeza que o conjunto de informações transferido está completo. Cada uma destas tarefas é realizada por diferentes protocolos formando “camadas” de informação adicional. De maneira bem simples, pode- se dizer que existem quatro destas camadas: física, internet, transporte e aplicação. A camada física é responsável pelo envio dos bytes e possível correção de erros de transmissão.

A camada internet organiza os dados em pacotes, sendo que o IP (Internet Protocol) é o mais usado. Na camada de transporte Ocorre a validação de chegada e ordenação dos pacotes. Os protocolos de transporte mais comuns são TCP (Transmission Control Protocol) e UDP (User Datagram Protocol). Já a camada de aplicação interpreta os dados de acordo com o seu uso. HTTP, FTP, SMTP e POP estão entre os mais utilizados.

Portas Para envio de pacotes não basta apenas saber a identificação única da máquina (endereço IP), pois um mesmo computador pode oferecer diversos serviços. Cada um destes serviços é etiquetado com um número indicando uma porta. Esta porta é apenas lógica e não física como as portas seriais e paralelas do computador. Os serviços usados largamente têm suas portas pré-definidas, como 80 para HTTP,20 e 21 para FTP e 25 para SMTP.

URL Significa Uniform Resource Locator e é uma referência singular para algum recurso na Internet. Uma URL é composta das seguintes partes: 1) O protocolo de comunicação, como HTTP por exemplo; 2) Nome do servidor (domínio);

3) Porta do serviço desejado no servidor (opcional);

4) Caminho; 5) Nome do arquivo

6) Referência (posição ou marca) dentro do arquivo (opcional).

MIME É um padrão de codificação usado na transferência de informações como: arquivos binários, arquivos texto e arquivos texto com caracteres não convencionais. É bastante utilizado para a identificação do tipo do arquivo que está sendo transferido. Para tanto, possui duas classificações: tipo e subtipo. O tipo identifica de maneira genérico o arquivo como texto, figura (imagem), filme (vídeo), aplicação e outros. O subtipo indica de maneira específica o formato do arquivo: txt, html, gif, bmp, jpeg, zip, etc. Identificando Máquinas Para identificação única de computadores na Internet existe o IP, endereço composto de quatro números (de 0 a 255) separados por pontos e, eventualmente, um nome de domínio associado a este endereço. O endereço é representado internamente por um número de 32 bits.

O pacote java.net oferece uma classe chamada InetAddress para

identificação de computadores em uma rede. Para que seja possível a conexão com outras máquinas que podem estar localizadas em qualquer parte do mundo é necessário uma identificação única para cada uma. Esta identificação singular é alcançada através do IP e existe em duas formas: a) nome de domínio - DNS (domain name system) ou

b) através dos quatro números separados por um ponto, por exemplo, 200.250.71.28, chamado endereço IP.

Em ambos os casos o endereço é representado internamente por um número de 32 bits, sendo que cada um dos quatro números pode estar na faixa de 0 a 255. A classe InetAddress (de java.net) fornece funções para a recuperação destes endereços de rede. A Classe InetAddress está definida em java.net Métodos Principais: -getLocalHost() -getByName() -getAllByName() -getHostName() getHostAddress() -getAddress() -isMulticastAddress() String getHostAddress() Após o objeto ter sido criado por um dos dois primeiros métodos, o endereço em formato string pode ser recuperado com esta função. byte[]getAddress() Retorna o endereço em formato numérico. O retorno atual é um vetor de 4 bytes. Entretanto não é aconselhável usar este tamanho como referência, já que já está em vigor o IPv6 com 16 bytes. Para manter o programa funcionando mesmo após esta atualização deve-se usar o tamanho do vetor retornado com o atributo length. Nas situações em que um programa deve ser testado sem que exista uma rede disponível pode ser usado o endereço ou nome de domínio local (loopback address) localhost ou 127.0.0.1 Os principais métodos da classe InetAddress são: InetAddress getLocalHost() Cria um objeto InetAddress a partir do localhost da máquina. InetAddress getByName(String pNome)

Cria um objeto InetAddress a partir de um nome de domínio ou de um endereço numérico. InetAddress[] getAllByName(String Nome) Cria um objeto InetAddress para cada endereço numérico correspondente ao domínio passado como parâmetro. String getHostName() Após o objeto ter sido criado por um dos métodos anteriores, o nome de domínio pode ser recuperado com esta função. Exemplos /* Função : Utilização da classe InetAddress*/ import java.net.*; import utilnet.*; class Network0201 public static void main(String[] args) try

InetAddress tEnd; byte [] int tIP; tRes; tHost; String

tEnd =InetAddress. getLocalHost () ; tIP = tEnd.getAddress(); System.out.println("Host Name :"+tEnd.getHostName()); System.out.print1n ("Host Address: " + tEnd.getHostAddress ()); Conectando-se com URL A conexão com uma determinada URL pode ser feita através das classes URL, URLConnection, URLEncoder e URLDecoder. A classe URL também possui métodos para recuperar e alterar cada uma das partes de uma URL. As classes URLEncoder e URLDecoder são usadas na codificação de URLs. A classe URLConnection representa uma conexão ativa. A conexão com uma determinada URL pode ser feita através das classes URL, URLConnection, URLEncoder e URLDecoder.

A classe URL também possui métodos para recuperar e alterar cada uma das partes de uma URL e para conectar-se com determinada localização para recuperação e envio de informações. As classes URLEncoder e URLDecoder são usadas na codificação de URL's. A classe URLConnection representa uma conexão ativa criada pelos métodos da classe URL. Classe URL

Em Java, a classe URL, do pacote java.net possui as funções para uso de URL's: acesso ao conteúdo, recuperação de cada uma das partes componentes, etc. Os principais métodos são: int getPort() Retorna o número da porta da URL; String getProtocol() Retorna o protocolo da URL; String getHost() Retorna o nome do host relativo à URL; String getPath() Retorna o caminho da URL. Define-se como caminho da URL o string colocado após o endereço da máquina e porta, sem a parte retornada pelo meto getQuery(). String getFile()

Retorna o nome do arquivo. Esse método indica o retorno do método getPath( ) concatenado com o retorno do método getQuery(). String getRef()

Retorna âncora da URL. A âncora também é conhecida como referência. String getQuery() Retorna a parte da URL conhecida como query string. A query stringgeralmente vem depois do nome do arquivo e é separado da URL por um “&” ou um “;”. URLConnection openConnection() Abre uma conexão com a URL, retornando um objeto do tipo URLConnection (veremos a seguir esse classe em detalhes). Object getContent() Ocasiona o download do conteúdo da URL indicada e disponibiliza-o como um objeto genérico. Este comando é uma simplificação de URL.openConnection().getContent(). InputStream openStream() Abre uma conexão com a URL e retorna uma stream de entrada. Deve ser usado no lugar de getContent( ) quando o arquivo for desconhecido ou quando houver necessidade de uma manipulação diferente do padrão. Este comando é uma simplificação de URL.openConnection().openStream(). Exemplos /** Funcão :Utilização bâsica da classe URL */ import java.io.*; import java.net.*; class Network0202 } public static void main(String[] args) ) String URL Object while (true) ( try{ tUr1Dig; tUr1; tobj; tUr1Dig = Console.readstring("Digite uma URL: "); if (tUrlDig.equals("fim"))

break;

tUr1= new URL(tUr1Dig); System.out.println("URL correta.");

System. out. println("Protocolo :" + tUr1.getProtoco1()); /* Funcão tobj=tUrl.getContent(); System.out.print1n("Classe: "+tObj.getClass ()) ; }catch(MalformedURLException e1){ System.out.println("URL inválida: " + e1.getMessage()) ; }catch(IOException e2){ System.out.println("Erro na obtenção do objeto") ;} :Utilização de URL para leitura do recurso apontado pela URL */ System.out.println("Host :"+tUr1.getHost()); System.out.println("Porta :"+ tUr1.getPort()); System. out. print1n ("Arquivo :"+tUrl.getFile()); System.out.println("Referencia:" + tUr1.getRef ()) ;

Codificação em URLs A classe URLEncoder contém o método encode() para converter uma String em um tipo MIME chamado “x-www-form-urlencoded”. Este método pode ser usado na montagem de URLs. A classe URLDecoder contém o método decode() que faz a operação inversa, ou seja, converte o tipo MIME em uma string normal.

Este tipo é definido com espaços em branco como "+" e caracteres especiais como "%nn" onde nn é o número hexadecimal que define o caractere. Classe URLEncoder Existem algumas diferenças na nomenclatura de localizações em sistemas operacionais, como alguns aceitam espaços em nomes de arquivos, outros não, alguns aceitam o # em um caminho e este símbolo tem um significado especial para uma URL (referência dentro de um arquivo). Para resolver estas diferenças foi criada uma solução bem simples: caracteres especiais devem ser representados por códigos antecedidos de um % e espaços em branco são representados por sinais de '+'. Em java a classe URLEncoder faz esta conversão diretamente através da chamada do seu único método: encode(). Classe URLDecoder Converte uma URL codificada em sua representação de String através da chamada de seu único método decode(). Exemplos

/*Funcão : Utilização das classes URLEncoder e URLDecoder*/ import java.net.*; import java.io.*; class Network0205{

public static void main(String[] args){ String strUrl, strCod; while (true) strur1=Console.readstring("Digite uma URL: "); if (strUr1.equals("fim")) break; strCod=URLEncoder.encode(strUr1); System.out.println("URL Codificada:"+strCod); strUr1=URLDecoder.decode(strCod); System.out.print1n("URL Decodificada: " + strUr1); Classe URLConnection A classe URLConnection é uma classe abstrata que representa uma conexão ativa para um recurso especificado através de uma URL. Como diferenças principais com a classe URL, temos: a) maior controle sobre a conexão;

b) possibilidade de se especificar um protocolo diferente para a sua aplicação. Caso esta não seja a sua necessidade, a criação de um objeto URLConnection é feita através do método openConnection() da classe URL.

Classe URLConnection(cont.) Os principais métodos da classe URLConnection são: § String getContentType()

§ int getContentLength()

§ String getContentEnconding() § long getDate()

§ long getExpiration()

§ long getLastModified() Os métodos acima servem para recuperação de informações padronizadas do cabeçalho MIME. O tamanho geralmente não é indicado para arquivos texto (como HTML por exemplo). String getHeaderField(String Nome) Recuperação de dados arbitrários do cabeçalho MIME. Pode receber tanto o nome de um campo MIME quanto o seu número de ordem. É usado no caso de campos especiais que não tenham um método correspondente.

InputStream getInputStream() OutputStream getOutputStream() Retornam os streams para recebimento e envio de dados, respectivamente. void setDoOutput(boolean Flag) Caso se passe true está se indicando que a conexão será usada para gravação. O valor default é false, indicando que se quer realizar leitura. Esse método deve ser chamado entes da conexão. Exemplos /* Funcão :Uso da classe URLConection para recuperar os cabeçalhos HTTP tipos MIME */ import java.io.*; import java.util.*; class Network0206

public static void main(String[] args){ String tSite; URL tURL; URLConnection tConexao; while (true) try{ tSite =Console.readstring("\nDigite um site : "); if (tSite.equals("fim")) break; tURL =new URL(tSite);

tConexao =tURL.openConnection();

System.out.println("Conteúdo + tConexao.getContentType()); System. out. println ("Condificação “+ tConexao.getContentEncoding()); new Date(tConexao.getDate ())) ; new Date(tConexao.getLastModified())); new Date(tConexao.getExpiration())); tConexao.getContentLength()); }catch (MalformedURLException e1){ e1.printStackTrace(); }catch (IOException e2){ e2. printStackTrace (); }     Thread Objetivo do Módulo

Após completar este módulo, você deverá ser capaz de: Entender o conceito de multithreading Utilizar a classe Thread e a interface Runnable Executar threads Interromper threads Priorizar threads Sincronizar threads Conceito de Multithreading

Multitasking é a característica que permite executar diversos programas (processos) simultaneamente, por exemplo, fazer download de arquivos enquanto edita um documento em um editor de textos. Se a plataforma tiver apenas um processador, o que ocorre é que para cada aplicação é dado um determinado tempo para execução. Nestes casos a multitarefa pode ser de dois tipos, preemptiva quando o S.O. interrompe os programas sem consultá-los e cooperativa, quando o S.O. só interrompe um programa quando este deixar. Tais mudanças da tarefa executada são conhecidas como trocas de contexto. Multithreading permite que um mesmo programa tenha várias linhas de execução (threads), por exemplo, o browser faz download de um arquivo enquanto abre um novo site. A diferença entre as multitasking e a multithreading reside na memória utilizada. Processos possuem seus próprios conjuntos de variáveis, enquanto threads compartilham a mesma área de memória.

A própria troca de informações entre threads é muito mais fácil e rápido do que entre processos. Iniciar novos processos causa um grande overhead e o

compartilhamento de dados entre eles é mais lento do que em threads e com muito mais restrições. Threads são linhas de execução de um mesmo programa. São usadas quando um programa precisa executar diversas tarefas simultâneas, ou seja, na programação multithreading cada tarefa terá chance de ser executada.

O programador é responsável pela criação de novas threads e controlar seu comportamento durante a execução do programa. A linguagem Java foi concebida com duas maneiras de se trabalhar com múltiplas linhas de execução: a classe Thread e a interface Runnable. São usadas quando um programa precisa executar diversas tarefas simultâneas. O tempo de processamento é dividido entre as diversas threads e assim cada uma terá sua chance de ser executada. Antigamente o programador era responsável pela criação de novas threads e pelo controle do seu comportamento durante a execução do programa. Entretanto, é o S.O. quem executa e interrompe as threads. A linguagem Java possui duas maneiras de se trabalhar com múltiplas linhas de execucão:classe Thread e interface Runnable. Usar uma ou outra vai depender da maneira com a qual a aplicação está organizada, mas não existe diferença de velocidade ou funcional entre as duas. Estados de uma Thread New:ocorre quando a thread é criada com o operador new, mas ainda não está pronta para ser executada, Runnable: já foi dado o comando para iniciar a thread mas talvez ela ainda não esteja sendo executada. É tarefa do sistema operacional executar a thread. Blocked: a thread está bloqueada, ou seja, não está sendo executada. Ela pode estar bloqueada porque o S.O. a interrompeu ou porque ela mesma fez uma pausa Dead: a thread terminou, naturalmente com a finalização de seu método principal ou devido a algum tipo de erro. Classe Thread A classe Thread é utilizada para a implementação de múltiplas threads em um programa. Cada programa tem, no mínimo, uma thread criada pelo JVM quando é iniciado. Os principais métodos são:

- Thread(...): Construtores permitem a criação de threads com diversas características como: threads com nomes, threads que pertencem a um grupo, threads de objetos específicos. - run() : Método que é chamado quando se inicia uma thread. - start( ) : Método que transforma uma thread do estado de new para o estado de runnable. - sleep() : deixa a thread atual em estado dormindo. Deve-se indicar um tempo em milisegundos como parâmetro. - yield( ) : Pausa temporariamente a thread, liberando a CPU para que as outras threads possam rodar. - setPriority( )/getPriority( ) : Assinala uma prioridade ou retorna a prioridade atual da thread. -interrupt(): Interrompe a execução de uma thread se possível.

- isAlive()/interrupted()/isInterrupted():Testa se a thread está viva ou foi interrompida. -wait():Coloca a thread em um estado de espera. A thread irá ficar parada até que outro método chame o método notify() ou notifyAll().

- notify( )/notifyAll( ) : Acorda todas as threads que estão esperando o recurso. Não existe garantia de que thread irá ser ativada após a chamada desses métodos. Definindo uma Thread A definição de uma thread é feita criando-se uma subclasse de Tread e implementando o seu método run(). Este é o método principal da thread que será chamado pelo S.O. quando a thread for iniciada. Em geral, ele possui um loop para manter a execução enquanto a thread estiver ativa. Quando este método termina, a thread também é finalizada.

Executando uma Thread

Para se executar uma thread deve-se criar um objeto da classe desejada que seja filha de Thread e em seguida chamar o método start dessa classe. O método start solicita a execução da thread, mas quem a executa efetivamente é Máquina Virtual Java (JVM). O resultado desta execução é que duas threads passam a rodar concorrentemente, a thread atual (que retoma o controle após a execução do método start e a outra que passa a ter o seu método run( ) executado. O método run não deve ser executado diretamente pois a chamada não cria uma nova thread apenas invoca o método e retorna após seu término. Se a thread que chamou o método start() terminar, somente essa thread irá terminar e não as outras threads iniciadas por ela. Para que essa thread tenha o controle e determine que o seu fim implique no fim de outras threads, deve-se antes de iniciar as outras threads, chamar o método setDeamon() passando true como parâmetro. Ao fazer isso, está se indicando a dependência das thread iniciadas à thread atual. Caso a thread atual termine, as outras irão terminar também. Pausa em uma Thread Em sistemas operacionais com multitarefa cooperativa podem ocorrer situações onde uma determinada thread ocupe todo o processamento,

deixando o sistema lento. Para evitar este problema basta fazer uma pequena pausa na thread, assim outras threads também poderão ser executadas. No caso de sistemas preemptivos a pausa é interessante para controlar o tempo mínimo que uma thread deve esperar para sair do estado de bloqueio. Para sistemas cooperativos uma outra solução é o uso do método yield() que devolve o controle ao S.O.

Interrompendo uma Thread Quando uma thread está bloqueada ela não tem condições de verificar se deve continuar aguardando o desbloqueio. Para interromper o bloqueio de uma thread nestas condições deve-se utilizar o método interrupt(). Este método dispara a exceção InterruptedException. Cabe ao programador a tarefa de capturar a exceção e continuar ou não com a thread. Este método só deve ser chamado se a thread estiver bloqueada.Para verificar se uma thread está bloqueada ou não se usam os seguintes métodos: interrupted() ou isInterrupted(). Prioridades de uma Thread

Todas as threads são inicialmente criadas com a prioridade NORM_PRIORITY. Esta prioridade pode ser mudada no momento da criação da thread ou depois com uma chamada ao método setPriority(). Este método recebe como parâmetro um valor entre os intervalos MIN PRIORITY e MAX PRIORITY. A alteração da prioridade de threads deve ser feita com o máximo de cuidado pois esta característica é dependente do sistema operacional no qual o programa estiver rodando. Java define 10 niveis de prioridade de 1(MIN_PRIORITY) a 10(MAX PRIORITY). Alguns S.O. possuem menos níveis de prioridade, como o Windows NT que possui apenas 7. Neste caso alguns níveis de prioridade diferentes da JVM serão mapeados para o mesmo nível no S.O. e os resultados podem não ser os esperados. Alterar os níveis de prioridade de threads pode causar problemas inusitados, como por exemplo, corromper dados de aplicações que não são thread-safe. Sincronizando Threads Uma thread pode ser interrompida a qualquer momento por outra thread de maior prioridade ou pelo próprio S.O. quando o seu tempo de execução tiver se esgotado. Para evitar que uma thread seja interrompida em um ponto crítico, onde dados podem ficar em estados incompletos, pode-se utilizar o modificador synchronized no método em questão. Desta forma protegem-se operações que necessariamente tem que ser executadas como um conjunto e evita-se dano aos dados, principalmente. Existe também a forma synchronized(Object) que permite a sincronização de um determinado trecho de código ao invés da função inteira. Existem outras situações na qual uma ou mais threads tem que esperar pelo término de determinadas tarefas em outras threads para poderem continuar executando. Nestes casos utiliza-se o método wait( ) que coloca a thread no estado bloqueado. Outra thread deverá em algum momento chamar o método notify( ) ou notifyAll() para acordar as threads que estejam em estado bloqueado pelo método wait().

O método notify() retira um thread arbitrário do estado bloqueado e o notifyAll() retira todos os threads do estado bloqueado. É responsabilidade do programador colocar código de controle indicando qual thread deve ser iniciada e qual deve voltar a ser bloqueada com wait(). Interface Runnable Caso a classe de thread já seja uma subclasse deve-se usar a interface Runnable para transformá-la em uma thread. Como padrão também, deve-se escrever o método run( ) que será ativado quando a thread for iniciada. Com relação ao que já foi visto até aqui, as únicas mudanças são no momento da criação do objeto de Thread e no uso de comandos da classe Thread (como sleep(), por exemplo). Todos os comandos estudados continuam valendo. Não existe diferença de tratamento pela JVM de uma classe que seja filha de Thread e outra que implemente a interface Runnable. Executando um Runnable Para se executar uma thread de uma classe que implemente a interface Runnable, inicialmente deve-se instanciar a classe que implementa runnable, criar uma instância da classe Thread passando como parâmetro no construtor a instância criada da classe e depois chamar o método start( ) da instância da classe Thread.

Observe que, a partir do momento em que a instância da Thread foi criada com o objeto obj, sempre se deve usar o objeto thread para executar as tarefas comuns de threads e não mais a classe pois a mesma por não ser filha de Threadnão possui mais os métodos adequados de controle de threads.

“Java fornece inúmeras bibliotecas para acesso a dados em computadores remotos. No caso do JDBC, JDBC pode ser curso, caso tenha procura Java Database Connectivity que é um conjunto de classes e interfaces escritas em Java que fazem o envio de instruç ões SQL para banco de dados relacional  

Sockets Objetivos

Após completar você deverá ser capaz de:

Entender o conceito de socket

Conectar-se a computadores remotos utilizando sockets Criar programas Cliente-Servidor

✓ Entender o conceito de Datagrama

Conhecer as classes de Datagramas disponíveis em Java O que são Sockets Sockets são abstrações de software utilizadas para representar as duas “pontas” de uma conexão. Para uma determinada conexão existe um socket em cada uma das máquinas. Entre estes dois sockets trafegam os dados da conexão. Esta abstração permite que se programe a comunicação entre computadores sem o conhecimento do hardware e cabeamento de uma rede Um socket é uma ponta lógica de uma conexão entre programas rodando em uma rede. Portanto, para cada conexão existem dois sockets, um em cada ponta da comunicação. Esta abstração faz com que o conhecimento da parte física que compreende os conectores e os cabos não seja necessário no programa, pois a transferência de dados é tratada da mesma maneira que o acesso a arquivos comuns(streams).

Funcionamento de Sockets

Em Java, um socket é criado apenas para iniciar a conexão entre computadores. Em uma das máquinas, um programa “servidor” (classe ServerSocket ou outro tipo de servidor que aceite conexões TCP) é executado e fica aguardando uma conexão em uma determinada porta (método accept()). Outra máquina age como “cliente” (classe Socket) e faz uma requisição usando o endereço e a porta de um possível “servidor”. Caso a conexão ocorra com sucesso, o método accept( ) do programa “servidor” retorna um Socket e a transferência de dados pode ser iniciada. Repare que a partir deste instante não existe mais uma máquina “cliente” e outra “servidor”, pois ambas podem enviar e receber dados. A distinção é feita apenas para identificar quem está ouvindo e quem solicita a comunicação. É a aplicação que irá determinar quem será o “servidor” e quem será o “cliente”. Após o estabelecimento da conexão deve-se abrir um canal para a transferência dos dados, através do uso de streams. Cada ponto conectado possui duas streams: InputStream, para recebimento e OutputStream, para o envio de dados. Com este formato fica muito fácil enviar e receber dados, bastando para isso o uso das funções de streams como read() e write().

Deve-se destacar que tanto a conexão quanto a transferência são realizadas sem nenhum conhecimento sobre qual placa de rede está sendo usada, bem como plataforma, funcionamento do protocolo etc. A seguir são apresentadas as classes e funções necessárias para realizar o processo aqui descrito. A Classe Socket

Classe que representa um socket em Java. Possui diversos construtores para facilitar o uso, mas basicamente os parâmetros solicitados são: identificação do “servidor" e porta do serviço requisitado. O próprio construtor é o responsável pela solicitação da conexão e continua tentando até que ocorra: sucesso, erro de E/S ou endereço não encontrado. void setSoTimeout() Indica o tempo máximo (em milissegundos) que uma operação de leitura deve aguardar. Se for definido um timeout de 0 (zero) o tempo máximo é infinito. InetAddress getLocalAddress() int getLocalPort() Retornam o endereço e a porta do computador no qual o socket está sendo executado. InetAddress getInetAddress() int getPort()

Retornam o endereço e a porta do computador no qual o socket está conectado. InputStream getInputStream() OutputStream getOutputStream() Retornam os streams padrões para entrada e saída de dados. Através de filtros apropriados podem ser convertidos para outros tipos de streams. void close() Fecha a conexão. Exemplos /*Funcão : Criação de um Socket com verificação de conexão e exibição de informações sobre a conexão realizada */ import java.net.*; import java.io.*; class Network0301{

public static void main(String[] args){ String int Socket while(true) tHost = Console.readString("Digite um host : "); if (tHost.equals("fim")) break;

tPorta =Console.readInt("Numero da porta : ") ; try{

tSocket = new Socket (tHost, tPorta); tHosti tPorta; tSocket; System.out.println("Conexão estabelecida. ") ; System.out.println("Porta Local: n + tSocket.getLocalPort()); System.out.println("Porta Remota: " + tSocket.getPort ()) ; System.out.println("In Buffer Size: " + tSocket.getReceiveBufferSize () ) ; tSocket.getSendBufferSize()); tSocket.getSoTimeout ()) ; tSocket.getKeepAlive () ) ; tSocket.close(); }catch(UnknownHostException e){ System.out.println("IP não encontrado. ") ; e.printStackTrace(); }catch(IOException e){ System.out.println("Erro na conexão. ") ; e.printStackTrace(); }

Lendo Dados com Sockets

Para abrir uma conexão, deve-se criar um objeto da classe Socket, passando como parâmetro uma localização e uma porta. O construtor do socket tenta conectar-se até que ocorra sucesso, erro de I/O ou endereço não encontrado. Após a conexão efetivada abre-se uma stream de entrada para recuperação dos dados com getlnputStream(). O protocolo de comunicação utilizado pela classe Socket é o TCP. Para abrir uma conexão, deve-se criar um objeto da classe Socket, passando como parâmetro uma localização e uma porta. O construtor do socket tenta conectar-se com a URL indicada até que ocorra sucesso, erro de E/S ou endereço não seja encontrado. Após a conexão efetivada abre-se uma stream de entrada para recuperação dos dados com o método getInputStream( ). Geralmente se transforma esse stream em uma outra classe de E/S que tenha o nível desejado de funcionalidade para o usuário. Para isso usam-se as classes de E/S do Java considerando que o stream de entrada retornado pelo método getInputStream() é uma instância da família da classe InputStream. O protocolo de comunicação utilizado pela classe Socket é o TCP.

Definindo um Timeout de E/S Uma operação de E/S ou mesmo a abertura da conexão podem durar indefinidamente. Para evitar este problema pode ser definido um tempo máximo (timeout) para a conclusão da tarefa. O método setSoTimeOut é utilizado para a definição do tempo máximo de todas as operações subsequentes. Timeout de Conexão A partir da versão 1.4 do Java, é possível se indicar um tempo de timeout na conexão do programa com um servidor. Para que se tenha essa característica deve-se mudar a maneira de se criar e manipular o Socket. Primeiramente deve-se criar uma instância de InetSocketAddress, onde se passa o nome do host (ou endereço IP) e a porta de conexão desejada. Essa

classe é filha da classe abstrata SocketAddress que representa uma conexão socket sem a parte do protocolo.

Depois se deve criar uma instância de Socket sem os parâmetros de host e porta. Usando essa instância criada, chama-se o método connect() que recebe a instância InetSocketAddress e como segundo parâmetro um valor indicando o tempo de timeout em milisegundos que será usado para a conexão.

Classe ServerSocket

Classe que representa o socket servidor. Todos os seus construtores têm como parâmetro comum a porta de comunicação a ser usada. Caso seja usado o valor 0 (zero) uma porta livre é escolhida pelo programa. void setSoTimeout() Indica o tempo máximo (em milissegundos) que o servidor deve aguardar uma conexão (accept). Se for definido um timeout de 0 (zero) o tempo máximo é infinito. int getSoTimeout() Retorna o timeout atual. Socket accept() Bloqueia o programa e aguarda uma conexão. Finaliza apenas quando uma conexão é feita ou quando for atingido o timeout. Quando a conexão é estabelecida o método irá retornar uma instância da classe Socket que deve ser usada para a comunicação com o cliente que se conectou. InetAddress getlnetAddress() int getLocalPort() Retornam o endereço e a porta do computador no qual o ServerSocket está sendo executado. void close() Fecha a“escuta”.

Servidor de Socket Um servidor de socket deve ficar em espera, aguardando uma conexão em uma porta específica.

A classe para criação de servidores chama-se ServerSocket. Após a conexão, um objeto Socket é retornado com uma nova porta para que a original fique livre e possa aguardar por novas requisições Ambos os lados da conexão podem enviar e receber dados. Quase sempre um servidor que irá receber conexões via socket possui uma estrutura padrão. A comunicação pode ocorrer de modo serial ou de modo múltiplo. No modo serial somente é permitido que um cliente se comunique com o servidor de cada vez. O modo múltiplo irá permitir que vários clientes realizem a comunicação com o servidor ao mesmo tempo. A estrutura de um tipo de servidor difere da outra. O servidor de socket serial deve ficar em estado de espera, aguardando uma requisição em uma porta específica, realizada através do método accept( ). Após a conexão, um objeto Socket é retornado com uma nova porta para que a original fique livre e possa aguardar por novas requisições. Ambos os lados da conexão podem enviar e receber dados, ou seja, após o estabelecimento da conexão, não existe mais a figura de cliente e servidor. Essa comunicação deve sempre seguir um protocolo. Após a comunicação ter sido terminada, pode-se voltar a chamar o método accept() que irá deixar o servidor em estado de espera novamente. Com essa estrutura de implementação a comunicação cliente/servidor será realizada de modo serial, sendo que somente um cliente terá comunicação com o servidor

Múltiplos Clientes Em geral, um servidor é capaz de atender múltiplas requisições e criar múltiplos sockets, um para cada cliente. Esta tarefa é facilmente realizada com a utilização de múltiplas threads. Para tanto, basta pegar o Socket resultante do método accept() e usá-lo para criar uma nova thread. Em seguida deve-se chamar o método accept() novamente e aguardar novas requisições.

Data gramass A classe Socket utiliza o protocolo TCP para realizar sua comunicação. Este protocolo garante a entrega dos pacotes e por isso tem um alto overhead devido ao controle de sequência e entrega dos pacotes. Existe outro protocolo que não garante nem a entrega e nem a ordem dos pacotes. Ele é chamado de User Datagram Protocol (UDP). Quando usado em aplicações que não necessitam de precisão na entrega dos pacotes, o protocolo UDP garante maior rapidez de transmissão. Também não existe a figura do servidor múltiplo, sendo que sempre se deve pensar em comunicação UDP sendo feito entre duas máquinas somente, pois o protocolo UDP é um protocolo sem conexão. Geralmente esse protocolo é usado em servidores de informações.

Como periodicamente o cliente vem buscar informações para consulta no servidor, caso se perca alguma informação no caminho não trará problemas pois logo em seguida o cliente irá pedir novamente a informação.

Classes para Datagramas A classe DatagramSocket é usada para representar um socket que se comunica com o protocolo UDP.

A classe DatagramPacket representa um pacote que é enviado ou recebido pelo DatagramSocket. DatagramPackets podem ser enviados para múltiplos recipientes que estejam escutando um MultiCastSocket. A classe DatagramSocket é usada para representar um socket que se comunica com o protocolo UDP para enviar e receber pacotes datagram. O socket tipo datagram é um ponto de envio e recebimento de serviços de pacotes. Cada pacote enviado ou recebido através do socket tipo datagram deve ser individualmente endereçado e direcionado. A informação da origem e destino acompanha o pacote que trafega na rede. A classe DatagramPacket representa um pacote que é enviado ou recebido pelo DatagramSocket. É essa classe que implementa o serviço de envio de pacotes sem conexão. Nessa instância é que é colocada a rota de envio do pacote, informando o destino do pacote.

Comunicação Multicast A comunicação multicast representa a comunicação de um servidor com vários clientes, formando grupos de máquinas. A comunicação é feita em UDP e, portanto, deve- se usar a classe DatagramPacket para o envio e recebimentos de informações. Para se criar um grupo de comunicação usa-se a classe MulticastSocket. Um grupo de multicast é caracterizado por um endereço IP de classe D, de 224.0.0.0 a 239.255.255.255. A comunicação multicast caracteriza-se pelo envio de um pacote não somente para um cliente e sim para todos os clientes que fazem parte de um grupo de multicast. O protocolo de rede tentará enviar o pacote para todos os clientes participantes do grupo. Um grupo multicast é caracterizado por um endereço IP classe D entre 224.0.0.0 a 239.255.255.255) sendo que o endereço 224.0.0.0 é reservado e não pode ser usado. Clientes que querem participar de um grupo, indicam isso através da criação de uma instância de MulticastSocket indicando ou não uma porta. Após criada a instância, o programa que quer receber uma comunicação

multicast deve se juntar ao grupo através do método joinGroup( ). Caso se queira deixar o grupo deve-se chamar o método leaveGroup(). É nesses dois métodos que se indica o endereço IP do grupo multicast que está se agrupando ou deixando. O envio e recebimento de informações via grupo multicast é sempre feito através da classe DatagramPacket. Para se enviar um pacote para o grupo usa- se o método send() e para receber usa-se o método receive().