AIM :- 在 Java 中使用阻塞 IO 构建多线程应用程序以下载文件。请不要建议我使用非阻塞IO,我被告知使用这个。
问题:- 我的代码在下载服务器上托管的文件的客户端计算机上运行良好。但是,问题是我的服务器使用多个线程为文件设定种子。在所有情况下,收到的文件都是确切的长度,但是,文件似乎已损坏。就像,当我下载PDF文件时,文件页面写到最后一半(意味着所有页面都充满了原始内容的一部分)。当我下载一首歌时,它会在整个过程中爆裂,并播放到最后。
问题1:- 我应该如何保持完美的流畅下载,以便文件正确播放/打开/读取?我应该在这里解决什么技术,例如多线程引起的问题?
我的代码:-
服务器多线程代码
::::import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
public class FileServer extends UnicastRemoteObject implements FileServerInitialise{
private String file="";
public FileServer() throws RemoteException{
super();
}
public void setFile(String f){
file=f;
//System.out.println("Length in setFile = "+f);
}
@Override
public boolean login(FileClientInitialise fci) throws RemoteException {
try {
InputStream is = new BufferedInputStream(new FileInputStream(file));
long len = new File(file).length();
System.out.println("Length of File = "+len);
WorkerThread wt1=new WorkerThread(0,len/2,fci,is,file);
wt1.setName("Worker Thread 1");
WorkerThread wt2=new WorkerThread(len/2+1,2*len/2,fci,is,file);
wt2.setName("Worker Thread 2");
//WorkerThread wt3=new WorkerThread(2*len/4+1,3*len/4,fci,is,file);
//wt3.setName("Worker Thread 3");
//WorkerThread wt4=new WorkerThread(3*len/4+1,len,fci,is,file);
//wt4.setName("Worker Thread 4");
wt1.start();
wt2.start();
//wt3.start();
//wt4.start();
wt1.join();
wt2.join();
//wt3.join();
//wt4.join();
return true;
}
catch (InterruptedException iex) {
iex.getMessage();
return false;
}
客户端下载代码 ::::
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.RandomAccessFile;
public class FileClient implements FileClientInitialise {
public static int count = 1;
public static File f;
public static FileOutputStream fos;
public static RandomAccessFile raf;
public static long pointer;
public FileClient (String filename) throws RemoteException, IOException {
super();
FileClient.f= new File(filename);
FileClient.fos = new FileOutputStream(f, true);
//FileClient.raf= new RandomAccessFile(f,"rwd");
FileClient.pointer=0;
}
@Override
public boolean sendData(String filename, byte[] data, int len, String threadName) throws RemoteException{
try{
FileClient.fos.write(data,0,len);
FileClient.fos.flush();
//FileClient.raf.seek(FileClient.pointer);
//FileClient.raf.write(data,0, len);
//FileClient.pointer=raf.getFilePointer();
System.out.println("Done writing data...");
//fos.close();
return true;
}catch(Exception e){
e.getMessage();
return false;
}
}
}
问题 2 :- 另外,我应该使用RandomAccessFile
来实现相同的目的吗?会更好吗?我检查了它,它的工作非常慢(几乎慢了 10 倍)。而且,如果我要使用RandomAccessFile
,我应该为每个线程创建一个单独的对象吗?如果在这种情况下建议,我应该如何使用它?
如果代码不可能,请给我一个技术描述,代码没有必要在答案中提及。
正如其他人在评论中已经提到的那样,这是一种允许输入流由多个线程共享并允许并发写入的糟糕方法,这会导致文件损坏。
我在多线程分布式文件服务器项目中执行的一种方法是,我允许文件服务器的多线程执行,但只允许顺序线程执行。
我以这样的方式编码,以确保线程以同步的方式访问输入流,只有一个接一个的方式。这也没有损坏客户端的文件。而且,这在性能上太有效了。
请注意,在对此答案采取任何行动之前:-
我当时对代码进行了基准测试,以确保我在此答案中陈述的内容确实是访问者/搜索者的最佳选择。我相信这是最佳情况,因为我有 4 个逻辑处理器(内核/CPU),这减少了多个线程的开销(尽管它们一次工作 1 个)。
人们会争辩说这是最糟糕的方法,或者是一种丑陋的方法,等等。但我发现这在文件服务器种子设定中非常有用。我在Linux Server [Intel(R) Core(TM) 2 Duo CPU E4600 @ 2.40GHz processor, CPU(s): 2]
上的 40 MB(大约)PDF 文件在 4-5 次执行测试中平均在近 33-34 秒内复制到文件客户端。然而,当我增加线程数(8-10 个线程)时,性能下降大约需要 36-38 秒。当我拥有单线程服务器时也是如此,其中相同的文件在 45-50 秒内被复制。随着线程数量的增加,性能有所提高,并且在 4-6 个线程的范围内效率很高。
虽然,维护这么多线程显然会有开销,人们会认为单个线程可以获胜,但是,令人惊讶的是,在 4-6 个线程的情况下,结果是最佳的。
因此,我的建议是通过 4-6 个线程对输入流执行顺序访问,如代码所示。这是最佳的,相信我,我也可以与其他人争论多线程覆盖,我发现在 4-6 个线程的情况下是最佳的。
对于您的代码,我建议进行以下更改:-
InputStream is = new BufferedInputStream(new FileInputStream(file));
long len = new File(file).length();
System.out.println("Length of File = "+len);
int numOFThreads=4;
WorkerThread wt1=new WorkerThread(0,len/numOFThreads,fci,is,file);
wt1.setName("Worker Thread 1");
WorkerThread wt2=new WorkerThread(len/numOFThreads+1,2*len/numOFThreads,fci,is,file);
wt2.setName("Worker Thread 2");
WorkerThread wt3=new WorkerThread(2*len/numOFThreads+1,3*len/numOFThreads,fci,is,file);
wt3.setName("Worker Thread 3");
WorkerThread wt4=new WorkerThread(3*len/numOFThreads+1,4*len/numOFThreads,fci,is,file);
wt4.setName("Worker Thread 4");
wt1.start();
wt1.join();
wt2.start();
wt2.join();
wt3.start();
wt3.join();
wt4.start();
wt4.join();