当前很多手机应用或者是网络应用都需要支持大文件上传功能,有些用FTP来实现上传但是FTP存在许多的问题。比如FTP的安全问题还有不支持GZIP压缩等问题。采用SOCKET来实现文件上传,很轻松就可以实现断点再续和负载均衡,将上传后的文件直接保存到APACHE等WEB服务器的指定路径下,便可以轻松的拥有一台文件服务器。至于文件的同步问题这个是另一个话题了不在次讨论。本次上传的代码去掉了复杂均衡方面的功能,只是本程序的V0.1版本,真正部署到应用中的支持负载均衡并改为了多线程下载。

        简单的说明一下,每次上传时首先是发送一个自定义格式的文件头,文件头包含文件名,文件大小,文件类型,在接收到后会生成一个文件标识,改文件标识是在服务器文件夹里生成的真实文件的文件名,服务器返回数据库中的ID字段和文件当前位置给客户端,如果断点在续那么客户端会将ID与当前位置上传上来,这时通过ID来得到文件的性息,这里一定不能用文件在客户端的位置来断点定位,一定要到服务器端的数据库查询,返回位置其实比较多余,但是客户端工程师要求这样。

主程序

import java.io.IOException;
import java.io.RandomAccessFile;
import java.net.InetSocketAddress;
import java.net.ServerSocket;
import java.nio.ByteBuffer;

import java.nio.channels.FileChannel;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class NIOServer {


	/* 缓冲区大小 */
	private int BLOCK = 4096;
	/* 接受数据缓冲区 */
	private ByteBuffer sendbuffer = ByteBuffer.allocate(BLOCK);
	/* 发送数据缓冲区 */
	private ByteBuffer receivebuffer = ByteBuffer.allocate(BLOCK);

	private Selector selector;

	private RandomAccessFile raf = null;

	private Map<SocketChannel, Handler> channelMap = new HashMap<SocketChannel, Handler>();
	
	private FileDBAdapter fileDB;

	public NIOServer(int port) throws IOException {
		// 打开服务器套接字通道
		ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
		// 服务器配置为非阻塞
		serverSocketChannel.configureBlocking(false);
		// 检索与此通道关联的服务器套接字
		ServerSocket serverSocket = serverSocketChannel.socket();
		// 进行服务的绑定
		serverSocket.bind(new InetSocketAddress(port));
		// 通过open()方法找到Selector
		selector = Selector.open();
		// 注册到selector,等待连接
		serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
		
		fileDB = FileDBAdapter.getInstance();
		
		System.out.println("Server Start----8888:");
	}

	// 监听
	private void listen() throws IOException {
		while (true) {
			// 选择一组键,并且相应的通道已经打开
			selector.select();

			// 返回此选择器的已选择键集。
			Set<SelectionKey> selectionKeys = selector.selectedKeys();

			Iterator<SelectionKey> iterator = selectionKeys.iterator();
			while (iterator.hasNext()) {
				SelectionKey selectionKey = iterator.next();
				iterator.remove();
				handleKey(selectionKey);
			}
		}
	}

	// 处理请求
	private void handleKey(SelectionKey selectionKey) throws IOException {
		// 接受请求
		ServerSocketChannel server = null;
		SocketChannel client = null;

		// 测试此键的通道是否已准备好接受新的套接字连接。
		if (selectionKey.isAcceptable()) {
			// 返回为之创建此键的通道。
			server = (ServerSocketChannel) selectionKey.channel();
			// 接受到此通道套接字的连接。
			// 此方法返回的套接字通道(如果有)将处于阻塞模式。
			client = server.accept();
			// 配置为非阻塞
			client.configureBlocking(false);

			// 注册到selector,等待连接
			client.register(selector, SelectionKey.OP_READ);
			
			channelMap.put(client, new HandlerImpl());
		} else if (selectionKey.isReadable() || selectionKey.isWritable()) {
			SocketChannel socketChannel = (SocketChannel) selectionKey
					.channel();
			Handler handle = (HandlerImpl) channelMap.get(socketChannel);
			try {
				
				if(!handle.isHeader()) {
					handle.header(selectionKey);
				} else {
					handle.content(selectionKey);
				}
			} catch (Exception e) {

				e.printStackTrace();
			}
		}

	}

	class HandlerImpl implements Handler {
		
		private SocketChannel client = null;
		private FileChannel fileChannel = null;
		private boolean isHeader = false;
		private long position = 0;
		private int id = 0;
		public void content(SelectionKey selectionKey) throws Exception {
			if(selectionKey.isReadable()) {
				// 返回为之创建此键的通道。
				client = (SocketChannel) selectionKey.channel();
				// 将缓冲区清空以备下次读取
				receivebuffer.clear();
				// 读取服务器发送来的数据到缓冲区中
				int end = 0;
	
				end = client.read(receivebuffer);
				if(end == -1) {
					client.close();
					selectionKey.cancel();
					fileDB.updateSuccess(id);
					return;
				}
				receivebuffer.flip();
				raf.write(receivebuffer.array());
				position += end;
			
				fileDB.updatePosition(position, id);	
				client.register(selector, SelectionKey.OP_READ);
			} else if(selectionKey.isWritable()) {
				
			}

		}

		@Override
		public void header(SelectionKey selectionKey) throws Exception {
			// 返回为之创建此键的通道。
			client = (SocketChannel) selectionKey.channel();
			// 将缓冲区清空以备下次读取
			receivebuffer.clear();
			// 读取服务器发送来的数据到缓冲区中
			int end = 0;
			System.out.println("handler header");
			end = client.read(receivebuffer);

			receivebuffer.flip();
			
			int size = receivebuffer.getInt();
			
			String header = new String(receivebuffer.array(), 4, size);
			System.out.println(header);
			
			String[] headers = header.split(":");
			id = Integer.parseInt(headers[0]);
			
			String filename = headers[1];
			String type = headers[2];
			String fileMark = System.currentTimeMillis() + "." + type;
			long fileLength = Long.parseLong(headers[3]);
			position = Long.parseLong(headers[4]);
			
			
			FileEntity entity = null;
			
			if(!fileDB.isFile(id)) {
				entity = fileDB.queryByID(id);
				position = entity.getPosition();
			} else {
				entity = new FileEntity();
				entity.setMark(fileMark);
				entity.setFilename(filename);
				entity.setFileType(type);
				entity.setFileSize(fileLength);
				entity.setIsSuccess(0);
				entity.setDownURL("http://127.0.0.1/move/" + filename);
				
				id = fileDB.insert(entity);
				
			}
			raf = new RandomAccessFile("c:/" + entity.getMark(), "rw");
			raf.seek(position);
			System.out.println(id);
			sendbuffer.clear();
			sendbuffer.putInt(id);
			sendbuffer.putLong(position);
			sendbuffer.flip();
			client.write(sendbuffer);
			fileChannel = raf.getChannel();
			client.register(selector, SelectionKey.OP_READ);
			isHeader = true;
		}

		public boolean isHeader() {
			return isHeader;
		}

	}

	/**
	 * @param args
	 * @throws IOException
	 */
	public static void main(String[] args) throws IOException {
		// TODO Auto-generated method stub
		int port = 8888;
		NIOServer server = new NIOServer(port);
		server.listen();
	}
}


数据库访问

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;

public class FileDBAdapter {

	private static FileDBAdapter db = null;

	private Connection conn = null;

	private FileDBAdapter() {
		try {
			Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
			conn = DriverManager.getConnection(
					"jdbc:sqlserver://localhost:1433;databaseName=FILE", "sa",
					"123456");
			conn.setAutoCommit(false);
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public static FileDBAdapter getInstance() {
		if (db == null) {
			db = new FileDBAdapter();
		}
		return db;
	}

	@SuppressWarnings("finally")
	public int insert(FileEntity entity) {
		int id = 0;
		try {
			PreparedStatement ps = conn
					.prepareStatement("insert into FILEINFO values(?,?,?,?,?,?,?)", PreparedStatement.RETURN_GENERATED_KEYS);
			ps.setString(1, entity.getMark());
			ps.setString(2, entity.getFilename());
			ps.setString(3, entity.getFileType());
			ps.setLong(4, entity.getFileSize());
			ps.setInt(5, entity.getIsSuccess());
			ps.setLong(6, entity.getPosition());
			ps.setString(7, entity.getDownURL());
			int flag = ps.executeUpdate();
		
			if (flag == 1) {
				ResultSet rs = ps.getGeneratedKeys();
				while(rs.next()) {
					id = rs.getInt(1);
				}
			}
			conn.commit();
		} catch (SQLException e) {
			e.printStackTrace();
			try {
				conn.rollback();
			} catch (SQLException e1) {
				// e1.printStackTrace();
			}
		} finally {
			return id;
		}
	}

	public void updatePosition(long position, int id) {
		try {
			System.out.println("update:" + position + ":" + id);
			PreparedStatement ps = conn
					.prepareStatement("update FILEINFO set _POSITION = ? where _ID = ?");
			ps.setLong(1, position);
			ps.setInt(2, id);
			System.out.println("update");
			ps.executeUpdate();
			conn.commit();
		} catch (SQLException e) {
			e.printStackTrace();
			try {
				conn.rollback();
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
		}
	}
	
	public void updateSuccess(int id) {
		try {
			
			PreparedStatement ps = conn
					.prepareStatement("update FILEINFO set _ISSUCCESS = 1 where _ID = ?");
			ps.setInt(1, id);
			System.out.println("update");
			ps.executeUpdate();
			conn.commit();
		} catch (SQLException e) {
			e.printStackTrace();
			try {
				conn.rollback();
			} catch (SQLException e1) {
				e1.printStackTrace();
			}
		}
	}

	public FileEntity queryByID(int id) {
		ResultSet rs = null;
		FileEntity entity = new FileEntity();
		
		try {
			PreparedStatement ps = conn
					.prepareStatement("select _ID, _FILE_MARK, _POSITION from FILEINFO where _ID = ?");
			ps.setInt(1, id);
			rs = ps.executeQuery();
			while (rs.next()) {
				entity.setId(rs.getInt("_ID"));
				entity.setMark(rs.getString("_FILE_MARK"));
				entity.setPosition(rs.getLong("_POSITION"));
			}
			rs.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		return entity;
	}
	
	public boolean isFile(int id) {
		ResultSet rs = null;
		int count = 0;
		try {
			PreparedStatement ps = conn
					.prepareStatement("select count(_ID) from FILEINFO where _ID = ? and _ISSUCCESS != 1");
			ps.setInt(1, id);
			rs = ps.executeQuery();
			while (rs.next()) {
				count = rs.getInt(1);
			}
			rs.close();
		} catch (SQLException e) {
			e.printStackTrace();
		}
		if(count == 0) {
			return true;
		} else {
			return false;
		}
	}

}



文件实体


public class FileEntity {

	private int id;
	
	private String mark;
	
	private String filename;
	
	private String fileType;
	
	private long fileSize;
	
	private int isSuccess;
	
	private long position;
	
	private String downURL;

	public int getId() {
		return id;
	}

	public void setId(int id) {
		this.id = id;
	}

	public String getMark() {
		return mark;
	}

	public void setMark(String mark) {
		this.mark = mark;
	}

	public String getFilename() {
		return filename;
	}

	public void setFilename(String filename) {
		this.filename = filename;
	}

	public String getFileType() {
		return fileType;
	}

	public void setFileType(String fileType) {
		this.fileType = fileType;
	}

	public long getFileSize() {
		return fileSize;
	}

	public void setFileSize(long fileSize) {
		this.fileSize = fileSize;
	}

	public int getIsSuccess() {
		return isSuccess;
	}

	public void setIsSuccess(int isSuccess) {
		this.isSuccess = isSuccess;
	}

	public long getPosition() {
		return position;
	}

	public void setPosition(long position) {
		this.position = position;
	}

	public String getDownURL() {
		return downURL;
	}

	public void setDownURL(String downURL) {
		this.downURL = downURL;
	} 

}