TCP不写完整文件?

时间:2016-05-12 17:58:31

标签: java multithreading sockets tcp

我正在尝试使用一个程序将文本文件写入多个套接字。用于编写文本文件的代码如下:

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

public class Server {
    public static void main(String args[]) throws Exception{
        String servers[] = {"127.0.0.1","127.0.0.1","127.0.0.1"};
        int[] ports = {4525, 4003,3621};
        String fileName = "example";

        for(int i = 0; i < servers.length; i++){
            getFile(fileName, InetAddress.getByName(servers[i]), ports[i], "1");
        }
    }
    public static void getFile(String fileName, InetAddress ia, int port, String vers) throws Exception{
        Socket myServers = new Socket(ia, port);
        PrintWriter pwSoc = new PrintWriter(myServers.getOutputStream(), true);
        pwSoc.println(fileName + " " + "write " + vers);
        InputStream is = null;
        int i =0;
        while(i < 20000){
            i++;
        }
        int c;
        try{
            is = new FileInputStream(fileName + ".txt");
        }
        catch(Exception e){
            System.out.println("Error. This file is not found"); 
            return;
        }
        while ((c = is.read()) != -1) {
            pwSoc.println((char)c); 
            pwSoc.flush();
        }
        pwSoc.close();
        is.close();
        pwSoc.close();
        return;
    }
}

然后,在服务器端(对于所有三个服务器),我使用以下线程,该线程计算为else if(req.equals("write")){代码块:

import java.io.*;
import java.net.*;
import java.util.StringTokenizer;
public class ServerRequestThread implements Runnable{ //Server 1
    Socket client;
    File[] files;
    VersionInfo vi;
    public ServerRequestThread(Socket s, VersionInfo vi) throws Exception{
        this.client = s;
        String filePath = new File(".").getCanonicalPath();
        this.files = new File(filePath).listFiles();
        this.vi = vi;
    }

    public void run() {
        String req = "", reversedString = "";
        try {
            while(true){
                InputStream in = client.getInputStream();
                BufferedReader bin = new BufferedReader(new InputStreamReader(in));
                String ex = bin.readLine();
                StringTokenizer st = new StringTokenizer(ex, " ");
                String fileName = st.nextToken();
                if(fileName.equals("done")){
                    return;
                }
                else if(fileName.equals("fileList")){
                    String returnList = showFiles(files).trim();
                    PrintWriter pout = new PrintWriter(client.getOutputStream(), true);
                    pout.println(returnList);
                    pout.close();
                }
                else if(fileName.equals("filecatchup")){
                    PrintWriter pout = new PrintWriter(client.getOutputStream(), true);
                    pout.println(vi.printHash());
                    pout.close();
                }
                else{
                    req = st.nextToken();
                }
                if(req.equals("read")){
                    PrintWriter pout = new PrintWriter(client.getOutputStream(), true);
                    int c;
                    InputStream is = null;
                    try{
                        is = new FileInputStream(fileName + ".txt");
                    }
                    catch(Exception e){
                        pout.println("File could not be found");
                        return;
                    }
                    while ((c = is.read()) != -1) {
                        pout.println((char)c); 
                    }
                    pout.close();
                    return;
                }
                else if(req.equals("write")){
                    if(st.hasMoreTokens()){
                        String vers = st.nextToken();
                        vi.updateHash(fileName, Integer.parseInt(vers));
                        System.out.println("The file " + fileName + " was updated  (via remote update) to v." + vers);
                    }
                    else{
                        vi.updateFile(fileName);
                        System.out.println("The file " + fileName + " was updated to v." + vi.getFile(fileName));
                    }

                    InputStream in2 = client.getInputStream();
                    BufferedReader reader = new BufferedReader(new InputStreamReader(in2));
                    reversedString = reader.readLine();


                    PrintWriter out = new PrintWriter(fileName + ".txt");
                    while(reversedString != null){
                        //cont+=reversedString;
                        out.print(reversedString);
                        reversedString = reader.readLine();
                    }
                    reader.close();
                    out.close();
                    return;
                }
            }
        } catch (Exception e) {}
    }
    public String showFiles(File[] files) throws Exception {
        String ret = "";
        //System.out.println("Path: " + new File(".").getCanonicalPath());
        for (File file : files) {
            if (file.isDirectory()) {
                //System.out.println("Directory: " + file.getName());
                showFiles(file.listFiles()); // Calls same method again.
            } else { 
                if(file.getName().toLowerCase().endsWith(".txt")){
                     ret+= " "  + file.getName();
                }
            } 
        } 
        return ret;
    } 

}

出于某种原因,当我写作时,整个文件不会传到另一边。相反,我错过了文件的前100个字节周围的某个地方。例如,如果我写了Lorem Ipsum文本,我会在第一句话的某处丢失。

有谁知道为什么会这样?我几乎尝试了所有东西,但我似乎无法解决问题

2 个答案:

答案 0 :(得分:2)

首先创建一个BufferedReader bin并使用它来读取命令的单行。但是,这并不意味着只从输入流中读取此行。相反,读取更多字节,但BufferedReader bin仅返回这些字节的第一行,其余的保留在缓冲读取器的缓冲区内(这就是名称的原因!)。

稍后您尝试阅读有效负载,而不是使用阅读器bin,而是已经创建了一个新的reader并从中读取。这样就可以忽略bin中已经缓冲的任何字节 - 这意味着你缺少字节。

答案 1 :(得分:1)

您的代码存在很多问题。质量非常差,你不应该对它无法正常工作感到惊讶。我建议你阅读一本关于Java的初学者书和一般的OOP。仅举几个问题:

  • 您不使用try/finally / try-with-resources,这意味着如果发生异常,您的流可能会永远保持打开状态。
  • 你好像distinguish between characters and bytes,这是无休止的麻烦的主要根源。在您的情况下,您读取字节,然后将它们写为字符。获取带有非ASCII字符的UTF-8文件,您将立即将它们分解(字面意思为字节)。
  • 您滥用PrintWriter这是一种非常低效的输出数据的方式,更糟糕的是,您出于某种原因使用println在一行上打印每个字节。
  • 您使用没有明确目的的魔术代码,例如while (i < 20000) ++i;

现在回答你的问题。您看到的效果是因为您没有在服务器端以一致的方式读取数据。您可以随机创建各种帮助对象来读取数据,而不是将协议封装到类中。看看这个,例如:

            InputStream in = client.getInputStream();
            BufferedReader bin = new BufferedReader(new InputStreamReader(in));
            String ex = bin.readLine();

你刚才在这做什么?您创建了一个缓冲读取器,然后(令人惊讶地)缓存了一些数据以读取一行。当然它缓冲的不仅仅是一行,这就是缓冲的工作原理。此时您应该忘记in对象并仅使用bin来阅读。这将是一致的。事实上,我将这些行写为

            BufferedReader bin = new BufferedReader(new InputStreamReader(
                                                        client.getInputStream()));
            String ex = bin.readLine();

为了安全起见。但是你做了

                InputStream in2 = client.getInputStream();
                BufferedReader reader = new BufferedReader(new InputStreamReader(in2));

也就是说,您可以绕过先前创建的缓冲读取器直接访问相同的流。当然,新的阅读器会在最后一个阅读器停止的地方找到,这可以是任何地方,具体取决于缓冲区大小,缓冲策略以及前一个阅读器读取的数据量。

解决此问题:

  • 将您的协议封装在单独的类或类别中。
  • 使用一个缓冲读卡器一致地读取数据。
  • 使用PrintWriter,除非您实际打印的内容供某人阅读管道或tail
  • 向读者明确指定编码。选择一个并坚持下去。 UTF-8是网络协议的不错选择。
  • close()个来电置于finally个区块或使用try-with-resources。