目标:模拟网络文件下载,准备一个服务器,一个客户端,服务器能够提供多用户同时文件下载,且若出现异常(断点,停止)下次能够接着之前的位置继续下载,服务端可显示进度
思路:服务器根据指定端口提供服务,当然作为服务器需要24小时不停运行,死循环是必须的,一旦有客户端连接进来,拿到对应的连接,然后对这个连接进行文件传输任务(多线程实现),首先将文件名和文件的大小发送给客户端,客户端收到后根据得到的文件名创建一个新文件。在文件开始传输之间需要先读取之前断开的位置,服务器这边的断点位置以客户端的为准,所以需要客户端发送断点位置,第一次传送是没有位置的,所以发送过来的是位置0,即从0开始,然后开始文件的传送。这个实现创建了3个类,Server(服务器),Server2(用来提供服务的),Client(客户端),具体代码如下:
public class Server{
File file;
public Server(String path){
this.file = new File(path);
}
public void send(){
try {
ServerSocket ss = new ServerSocket(8888);
// 服务器死循环不停执行
while(true){
// 等待连接
Socket s = ss.accept();
System.out.println("客户端"+s.getInetAddress().getHostAddress()+"已连接");
// 对每一个连接上的用户多线程执行文件传输操作,
new Server2(file,s).start();
}
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
Server ser = new Server("D:/test/1.mp4");
ser.send();
}
}
public class Server2 extends Thread {
private File f;
private Socket s;
// 创建构造器用以接收Server中传过来的值
public Server2(File f, Socket s) {
super();
this.f = f;
this.s = s;
}
@Override
public void run() {
RandomAccessFile raf = null;
BufferedOutputStream bos = null;
PrintWriter pw = null;
try {
raf = new RandomAccessFile(f,"rw");
// 获取基于Socket的输出流,发送文件名给客户端
pw = new PrintWriter(s.getOutputStream());
pw.println(f.getName()+"-"+f.length());
pw.flush();
// 获取从客户端发送过来的上一次断点位置
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
long pos = Long.parseLong(br.readLine());
// 将文件指针定位到上一次断点的位置
raf.seek(pos);
// 获取基于Socket的缓冲输出流用以向客户端发送文件
bos = new BufferedOutputStream(s.getOutputStream());
int len = 0;
byte[] b = new byte[1024];
while ((len = raf.read(b)) != -1) {
bos.write(b, 0, len);
}
System.out.println("拷贝完成");
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} finally {
// 资源回收
try {
if (bos != null) {
bos.close();
}
if (raf != null) {
raf.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
public class Client {
File target;
static File newFile;
File positionFile;
public Client(String dir){
target = new File(dir);
}
// 根据服务端发过来的 文件名生成新文件并且得到文件大小
public long createNewFile(Socket s) throws IOException, ClassNotFoundException{
BufferedReader br = new BufferedReader(new InputStreamReader(s.getInputStream()));
// 因为服务端发送过来的字符串有指定格式,将其分割为文件名和文件大小,文件大小用来记录进度
String[] str = br.readLine().split("-");
newFile = new File(target,str[0]);
return Long.parseLong(str[1]);
}
public void download() throws UnknownHostException, IOException, ClassNotFoundException{
// 连接上服务器,指定ip和端口
Socket s = new Socket("192.168.4.126",8888);
// 创建新文件
Long size = createNewFile(s);
// 读取文件断点位置并发送给服务端
PrintWriter pw = new PrintWriter(s.getOutputStream());
positionFile = new File(target,s.getInetAddress().getHostAddress()+".txt");
long pos = readPosition();
pw.println(pos);;
pw.flush();
// 获得基于Socket的输入流和文件输出流,完成文件传输
BufferedInputStream bis = new BufferedInputStream(s.getInputStream());
RandomAccessFile raf = new RandomAccessFile(newFile,"rw");
// 同样需要定位上一次出现异常断点的位置
raf.seek(pos);
int len = 0;
long current = pos;
byte[] b = new byte[1024];
while((len = bis.read(b)) != -1){
raf.write(b, 0, len);
current += len;
// 显示进度(这里进度显示太过于频繁,可将其优化)
showPro(current,size);
// 每向文件写入一次,就将其位置写入记录文件中
writePos(current);
}
System.out.println("下载完成");
// 下载完成后,将记录文件删除
positionFile.delete();
raf.close();
bis.close();
pw.close();
}
// 用来显示进度(格式化的)
private void showPro(long current, long size){
double d = (double) current/size;
String str = new DecimalFormat("##.#%").format(d);
System.out.println("下载进度:"+ str);
}
// 写入断点位置
private void writePos(Long pos) throws IOException {
RandomAccessFile raf_pos = new RandomAccessFile(positionFile, "rw");
raf_pos.writeLong(pos);
raf_pos.close();
}
// 读取断点位置
private long readPosition() throws IOException {
long pos = 0L;
if(positionFile.exists()){
System.out.println("继续下载...");
RandomAccessFile raf_pos = new RandomAccessFile(positionFile, "r");
pos = raf_pos.readLong();
raf_pos.close();
}else{
System.out.println("开始下载...");
positionFile.createNewFile();
}
return pos;
}
public static void main(String[] args) throws UnknownHostException, ClassNotFoundException, IOException{
Client c = new Client("D:/test/test1");
c.download();
}
}