java 使用 SocketChannel 进行文件传输

java Filetransfer with SocketChannel

我在使用 SocketChannels 进行文件传输时遇到问题:客户端结束传输文件,但服务器仍在等待来自客户端的更多字节。这会导致超时,文件会保存不到一小部分。服务器仍然卡在此处:"fileChannel.transferFrom(socketChannel, 0, fileLength);"。 之前发生的一切都正常工作。

服务器:

import java.io.BufferedReader;
import java.io.ByteArrayInputStream;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

import javax.imageio.ImageIO;

public class RequestHandler implements Runnable {

private SocketChannel socketChannel;

BufferedReader stringIn;

public RequestHandler(SocketChannel socketChannel) {
    this.socketChannel = socketChannel;
    // this.serverSocketChannel = socketChannel;
    System.out.println("RequestHandler initialized");
}

public static int getLastPush(String dir) {
    return new File("./" + dir).listFiles().length + 1;
}

public void run() {

    LoadConfig config = null;
    try {
        config = new LoadConfig();
    } catch (IOException e) {
        e.printStackTrace();
    }

    String type = null;

    try {

        socketChannel.socket().setSoTimeout(10000);
        MainServer.log("Client connected from: " + socketChannel);

        // Prendere immagine
        DataInputStream dis = new DataInputStream(socketChannel.socket().getInputStream());

        // Leggo string
        stringIn = new BufferedReader(new InputStreamReader(socketChannel.socket().getInputStream()));

        // Invio al client
        DataOutputStream dos = new DataOutputStream(socketChannel.socket().getOutputStream());

        // leggo in ricezione
        MainServer.log("Attendo auth");
        String auth = stringIn.readLine();

        // check auth
        MainServer.log("Auth ricevuto: " + auth);

        String pass = config.getPass();
        if (pass.equals(auth)) {
            dos.writeBytes("OK\n");
            System.out.println("Client Authenticated");

            type = stringIn.readLine();
            System.out.println("fileType: " + type);

            dos.writeBytes(type + "\n");

            Integer i = getLastPush(config.getFolder());

            String fileName = i.toString();
            System.out.println("fileName: " + fileName);

            switch (type) {

            case "img":

                // transfer image
                int len = dis.readInt();
                System.out.println("Transfer started.");
                byte[] data = new byte[len];
                dis.readFully(data);
                System.out.println("Transfer ended.");

                File toWrite = new File(config.getFolder() + "/" + fileName + ".png");

                ImageIO.write(ImageIO.read(new ByteArrayInputStream(data)), "png", toWrite);

                dos.writeBytes("http://" + config.getDomain() + "/" + toWrite.getName());

                break;
            case "file":

                // transfer file
                System.out.println("Transfer started.");

                readFileFromSocket(config.getFolder() + "/" + fileName + ".zip");
                System.out.println("Transfer ended.");

                System.out.println("Sending link...");
                dos.writeBytes("http://" + config.getDomain() + "/" + fileName + ".zip");

                break;
            default:

            }

            i++;

            System.out.println("Chiudo");
            dos.close();
            dis.close();
            stringIn.close();
        } else {
            dos.writeBytes("Invalid Id or Password");
            System.out.println("Invalid Id or Password");
            dos.close();
            dis.close();
            stringIn.close();
        }

        socketChannel.close();

    } catch (Exception exc) {
        exc.printStackTrace();
    }
    System.out.println("----------");
}

public void readFileFromSocket(String fileName) {
    RandomAccessFile aFile = null;
    try {
        aFile = new RandomAccessFile(fileName, "rw");
        FileChannel fileChannel = aFile.getChannel();

        long fileLength = Long.parseLong(stringIn.readLine());
        System.out.println("File length: " + fileLength);

        fileChannel.transferFrom(socketChannel, 0, fileLength);
        fileChannel.close();

        Thread.sleep(1000);
        fileChannel.close();
        System.out.println("End of file reached, closing channel");

    } catch (FileNotFoundException e) {
        e.printStackTrace();
    } catch (IOException e) {
        e.printStackTrace();
    } catch (InterruptedException e) {
        e.printStackTrace();
    }

}

}

客户:

import java.awt.AWTException;
import java.awt.GraphicsDevice;
import java.awt.GraphicsEnvironment;
import java.awt.Rectangle;
import java.awt.Robot;
import java.awt.image.BufferedImage;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.net.UnknownHostException;
import java.nio.channels.FileChannel;
import java.nio.channels.SocketChannel;

import javax.imageio.ImageIO;

public class Uploader {
    private BufferedImage img;
    private byte[] bytes;
    private SocketChannel socketChannel;
    private String link;
    private String fileName;

    DataOutputStream dos;

    // Per gli screen parziali
    public Uploader(Rectangle r, String ip, int port) throws IOException, AWTException {

        SocketChannel socketChannel = createChannel(ip, port);
        this.socketChannel = socketChannel;

        Rectangle screenRect = new Rectangle(0, 0, 0, 0);
        for (GraphicsDevice gd : GraphicsEnvironment.getLocalGraphicsEnvironment().getScreenDevices()) {
            screenRect = screenRect.union(gd.getDefaultConfiguration().getBounds());
        }

        this.img = new Robot().createScreenCapture(screenRect).getSubimage(r.x, r.y, r.width, r.height);

        ByteArrayOutputStream outputArray = new ByteArrayOutputStream();
        ImageIO.write(img, "png", outputArray);
        outputArray.flush();
        this.bytes = outputArray.toByteArray();
        outputArray.close();
    }

    // Per gli screen completi
    public Uploader(BufferedImage bi, String ip, int port) throws IOException {

        SocketChannel socketChannel = createChannel(ip, port);
        this.socketChannel = socketChannel;
        this.img = bi;

        ByteArrayOutputStream outputArray = new ByteArrayOutputStream();
        ImageIO.write(img, "png", outputArray);
        outputArray.flush();
        this.bytes = outputArray.toByteArray();
        outputArray.close();
    }

    // Per i file
    public Uploader(String fileName, String ip, int port) throws UnknownHostException, IOException {

        SocketChannel socketChannel = createChannel(ip, port);
        this.socketChannel = socketChannel;

        // this.socket = new Socket(ip, port);
        this.fileName = fileName;
    }

    public void send(String pass, String type) throws IOException {

        dos = new DataOutputStream(socketChannel.socket().getOutputStream());
        BufferedReader stringIn = new BufferedReader(new InputStreamReader(socketChannel.socket().getInputStream()));

        try {
            socketChannel.socket().setSoTimeout(10000);

            // send auth
            System.out.println("Sending auth");
            dos.writeBytes(pass + "\n");
            System.out.println("Auth sent: " + pass);
            this.link = stringIn.readLine();
            // this.link = os.println();
            System.out.println("Auth reply: " + link);
            if (this.link.equals("OK")) {

                System.out.println("Sending type: " + type);
                dos.writeBytes(type + "\n");

                // Controllo e aspetto che il server abbia ricevuto il type
                // corretto
                if (stringIn.readLine().equals(type)) {

                    System.out.println("Il server riceve un: " + type);

                    switch (type) {

                    // image transfer
                    case "img":

                        System.out.println("Uploading image...");

                        dos.writeInt(bytes.length);
                        dos.write(bytes, 0, bytes.length);
                        dos.flush();

                        break;

                    // file transfer
                    case "file":

                        sendFile(fileName);

                        break;

                    // default case, hmm
                    default:

                        break;
                    }

                    // return link
                    System.out.println("Waiting link...");
                    this.link = stringIn.readLine();
                    System.out.println("Returned link: " + link);

                    bytes = null;
                } else {
                    System.out.println("The server had a bad interpretation of the fileType");
                }

            } else {
                System.out.println("Closed");
            }

            dos.close();
            stringIn.close();
            socketChannel.close();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    public SocketChannel createChannel(String ip, int port) {

        SocketChannel socketChannel = null;
        try {
            socketChannel = SocketChannel.open();
            SocketAddress socketAddress = new InetSocketAddress(ip, port);
            socketChannel.connect(socketAddress);
            System.out.println("Connected, now sending the file...");

        } catch (IOException e) {
            e.printStackTrace();
        }
        return socketChannel;
    }

    public void sendFile(String fileName) {
        RandomAccessFile aFile = null;
        try {
            File file = new File(fileName);
            aFile = new RandomAccessFile(file, "r");
            FileChannel inChannel = aFile.getChannel();

            long bytesSent = 0, fileLength = file.length();
            System.out.println("File length: " + fileLength);
            dos.writeBytes(fileLength + "\n");

            // send the file
            while (bytesSent < fileLength) {
                bytesSent += inChannel.transferTo(bytesSent, fileLength - bytesSent, socketChannel);
            }
            inChannel.close();

            Thread.sleep(1000);
            System.out.println("End of file reached..");
            aFile.close();
            System.out.println("File closed.");

        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    public String getLink() {
        return link;
    }

}

您正在丢失 BufferedReader 中的数据。如果您查看已收到的文件,您会发现它的开头部分丢失了。

您不能在同一通道上混合使用缓冲和非缓冲输入。我建议你使用 DataInputStream 并使用 read/writeUTF() 传输文件名,并使用 read/writeLong() 传输长度。

我无法想象您为什么要使用不同的代码来传输图像和其他文件。都是字节。