通过TCP/IP(SOCKET)协议实现文件断点上传(实现多用户并发访问)。
HTTP不支持文件断点续传,所以无法使用HTTP协议。
场景:
1. 网络不稳定,导致上传失败,下次不是从头开始,而是从断点开始上传;
2. 上传大文件,无法http上传,因为web服务器考虑到安全因素,会限制文件大小,一般10+m。
此文件断点上传器使用自定义协议。
服务器为上传的文件在服务器端生成唯一的sourceid关联上传文件,当客户端上传文件时,首次的sourceid为空,服务端先判断sourceid是否为空,如果为空,生成sourceid和下载断点position=0返回给客户端,如果不为空,把之前记录的sourceid和上次记录的当前下载断点position返回给客户端,客户端指定从文件的position位置开始上传数据。当下一次传文件时,服务器由sourceid关联到文件,找到sourceid对应文件的当前下载断点position返回给客户端,客户端从指定位置position开始上传数据。
服务器端:
FileServer.java
package cn.itcast.net.server;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PushbackInputStream;
import java.io.RandomAccessFile;
import java.net.ServerSocket;
import java.net.Socket;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import cn.itcast.utils.StreamTool;
public class FileServer {
private ExecutorService executorService;//线程池
private int port;//监听端口
private boolean quit = false;//退出
private ServerSocket server;
private Map<Long, FileLog> datas = new HashMap<Long, FileLog>();//存放断点数据
public FileServer(int port){
this.port = port;
//创建线程池,池中具有(cpu个数*50)条线程
executorService = Executors.newFixedThreadPool(Runtime.getRuntime().availableProcessors() * 50);
}
/**
* 退出
*/
public void quit(){
this.quit = true;
try {
server.close();
} catch (IOException e) {
}
}
/**
* 启动服务
* @throws Exception
*/
public void start() throws Exception{
server = new ServerSocket(port);
while(!quit){
try {
Socket socket = server.accept();
//为支持多用户并发访问,采用线程池管理每一个用户的连接请求
executorService.execute(new SocketTask(socket));
} catch (Exception e) {
// e.printStackTrace();
}
}
}
private final class SocketTask implements Runnable{
private Socket socket = null;
public SocketTask(Socket socket) {
this.socket = socket;
}
public void run() {
try {
System.out.println("accepted connection "+ socket.getInetAddress()+ ":"+ socket.getPort());
//创建回退流对象,将拆解的字节数组流传入
//PushbackInputStream类继承了FilterInputStream类是iputStream类的修饰者。
//提供可以将数据插入到输入流前端的能力。能够插入的最大字节数与推回缓冲区的大小相关。
PushbackInputStream inStream = new PushbackInputStream(socket.getInputStream());
//得到客户端发来的第一行协议数据:Content-Length=143253434;filename=xxx.3gp;sourceid=
//如果用户初次上传文件,sourceid的值为空。
String head = StreamTool.readLine(inStream);
System.out.println(head);
//黑客假如注入很长的第一行数据,那么服务器肯定会由于内存溢出而崩溃
//实际项目中这里要判断第一行协议内容不能超过多长,设置一个长度上线,防止内存溢出异常。
if(head!=null){
//下面从协议数据中提取各项参数值
String[] items = head.split(";");
String filelength = items[0].substring(items[0].indexOf("=")+1);
String filename = items[1].substring(items[1].indexOf("=")+1);
String sourceid = items[2].substring(items[2].indexOf("=")+1);
long id = System.currentTimeMillis();//生产资源id,如果需要唯一性,可以采用UUID