加载中...

7.6.3 基于TCP协议的Socket通信(2)


本节引言:

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

1.运行效果图:

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

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

3.运行我们的客户端:

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

2.实现流程图:

3.代码示例:

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

StreamTool.java

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

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

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.  
  8. <TextView
  9. android:layout_width="fill_parent"
  10. android:layout_height="wrap_content"
  11. android:text="文件名"
  12. android:textSize="18sp" />
  13.  
  14. <EditText
  15. android:id="@+id/edit_fname"
  16. android:layout_width="fill_parent"
  17. android:layout_height="wrap_content"
  18. android:text="Nikki Jamal - Priceless.mp3" />
  19.  
  20. <Button
  21. android:id="@+id/btn_upload"
  22. android:layout_width="wrap_content"
  23. android:layout_height="wrap_content"
  24. android:text="上传" />
  25.  
  26. <Button
  27. android:id="@+id/btn_stop"
  28. android:layout_width="wrap_content"
  29. android:layout_height="wrap_content"
  30. android:text="停止" />
  31.  
  32. <ProgressBar
  33. android:id="@+id/pgbar"
  34. style="@android:style/Widget.ProgressBar.Horizontal"
  35. android:layout_width="fill_parent"
  36. android:layout_height="40px" />
  37.  
  38. <TextView
  39. android:id="@+id/txt_result"
  40. android:layout_width="fill_parent"
  41. android:layout_height="wrap_content"
  42. android:gravity="center" />
  43. </LinearLayout>

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

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

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

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

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

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

最后,还有,记得往**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.代码下载:

5.本节小结:

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

下载地址


还没有评论.