Android应用开发之使用Socket进行大文件断点上传续传

http://www.linuxidc.com/Linux/2012-03/55567.htm

http://blog.csdn.net/shimiso/article/details/8529633/

Android中上传文件可以采用HTTP方式,也可以采用Socket方式,但是HTTP方式不能上传大文件,这里介绍一种通过Socket方式来进行断点续传的方式,服务端会记录下文件的上传进度,当某一次上传过程意外终止后,下一次可以继续上传,这里用到的其实还是J2SE里的知识。

这个上传程序的原理是:客户端第一次上传时向服务端发送“Content-Length=35;filename=WinRAR_3.90_SC.exe;sourceid=“这种格式的字符串,服务端收到后会查找该文件是否有上传记录,如果有就返回已经上传的位置,否则返回新生成的sourceid以及position为0,类似”sourceid=2324838389;position=0“这样的字符串,客户端收到返回后的字符串后再从指定的位置开始上传文件。

首先是服务端代码:

SocketServer.java

  1. package com.android.socket.server;
  2. import java.io.File;
  3. import java.io.FileInputStream;
  4. import java.io.FileOutputStream;
  5. import java.io.IOException;
  6. import java.io.OutputStream;
  7. import java.io.PushbackInputStream;
  8. import java.io.RandomAccessFile;
  9. import java.net.ServerSocket;
  10. import java.net.Socket;
  11. import java.text.SimpleDateFormat;
  12. import java.util.Date;
  13. import java.util.HashMap;
  14. import java.util.Map;
  15. import java.util.Properties;
  16. import java.util.concurrent.ExecutorService;
  17. import java.util.concurrent.Executors;
  18. import com.android.socket.utils.StreamTool;
  19. public class SocketServer {
  20. private ExecutorService executorService;// 线程池
  21. private ServerSocket ss = null;
  22. private int port;// 监听端口
  23. private boolean quit;// 是否退出
  24. private Map<Long, FileLog> datas = new HashMap<Long, FileLog>();// 存放断点数据,最好改为数据库存放
  25. public SocketServer(int port) {
  26. this.port = port;
  27. // 初始化线程池
  28. executorService = Executors.newFixedThreadPool(Runtime.getRuntime()
  29. .availableProcessors() * );
  30. }
  31. // 启动服务
  32. public void start() throws Exception {
  33. ss = new ServerSocket(port);
  34. while (!quit) {
  35. Socket socket = ss.accept();// www.linuxidc.com接受客户端的请求
  36. // 为支持多用户并发访问,采用线程池管理每一个用户的连接请求
  37. executorService.execute(new SocketTask(socket));// 启动一个线程来处理请求
  38. }
  39. }
  40. // 退出
  41. public void quit() {
  42. this.quit = true;
  43. try {
  44. ss.close();
  45. catch (IOException e) {
  46. e.printStackTrace();
  47. }
  48. }
  49. public static void main(String[] args) throws Exception {
  50. SocketServer server = );
  51. server.start();
  52. }
  53. private class SocketTask implements Runnable {
  54. private Socket socket;
  55. public SocketTask(Socket socket) {
  56. this.socket = socket;
  57. }
  58. @Override
  59. public void run() {
  60. try {
  61. System.out.println("accepted connenction from "
  62. + socket.getInetAddress() + " @ " + socket.getPort());
  63. PushbackInputStream inStream = new PushbackInputStream(
  64. socket.getInputStream());
  65. // 得到客户端发来的第一行协议数据:Content-Length=143253434;filename=xxx.3gp;sourceid=
  66. // 如果用户初次上传文件,sourceid的值为空。
  67. String head = StreamTool.readLine(inStream);
  68. System.out.println(head);
  69. if (head != null) {
  70. // 下面从协议数据中读取各种参数值
  71. String[] items = head.split(";");
  72. String filelength = items[].substring(items[].indexOf();
  73. String filename = items[].substring(items[].indexOf();
  74. String sourceid = items[].substring(items[].indexOf();
  75. Long id = System.currentTimeMillis();
  76. FileLog log = null;
  77. if (null != sourceid && !"".equals(sourceid)) {
  78. id = Long.valueOf(sourceid);
  79. log = find(id);//查找上传的文件是否存在上传记录
  80. }
  81. File file = null;
  82. ;
  83. if(log==null){//如果上传的文件不存在上传记录,为文件添加跟踪记录
  84. String path = new SimpleDateFormat("yyyy/MM/dd/HH/mm").format(new Date());
  85. File dir = new File("file/"+ path);
  86. if(!dir.exists()) dir.mkdirs();
  87. file = new File(dir, filename);
  88. if(file.exists()){//如果上传的文件发生重名,然后进行改名
  89. filename = filename.substring(, filename.indexOf()+ dir.listFiles().length+ filename.substring(filename.indexOf("."));
  90. file = new File(dir, filename);
  91. }
  92. save(id, file);
  93. }else{// 如果上传的文件存在上传记录,读取上次的断点位置
  94. file = new File(log.getPath());//从上传记录中得到文件的路径
  95. if(file.exists()){
  96. File logFile = new File(file.getParentFile(), file.getName()+".log");
  97. if(logFile.exists()){
  98. Properties properties = new Properties();
  99. properties.load(new FileInputStream(logFile));
  100. position = Integer.valueOf(properties.getProperty("length"));//读取断点位置
  101. }
  102. }
  103. }
  104. OutputStream outStream = socket.getOutputStream();
  105. String response = "sourceid="+ id+ ";position="+ position+ "\r\n";
  106. //服务器收到客户端的请求信息后,给客户端返回响应信息:sourceid=1274773833264;position=0
  107. //sourceid由服务生成,唯一标识上传的文件,position指示客户端从文件的什么位置开始上传
  108. outStream.write(response.getBytes());
  109. RandomAccessFile fileOutStream = new RandomAccessFile(file, "rwd");
  110. ) fileOutStream.setLength(Integer.valueOf(filelength));//设置文件长度
  111. fileOutStream.seek(position);//移动文件指定的位置开始写入数据
  112. ];
  113. ;
  114. int length = position;
  115. ){//从输入流中读取数据写入到文件中
  116. fileOutStream.write(buffer, , len);
  117. length += len;
  118. Properties properties = new Properties();
  119. properties.put("length", String.valueOf(length));
  120. FileOutputStream logFile = new FileOutputStream(new File(file.getParentFile(), file.getName()+".log"));
  121. properties.store(logFile, null);//实时记录文件的最后保存位置
  122. logFile.close();
  123. }
  124. if(length==fileOutStream.length()) delete(id);
  125. fileOutStream.close();
  126. inStream.close();
  127. outStream.close();
  128. file = null;
  129. }
  130. catch (Exception e) {
  131. e.printStackTrace();
  132. finally {
  133. try {
  134. if(socket != null && !socket.isClosed()) socket.close();
  135. catch (IOException e) {}
  136. }
  137. }
  138. }
  139. public FileLog find(Long sourceid) {
  140. return datas.get(sourceid);
  141. }
  142. // 保存上传记录
  143. public void save(Long id, File saveFile) {
  144. // 日后可以改成通过数据库存放
  145. datas.put(id, new FileLog(id, saveFile.getAbsolutePath()));
  146. }
  147. // 当文件上传完毕,删除记录
  148. public void delete(long sourceid) {
  149. if (datas.containsKey(sourceid))
  150. datas.remove(sourceid);
  151. }
  152. private class FileLog {
  153. private Long id;
  154. private String path;
  155. public FileLog(Long id, String path) {
  156. super();
  157. this.id = id;
  158. this.path = path;
  159. }
  160. public Long getId() {
  161. return id;
  162. }
  163. public void setId(Long id) {
  164. this.id = id;
  165. }
  166. public String getPath() {
  167. return path;
  168. }
  169. public void setPath(String path) {
  170. this.path = path;
  171. }
  172. }
  173. }

ServerWindow.java

  1. package com.android.socket.server;
  2. import java.awt.BorderLayout;
  3. import java.awt.Frame;
  4. import java.awt.Label;
  5. import java.awt.event.WindowEvent;
  6. import java.awt.event.WindowListener;
  7. public class ServerWindow extends Frame{
  8. private SocketServer server;
  9. private Label label;
  10. public ServerWindow(String title){
  11. super(title);
  12. server = );
  13. label = new Label();
  14. add(label, BorderLayout.PAGE_START);
  15. label.setText("服务器已经启动www.linuxidc.com");
  16. this.addWindowListener(new WindowListener() {
  17. @Override
  18. public void windowOpened(WindowEvent e) {
  19. new Thread(new Runnable() {
  20. @Override
  21. public void run() {
  22. try {
  23. server.start();
  24. catch (Exception e) {
  25. e.printStackTrace();
  26. }
  27. }
  28. }).start();
  29. }
  30. @Override
  31. public void windowIconified(WindowEvent e) {
  32. }
  33. @Override
  34. public void windowDeiconified(WindowEvent e) {
  35. }
  36. @Override
  37. public void windowDeactivated(WindowEvent e) {
  38. }
  39. @Override
  40. public void windowClosing(WindowEvent e) {
  41. server.quit();
  42. System.exit();
  43. }
  44. @Override
  45. public void windowClosed(WindowEvent e) {
  46. }
  47. @Override
  48. public void windowActivated(WindowEvent e) {
  49. }
  50. });
  51. }
  52. /**
  53. * @param args
  54. */
  55. public static void main(String[] args) {
  56. ServerWindow window = new ServerWindow("文件上传服务端");
  57. window.setSize(, );
  58. window.setVisible(true);
  59. }
  60. }

工具类StreamTool.java

  1. package com.android.socket.utils;
  2. import java.io.ByteArrayOutputStream;
  3. import java.io.File;
  4. import java.io.FileOutputStream;
  5. import java.io.IOException;
  6. import java.io.InputStream;
  7. import java.io.PushbackInputStream;
  8. public class StreamTool {
  9. public static void save(File file, byte[] data) throws Exception {
  10. FileOutputStream outStream = new FileOutputStream(file);
  11. outStream.write(data);
  12. outStream.close();
  13. }
  14. public static String readLine(PushbackInputStream in) throws IOException {
  15. ];
  16. int room = buf.length;
  17. ;
  18. int c;
  19. loop:       while (true) {
  20. switch (c = in.read()) {
  21. :
  22. case '\n':
  23. break loop;
  24. case '\r':
  25. int c2 = in.read();
  26. )) in.unread(c2);
  27. break loop;
  28. default:
  29. ) {
  30. char[] lineBuffer = buf;
  31. buf = ];
  32. room = buf.length - offset - ;
  33. System.arraycopy(lineBuffer, , buf, , offset);
  34. }
  35. buf[offset++] = (char) c;
  36. break;
  37. }
  38. }
  39. ) && (offset == )) return null;
  40. , offset);
  41. }
  42. /**
  43. * 读取流
  44. * @param inStream
  45. * @return 字节数组
  46. * @throws Exception
  47. */
  48. public static byte[] readStream(InputStream inStream) throws Exception{
  49. ByteArrayOutputStream outSteam = new ByteArrayOutputStream();
  50. ];
  51. ;
  52. ){
  53. outSteam.write(buffer, , len);
  54. }
  55. outSteam.close();
  56. inStream.close();
  57. return outSteam.toByteArray();
  58. }
  59. }

Android客户端代码:

Android应用开发之使用Socket进行大文件断点上传续传

上一篇:docker学习---搭建Docker LAMP环境


下一篇:可扩展多线程异步Socket服务器框架EMTASS 2.0 续