[关闭]
@coder-pig 2015-09-17T07:56:10.000000Z 字数 12159 阅读 1161

Android基础入门教程——7.6.3 基于TCP协议的Socket通信(2)

Android基础入门教程


本节引言:

上节中我们给大家接触了Socket的一些基本概念以及使用方法,然后写了一个小猪简易聊天室的
Demo,相信大家对Socket有了初步的掌握,本节我们来学习下使用Socket来实现大文件的断点续传!
这里讲解的是别人写好的一个Socket上传大文件的例子,不要求我们自己可以写出来,需要的时候会用
就好!


1.运行效果图:

1.先把我们编写好的Socket服务端运行起来:

2.将一个音频文件放到SD卡根目录下:

3.运行我们的客户端:

4.上传成功后可以看到我们的服务端的项目下生成一个file的文件夹,我们可以在这里找到上传的文件:
.log那个是我们的日志文件


2.实现流程图:


3.代码示例:

先编写一个服务端和客户端都会用到的流解析类:

StreamTool.java

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

1)服务端的实现:

socket管理与多线程管理类:

FileServer.java

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

服务端界面类:ServerWindow.java

  1. public class ServerWindow extends Frame {
  2. private FileServer s = new FileServer(12345);
  3. private Label label;
  4. public ServerWindow(String title) {
  5. super(title);
  6. label = new Label();
  7. add(label, BorderLayout.PAGE_START);
  8. label.setText("服务器已经启动");
  9. this.addWindowListener(new WindowListener() {
  10. public void windowOpened(WindowEvent e) {
  11. new Thread(new Runnable() {
  12. public void run() {
  13. try {
  14. s.start();
  15. } catch (Exception e) {
  16. // e.printStackTrace();
  17. }
  18. }
  19. }).start();
  20. }
  21. public void windowIconified(WindowEvent e) {
  22. }
  23. public void windowDeiconified(WindowEvent e) {
  24. }
  25. public void windowDeactivated(WindowEvent e) {
  26. }
  27. public void windowClosing(WindowEvent e) {
  28. s.quit();
  29. System.exit(0);
  30. }
  31. public void windowClosed(WindowEvent e) {
  32. }
  33. public void windowActivated(WindowEvent e) {
  34. }
  35. });
  36. }
  37. /**
  38. * @param args
  39. */
  40. public static void main(String[] args) throws IOException {
  41. InetAddress address = InetAddress.getLocalHost();
  42. ServerWindow window = new ServerWindow("文件上传服务端:" + address.getHostAddress());
  43. window.setSize(400, 300);
  44. window.setVisible(true);
  45. }
  46. }

2)客户端(Android端)

首先是布局文件:activity_main.xml

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  3. android:layout_width="fill_parent"
  4. android:layout_height="fill_parent"
  5. android:orientation="vertical"
  6. android:padding="5dp">
  7. <TextView
  8. android:layout_width="fill_parent"
  9. android:layout_height="wrap_content"
  10. android:text="文件名"
  11. android:textSize="18sp" />
  12. <EditText
  13. android:id="@+id/edit_fname"
  14. android:layout_width="fill_parent"
  15. android:layout_height="wrap_content"
  16. android:text="Nikki Jamal - Priceless.mp3" />
  17. <Button
  18. android:id="@+id/btn_upload"
  19. android:layout_width="wrap_content"
  20. android:layout_height="wrap_content"
  21. android:text="上传" />
  22. <Button
  23. android:id="@+id/btn_stop"
  24. android:layout_width="wrap_content"
  25. android:layout_height="wrap_content"
  26. android:text="停止" />
  27. <ProgressBar
  28. android:id="@+id/pgbar"
  29. style="@android:style/Widget.ProgressBar.Horizontal"
  30. android:layout_width="fill_parent"
  31. android:layout_height="40px" />
  32. <TextView
  33. android:id="@+id/txt_result"
  34. android:layout_width="fill_parent"
  35. android:layout_height="wrap_content"
  36. android:gravity="center" />
  37. </LinearLayout>

因为断点续传,我们需要保存上传的进度,我们需要用到数据库,这里我们定义一个数据库
管理类:DBOpenHelper.java:

  1. /**
  2. * Created by Jay on 2015/9/17 0017.
  3. */
  4. public class DBOpenHelper extends SQLiteOpenHelper {
  5. public DBOpenHelper(Context context) {
  6. super(context, "jay.db", null, 1);
  7. }
  8. @Override
  9. public void onCreate(SQLiteDatabase db) {
  10. db.execSQL("CREATE TABLE IF NOT EXISTS uploadlog (_id integer primary key autoincrement, path varchar(20), sourceid varchar(20))");
  11. }
  12. @Override
  13. public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
  14. }
  15. }

然后是数据库操作类:UploadHelper.java

  1. /**
  2. * Created by Jay on 2015/9/17 0017.
  3. */
  4. public class UploadHelper {
  5. private DBOpenHelper dbOpenHelper;
  6. public UploadHelper(Context context) {
  7. dbOpenHelper = new DBOpenHelper(context);
  8. }
  9. public String getBindId(File file) {
  10. SQLiteDatabase db = dbOpenHelper.getReadableDatabase();
  11. Cursor cursor = db.rawQuery("select sourceid from uploadlog where path=?", new String[]{file.getAbsolutePath()});
  12. if (cursor.moveToFirst()) {
  13. return cursor.getString(0);
  14. }
  15. return null;
  16. }
  17. public void save(String sourceid, File file) {
  18. SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
  19. db.execSQL("insert into uploadlog(path,sourceid) values(?,?)",
  20. new Object[]{file.getAbsolutePath(), sourceid});
  21. }
  22. public void delete(File file) {
  23. SQLiteDatabase db = dbOpenHelper.getWritableDatabase();
  24. db.execSQL("delete from uploadlog where path=?", new Object[]{file.getAbsolutePath()});
  25. }
  26. }

对了,别忘了客户端也要贴上那个流解析类哦,最后就是我们的MainActivity.java了:

  1. public class MainActivity extends AppCompatActivity implements View.OnClickListener {
  2. private EditText edit_fname;
  3. private Button btn_upload;
  4. private Button btn_stop;
  5. private ProgressBar pgbar;
  6. private TextView txt_result;
  7. private UploadHelper upHelper;
  8. private boolean flag = true;
  9. private Handler handler = new Handler() {
  10. @Override
  11. public void handleMessage(Message msg) {
  12. pgbar.setProgress(msg.getData().getInt("length"));
  13. float num = (float) pgbar.getProgress() / (float) pgbar.getMax();
  14. int result = (int) (num * 100);
  15. txt_result.setText(result + "%");
  16. if (pgbar.getProgress() == pgbar.getMax()) {
  17. Toast.makeText(MainActivity.this, "上传成功", Toast.LENGTH_SHORT).show();
  18. }
  19. }
  20. };
  21. @Override
  22. public void onCreate(Bundle savedInstanceState) {
  23. super.onCreate(savedInstanceState);
  24. setContentView(R.layout.activity_main);
  25. bindViews();
  26. upHelper = new UploadHelper(this);
  27. }
  28. private void bindViews() {
  29. edit_fname = (EditText) findViewById(R.id.edit_fname);
  30. btn_upload = (Button) findViewById(R.id.btn_upload);
  31. btn_stop = (Button) findViewById(R.id.btn_stop);
  32. pgbar = (ProgressBar) findViewById(R.id.pgbar);
  33. txt_result = (TextView) findViewById(R.id.txt_result);
  34. btn_upload.setOnClickListener(this);
  35. btn_stop.setOnClickListener(this);
  36. }
  37. @Override
  38. public void onClick(View v) {
  39. switch (v.getId()) {
  40. case R.id.btn_upload:
  41. String filename = edit_fname.getText().toString();
  42. flag = true;
  43. if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
  44. File file = new File(Environment.getExternalStorageDirectory(), filename);
  45. if (file.exists()) {
  46. pgbar.setMax((int) file.length());
  47. uploadFile(file);
  48. } else {
  49. Toast.makeText(MainActivity.this, "文件并不存在~", Toast.LENGTH_SHORT).show();
  50. }
  51. } else {
  52. Toast.makeText(MainActivity.this, "SD卡不存在或者不可用", Toast.LENGTH_SHORT).show();
  53. }
  54. break;
  55. case R.id.btn_stop:
  56. flag = false;
  57. break;
  58. }
  59. }
  60. private void uploadFile(final File file) {
  61. new Thread(new Runnable() {
  62. public void run() {
  63. try {
  64. String sourceid = upHelper.getBindId(file);
  65. Socket socket = new Socket("172.16.2.54", 12345);
  66. OutputStream outStream = socket.getOutputStream();
  67. String head = "Content-Length=" + file.length() + ";filename=" + file.getName()
  68. + ";sourceid=" + (sourceid != null ? sourceid : "") + "\r\n";
  69. outStream.write(head.getBytes());
  70. PushbackInputStream inStream = new PushbackInputStream(socket.getInputStream());
  71. String response = StreamTool.readLine(inStream);
  72. String[] items = response.split(";");
  73. String responseSourceid = items[0].substring(items[0].indexOf("=") + 1);
  74. String position = items[1].substring(items[1].indexOf("=") + 1);
  75. if (sourceid == null) {//如果是第一次上传文件,在数据库中不存在该文件所绑定的资源id
  76. upHelper.save(responseSourceid, file);
  77. }
  78. RandomAccessFile fileOutStream = new RandomAccessFile(file, "r");
  79. fileOutStream.seek(Integer.valueOf(position));
  80. byte[] buffer = new byte[1024];
  81. int len = -1;
  82. int length = Integer.valueOf(position);
  83. while (flag && (len = fileOutStream.read(buffer)) != -1) {
  84. outStream.write(buffer, 0, len);
  85. length += len;//累加已经上传的数据长度
  86. Message msg = new Message();
  87. msg.getData().putInt("length", length);
  88. handler.sendMessage(msg);
  89. }
  90. if (length == file.length()) upHelper.delete(file);
  91. fileOutStream.close();
  92. outStream.close();
  93. inStream.close();
  94. socket.close();
  95. } catch (Exception e) {
  96. Toast.makeText(MainActivity.this, "上传异常~", Toast.LENGTH_SHORT).show();
  97. }
  98. }
  99. }).start();
  100. }
  101. }

对了,还有,记得往AndroidManifest.xml中写入这些权限哦!

  1. <!-- 在SDCard中创建与删除文件权限 -->
  2. <uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS"/>
  3. <!-- 往SDCard写入数据权限 -->
  4. <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  5. <!-- 访问internet权限 -->
  6. <uses-permission android:name="android.permission.INTERNET"/>

4.代码下载:

Socket上传大文件demo


5.本节小结:

本节给大家介绍了基于TCP协议的Socket的另一个实例:使用Socket完成大文件的续传,
相信大家对Socket的了解更进一步,嗯,下一节再写一个例子吧,两个处于同一Wifi
下的手机相互传递数据的实例吧!就说这么多,谢谢~

添加新批注
在作者公开此批注前,只有你和作者可见。
回复批注