Enviar arquivos via scket - Qual a forma correta?

Boa tarde,

A pergunta pode parecer algo repetitivo, mas o que eu gostaria de saber é apenas um ponto no processo de envio…

li alguns tópicos e tutoriais, como esse: http://www.rgagnon.com/javadetails/java-0542.html. Notei que, ao enviar um arquivos do cliente ao servidor, por exemplo, as pessoas colocam o tamanho do arquivo hardcoded, o que presumo ser meio que errado.

Como posso fugir disso? Quer dizer, como posso fazer informação do tamanho do arquivo possa ser enviada corretamente, de acordo com o tamanho de cada arquivo, sem gambiarras? Seria algo como o cliente “avisar” ao servidor “servidor, estou enviando um arquivo de X bytes, favor alocar o espaço necessário!”.

A ideia, antes de mais nada, é ter um servidor que aceite várias conexões. Assim, terei alguns clientes e o servidor vai tratar cada um numa Thread diferente. Estou com essa dificuldade no momento, não queria deixar nada hardcoded. Caso tenham alguma ideia, favor compartilhar. Estou travado nisso.

[quote=Garou_1]Notei que, ao enviar um arquivos do cliente ao servidor, por exemplo, as pessoas colocam o tamanho do arquivo hardcoded, o que presumo ser meio que errado.
[/quote]
Mas nesse tutorial o tamanho do arquivo não é hardcoded…
Edit, achei a linha

int filesize=6022386; // filesize temporary hardcoded

Então, como está no comentário, essa solução é temporária

Então, isso mesmo. É temporária.

O que eu não sei é como fazer da forma correta, entende? Como posso enviar essa informação para que o receptor saiba qual o tamanho do buffer que deve alocar? Penso que deve ser apenas alguma alteração pequena no código. Mas estou sem ideias para implementar.

Todo array de bytes possui um atributo que indica seu tamanho, qual é ele?

O que você quer dizer é pegar o tamanho do arquivo (o array de bytes vai seguir esse tamanho).

        File myFile = new File ("C:\\meuArquivo.wav");               // arquivo que quero enviar
        byte [] mybytearray  = new byte [(int)myFile.length()];  // array de bytes em função do tamanho do arquivo

O que não sei é como enviar essa informação ao destinatário. Eu preciso enviar essa informação e depois começar a enviar o arquivo.

Para exemplificar, no código abaixo…

Código para enviar o arquivo

import java.net.*;
import java.io.*;


public class FileServer {
  public static void main (String [] args ) throws IOException {
    // create socket
    ServerSocket servsock = new ServerSocket(13267);
    while (true) {
      System.out.println("Waiting...");

      Socket sock = servsock.accept();
      System.out.println("Accepted connection : " + sock);

      // sendfile
      File myFile = new File ("source.pdf");
      byte [] mybytearray  = new byte [(int)myFile.length()];   
      // (int)myFile.length() seria a informação à qual você se refere. Aqui ela é adquirida, mas ela não é passada ao receptor. Por conta disso acontece a
      // "gambiarra".  
      
      FileInputStream fis = new FileInputStream(myFile);
      BufferedInputStream bis = new BufferedInputStream(fis);
      bis.read(mybytearray,0,mybytearray.length);
      
      OutputStream os = sock.getOutputStream();
      System.out.println("Sending...");
      os.write(mybytearray,0,mybytearray.length);
      
      os.flush();
      sock.close();
      }
    }
}

Código para receber o arquivo

import java.net.*;
import java.io.*;

public class FileClient{
  public static void main (String [] args ) throws IOException {
    int filesize=6022386; // filesize temporary hardcoded
    // Ainda que tenha a informação (apresentada na classe logo acima), o autr não a recebe aqui, utilizando a gambiarra. 
    // Como melhorar esse código apenas para que o tamanho correto seja recebido aqui, nesse ponto? Passei por vários tutoriais e nenhum explica. 

    long start = System.currentTimeMillis();
    int bytesRead;
    int current = 0;
    // localhost for testing
    Socket sock = new Socket("127.0.0.1",13267);
    System.out.println("Connecting...");

    // receive file
    byte [] mybytearray  = new byte [filesize];
    InputStream is = sock.getInputStream();
    FileOutputStream fos = new FileOutputStream("source-copy.pdf");
    BufferedOutputStream bos = new BufferedOutputStream(fos);
    bytesRead = is.read(mybytearray,0,mybytearray.length);
    current = bytesRead;

    // thanks to A. Cádiz for the bug fix
    do {
       bytesRead =
          is.read(mybytearray, current, (mybytearray.length-current));
       if(bytesRead >= 0) current += bytesRead;
    } while(bytesRead > -1);

    bos.write(mybytearray, 0 , current);
    bos.flush();
    long end = System.currentTimeMillis();
    System.out.println(end-start);
    bos.close();
    sock.close();
  }
}

Camarada, se você não sabe, precisa estudar mais, não acha?
Debugou o código e viu onde o envio começa?

cara não vi direito o que você fez, mas sei que isso pode sim funcionar sem problemas MAS tem um detalhe, já existe a anos e anos e anos algo chamado FTP (File Transfere Protocol) traduzido (Protocolo de transferência de arquivo), em outras palavras o que você quer fazer já existe, você pode procurar uma API java para usar o FTP de uma aplicação para outra.

Por que eu digo que é melhor usar o FTP?

é um protocolo já usado há muito tempo, por isso bugs você não vai achar tão fácil

Tá victor FTP é bom mas é só um protocolo não uma API e APIs podem ter bugs.

Verdade, mas existe a api do apache (apache commons net) que da suporte ao FTP eu não tive nenhum problema com ela, aliais funcionou muito bem xD

segue a dica procure usar o FTP pode te poupar tempo

Já pensou em utilizar algum protocolo de comunicação ao invés do socket puro?
ex: file trasfer protocol (FTP)

[quote=drsmachado]Camarada, se você não sabe, precisa estudar mais, não acha?
Debugou o código e viu onde o envio começa?[/quote]

Cara, se você ler o início da minha pergunta, vai ver que passei por vários tutoriais e as pessoas sempre usam dessa forma. Eu não iria perguntar aqui sem tentar achar a resposta antes. Não sou preguiçoso e não estou querendo a resposta fácil, simplesmente. Se vim até aqui é porque estou com uma dúvida e travei nela.

Tem pessoas que procuram como salvar as informações no android quando a orientação da tela muda. é algo trivial, mas se a pessoa não sabe, é porque não tem aquele conhecimento, está com dificuldades. Com relação a estudar… Eu não estou pedindo o código do zero, estou apenas tirando uma dúvida sobre UM PONTO. Se eu não puder fazer isso num fórum, qual a serventia que ele tem?

Dessa forma, não estou aqui por conta de preguiça ou falta de estudo, pode ter certeza.

Victor Gerin, acho que a maioria das pessoas de TI (se não todas) conhecem esse protocolo (e a tradução da sigla).

heatcold, No primeiro momento teria que ser com Sockets mesmo, meu amigo. É meio que uma obrigação que seja implementado assim (infelizmente).

ok então me desculpe mas você vai ter algumas dificuldades para lidar, basicamente pelo que eu vi é assim mesmo que se faz você primeiro converte o arquivo em ByteArray e transfere o array via soket, no outro ponto você deve fazer o contrario pegar o ByteArray e converter no arquivo de volta

mas isso é só a transferência da informação você ainda precisará se preocupar com as propriedades do arquivo, além também que o ByteArray tem limite de tamanho significa que se você tentar enviar um arquivo por exemplo de 2GB você não vai conseguir, então você vai que trabalhar com buffer, que vai ter de ter nos dois lados, vou fazer uma pequena pesquisa de como fazer isso já retorno com alguma coisa

OBS : 2GB é um exemplo o tamanho máximo eu não sei

Obs² : desculpa aew se pegou mal a minha ultima fala, eu tenho uma mania de querer explicar tudo as vesses até o óbvio

Para enviar você pode fazer assim. isso é uma forma simplificada, não é 100% como eu disse antes

import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.ServerSocket;
import java.net.Socket;

/**
 *
 * @author victor
 */
public class Teste {

    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) throws IOException {
        ServerSocket serv = new ServerSocket(12345);
        Socket sok = serv.accept();
        System.out.println("Conectado : " + sok);

        FileInputStream file = new FileInputStream("./Arquivo");
        
        copyStream(file, sok.getOutputStream());
        
    }

    public static void copyStream(InputStream input, OutputStream output) throws IOException {
        byte[] buffer = new byte[1024]; // Adjust if you want
        int bytesRead;
        while ((bytesRead = input.read(buffer)) != -1) {
            output.write(buffer, 0, bytesRead);
        }
    }
}

Para receber :

[code]import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket;

/**
*

  • @author victor
    */
    public class Teste2 {

    /**

    • @param args the command line arguments
      */
      public static void main(String[] args) throws IOException {
      Socket sok = new Socket(“localhost”, 12345);
      System.out.println(“Conectado”);

      copyStream(sok.getInputStream(), new FileOutputStream("./saida"));

    }

    public static void copyStream(InputStream input, OutputStream output) throws IOException {
    byte[] buffer = new byte[1024];
    int bytesRead;
    while ((bytesRead = input.read(buffer)) != -1) {
    output.write(buffer, 0, bytesRead);
    }
    }
    }[/code]

achei um código que pode ajudar também :

a melhor parte dele é que ele usa o NIO do java, mais rápido para fazer isso xD

public static void copyFile(File sourceFile, File destFile) throws IOException { FileChannel source = null; FileChannel destination = null; try { source = new FileInputStream(sourceFile).getChannel(); destination = new FileOutputStream(destFile).getChannel(); destination.transferFrom(source, 0, source.size()); } finally { if(source != null) { source.close(); } if(destination != null) { destination.close(); } }

Agora ainda sim te recomendo usar o ftp

[quote=Garou_1]
O que não sei é como enviar essa informação ao destinatário. Eu preciso enviar essa informação e depois começar a enviar o arquivo.
(…)
Victor Gerin, acho que a maioria das pessoas de TI (se não todas) conhecem esse protocolo (e a tradução da sigla).
heatcold, No primeiro momento teria que ser com Sockets mesmo, meu amigo. É meio que uma obrigação que seja implementado assim (infelizmente).[/quote]
Basicamente o que você precisa é de um Protocolo de Comunicação! Pode fugir do ftp, mas não pode fugir de algum tipo de protocolo, mesmo que seja feito por você mesmo. O pessoal recomendou FTP por ser amplamente usado e testado, com APIs disponíveis para tratar de toda a parte pesada sem que vc precise se preocupar.

Mas se o que quer é implementar tudo manualmente (só vale essa solução se for para fins acadêmicos… em sistemas reais use algo estável ! ) basta criar um protocolo.

Um exemplo simples:

[color=red]Um envio será composto das seguintes informações:

  • um array de 4 bytes com o tamanho do arquivo (aumente se estiver previsto enviar arquivos muito grandes)
  • um array de 200 bytes com o nome do arquivo (o servidor pode precisar dessa informação para salvar com o mesmo nome)
  • o conteúdo do arquivo[/color]

Isso que está descrito acima é um protocolo! Mais fácil do que parece :slight_smile:
Agora basta o client e o server implementarem:

O cliente deve montar os arrays do cabeçalho (os 2 primeiros itens) e enviar. Depois começa a transferência do arquivo.

O servidor, ao receber um conteúdo, sabe o que esperar: lê os 4 primeiro bytes para obter o tamanho, lê os 200 seguintes para ter o nome do arquivo (guarda em variável para ler depois como quiser), e depois vai lendo os bytes restantes até completar o tamanho informado no cabeçalho - se quiser pré-aloque um buffer para ler tudo de uma vez, já que terá o tamanho. E assim ele terá o arquivo completo.

Então, camarada que não quer estudar nem debugar, nesta linha é realizado o envio

os.write(mybytearray,0,mybytearray.length);

E nesta é feita a leitura

bytesRead = is.read(mybytearray,0,mybytearray.length); 

Pronto ou quer que eu desenvolva pra ti também?

[quote=drsmachado]Então, camarada que não quer estudar nem debugar, nesta linha é realizado o envio

os.write(mybytearray,0,mybytearray.length);

E nesta é feita a leitura

bytesRead = is.read(mybytearray,0,mybytearray.length); 

Pronto ou quer que eu desenvolva pra ti também?[/quote]

Cara, na boa… Tô aqui na maior boa vontade pra tirar uma dúvida. Você estudu/debugou pro quantas horas pra saber que “write” escreve (escreve = envia) e “read” lê? Não pedi para você “desenvolver” para mim em momento algum.

Pessoal, obrigado pelas respostas. Vou usar as observações que fizeram.