8 votes

Envoyer une capture d'écran (bufferedImage) via une socket en Java

Je suis en train d'envoyer un BufferedImage sur un socket et j'utilise l'exemple trouvé dans ce post :

Expéditeur

   BufferedImage image = ....;
   ImageIO.write(image, "PNG", socket.getOutputStream());

Récepteur

   BufferedImage image = ImageIO.read(socket.getInputStream());

Cela fonctionne - SI, et SEULEMENT SI, je ferme le flux de sortie de l'expéditeur après cette ligne :

 ImageIO.write(image, "PNG", socket.getOutputStream());

Y a-t-il quelque chose que je peux faire à part fermer le flux de sortie ?

De plus, y a-t-il quelque chose d'autre que je peux faire pour éviter d'utiliser complètement ImageIO ? Il semble mettre une éternité pour faire quoi que ce soit. Notez également que la lecture ou l'écriture sur le disque dur doit être évitée à tout prix en raison de problèmes de performance. J'ai besoin de rendre ce transfert aussi rapide que possible (j'expérimente et essaie de créer un client similaire à VNC et enregistrer chaque capture d'écran sur le disque dur ralentirait considérablement tout). . .

@Jon Skeet

Éditer 3 :

Expéditeur : (Notez que j'envoie une image JPG et non une PNG).

                    int filesize;
                    OutputStream out = c.getClientSocket().getOutputStream();

                    ByteArrayOutputStream bScrn = new ByteArrayOutputStream();
                    ImageIO.write(screenshot, "JPG", bScrn);
                    byte[] imgByte = bScrn.toByteArray();
                    bScrn.flush();
                    bScrn.close();

                    filesize = bScrn.size();
                    out.write(new String("#FS " + filesize).getBytes()); //Envoyer la taille du fichier
                    out.write(new String("# \n").getBytes());        //Notifier le début de l'image

                    out.write(imgByte); //Ecrire le fichier
                    System.out.println("Terminé");                                 

Récepteur : (où input est le flux d'entrée du socket)

Tentative #1 :

String str = input.toString();
imageBytes = str.getBytes();

InputStream in = new ByteArrayInputStream(imageBytes);
BufferedImage image = ImageIO.read(in);
in.close();

System.out.println("largeur=" + image.getWidth());

(échec : exception Nullpointer sur la ligne de getWidth()) Je comprends cette erreur comme un "image corrompue" parce qu'elle n'a pas pu l'initialiser. Correct ?

Tentative #2 :

byte[] imageBytes = new byte[filesize];
for (int j = 0; i < filesize; i++)
{
    imageBytes[j] = (byte) input.read();
}

InputStream in = new ByteArrayInputStream(imageBytes);
BufferedImage image = ImageIO.read(in);
in.close();

System.out.println("largeur=" + image.getWidth());

(échec : exception Nullpointer sur la ligne de getWidth())

Tentative #3 :

if (filesize > 0)
{

    int writtenBytes = 0;
    int bufferSize = client.getReceiveBufferSize();

    imageBytes = new byte[filesize];     //Créer un tableau de bytes aussi grand que l'image
    byte[] buffer = new byte[bufferSize]; //Créer un tampon

    do {
        writtenBytes += input.read(buffer); //Remplir le tampon
        System.out.println(writtenBytes + "/" + filesize); //Montrer le progrès

        //Copier le tampon dans le tableau de bytes qui contiendra l'image complète
        System.arraycopy(buffer, 0, imageBytes, writtenBytes, client.getReceiveBufferSize());
        writtenBytes+=bufferSize;

    } while ((writtenBytes + bufferSize) < filesize);

    // Lire les bytes restants
    System.arraycopy(buffer, 0, imageBytes, writtenBytes-1, filesize-writtenBytes);
    writtenBytes += filesize-writtenBytes;
    System.out.println("Lecture terminée ! Lecture totale : " + writtenBytes + "/" + filesize);

}

InputStream in = new ByteArrayInputStream(imageBytes);
BufferedImage image = ImageIO.read(in);
in.close();

(échec : Le récepteur donne : exception Null pointer)

Tentative 4 :

   int readBytes = 0;
   imageBytes = new byte[filesize];     //Créer un tableau de bytes aussi grand que l'image

    while (readBytes < filesize)
    {
        readBytes += input.read(imageBytes);
    }

    InputStream in = new ByteArrayInputStream(imageBytes);
    BufferedImage image = ImageIO.read(in);
    in.close();

    System.out.println("largeur=" + image.getWidth());

(échec : l'expéditeur donne : java.net.SocketException: Connexion réinitialisée par l'hôte distant : erreur d'écriture de socket)

Tentative #5 :

En utilisant le snippet de code de Jon skeet, l'image arrive, mais seulement partiellement. Je l'ai enregistrée dans un fichier (1.jpg) pour voir ce qui se passait, et en réalité, il envoie 80 % de l'image, tandis que le reste du fichier est rempli d'espaces vides. Cela aboutit à une image partiellement corrompue. Voici le code que j'ai essayé (notez que captureImg() n'est pas en cause, enregistrer le fichier directement fonctionne) :

Expéditeur :

    Socket s = new Socket("127.0.0.1", 1290);
    OutputStream out = s.getOutputStream();

    ByteArrayOutputStream bScrn = new ByteArrayOutputStream();
    ImageIO.write(captureImg(), "JPG", bScrn);
    byte imgBytes[] = bScrn.toByteArray();
    bScrn.close();

    out.write((Integer.toString(imgBytes.length)).getBytes());
    out.write(imgBytes,0,imgBytes.length);

Récepteur :

    InputStream in = clientSocket.getInputStream();
    long startTime = System.currentTimeMillis();

    byte[] b = new byte[30];
    int len = in.read(b);

    int filesize = Integer.parseInt(new String(b).substring(0, len));

    if (filesize > 0)
    {
        byte[] imgBytes = readExactly(in, filesize);
        FileOutputStream f = new FileOutputStream("C:\\Users\\Dan\\Desktop\\Pic\\1.jpg");
        f.write(imgBytes);
        f.close();

        System.out.println("terminé");

L'expéditeur donne toujours une erreur de réinitialisation de connexion par l'hôte distant : erreur d'écriture de socket. Cliquez ici pour une image en taille réelle Problème

8voto

Jon Skeet Points 692016

Une option serait d'écrire l'image dans un ByteArrayOutputStream pour pouvoir déterminer la longueur, puis écrire cette longueur dans le flux de sortie en premier.

Ensuite, côté réception, vous pouvez lire la longueur, puis lire autant d'octets dans un tableau d'octets, puis créer un ByteArrayInputStream pour envelopper le tableau et le passer à cela à ImageIO.read().

Je ne suis pas entièrement surpris que cela ne fonctionne pas tant que le socket de sortie n'est pas fermé normalement - après tout, un fichier contenant un fichier PNG valide et ensuite quelque chose d'autre n'est pas réellement un fichier PNG valide en lui-même, n'est-ce pas?

EDIT: Voici une méthode pour lire le nombre donné d'octets dans un nouveau tableau d'octets. C'est pratique d'avoir comme une méthode "utilitaire" séparée.

public static byte[] readExactly(InputStream input, int size) throws IOException
{
    byte[] data = new byte[size];
    int index = 0;
    while (index < size)
    {
        int bytesRead = input.read(data, index, size - index);
        if (bytesRead < 0)
        {
            throw new IOException("Données insuffisantes dans le flux");
        }
        index += bytesRead;
    }
    return data;
}

2voto

SHS Points 41

Pour d'autres utilisateurs de StackOverflow comme moi.

Dans la réponse de "Jon Skeet". Modifiez la ligne suivante de la méthode readExactly.

    <>
    index += size;
    <>
    index += bytesRead;

Pour obtenir toutes les données de l'image.

0voto

Raj Points 55
public static void main(String[] args) {
    Socket socket = null;
    try {
        DataInputStream dis;
        socket = new Socket("192.168.1.48",8000);
        while (true) {
            dis = new DataInputStream(socket.getInputStream());
            int len = dis.readInt();
            byte[] buffer = new byte[len];
            dis.readFully(buffer, 0, len);
            BufferedImage im = ImageIO.read(new ByteArrayInputStream(buffer));
            jlb.setIcon(new ImageIcon(im));
            jfr.add(jlb);
            jfr.pack();
            jfr.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
            jfr.setVisible(true);
            System.gc();
        }
    } catch (Exception e) {
        e.printStackTrace();
    } 
    finally {
        try {
            socket.close();
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
}

Sur la machine 192.168.1.48:8000, un serveur python est en cours d'exécution et j'ai reçu un flux dans le code Java

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X