当前很多手机应用或者是网络应用都需要支持大文件上传功能,有些用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;
}
}