美文网首页
网络编程(3)——网络基础文件下载,多线程下载

网络编程(3)——网络基础文件下载,多线程下载

作者: 让时间走12138 | 来源:发表于2020-05-16 17:37 被阅读0次

    本节内容

    1.OSI文件模型介绍

    2.HTTP请求和响应报文结构

    3.IP地址、DNS和端口号

    4.使用URLConnection获取网络数据

    5.实现下载文件

    6.多线程下载获取文件

    7.多线程下载结构分析

    8.分配线程下载任务

    9.实现进度监听

    一、OSI文件模型介绍

    1.网络中传递数据的过程:客户端(发起请求)——>本机(对请求的数据进行包装)——>(TCP/IP协议进行传输)网络中传输——>服务器主机(通过端口号访问具体进程)——>进程
    2.OSI的七层网络数据模型
    • (1)应用层:为操作系统或网络程序提供访问网络服务器的接口。使用URLConnection这个接口或者socket发起网络申请
    • (2)表示层:提供数据格式转换服务,加密和解密,图片解码和编码,数据压缩和解压
    • (3)会话层:建立一个端到端的链接,并提供访问验证和会话管理
    • (4)传输层:提供应用进程之间的逻辑通信,建立连接,数据校验,数据包次序,如TCP,UDP,进程,端口
    • (5)网络层:为数据在节点之间的传输创建逻辑链路,并分组转发数据
    • (6)数据链路层:在通信实体间建立数据链路连接,如数据分帧,处理流控制,物理地址寻址,重发
    • (7)物理层:负责最后将信息编码成电流脉冲或者其他信号用于网上传输
    3.OSI的四层网络数据模型
    • (1)应用层:HTTP,FTP,DNS,SMTP
    • (2)传输层:通过TCP,UDP协议进行传输
    • (3)网络层:数据包装,寻址路由IP、PHP、ICMP协议
    • (4)物理层
    4.html与服务器建立联系的过程:html 通过操作系统提供的接口发起请求——>将请求的数据使用TCP/IP协议进行包装——>网络层进行数据分包——>链路中——>物理层——————————>网络————————>主机(物理层—>链路层—>网络层—>传输层—>应用层)

    二、HTTP请求和响应报文结构

    1.Http和TCP/UDP 的关系:
    • HTTP协议 :主要负责数据的具体传输,对数据进行封装
    • TCP/UDP协议 :负责将数据在网络中传输
    2.请求Request和响应Response
    • 请求:
    • 客户端需要向服务器上传数据
    • 客户端需要从服务器下载数据
    • 响应:
    • 服务器端对客户端的请求作出的回应
    3.三次握手建立连接
    • HTTP封装完数据后,需要将数据使用TCP协议向网络中的其他主机(服务器)进行发送,需要经过三个过程/三次握手
    4.Http协议报文
    • 请求方式:
    • GET:请求数据(1.提交数据 2.接收返回的数据)提交的数据会在url地址中显示表示出来(大小有限制)
    • POST:请求数据(1.提交数据 2.接收返回的数据)提交的数据在请求体重 url地址不会出现(有文件就必须使用POST)
    • HEAD:只是获取服务器端返回的响应信息,不会获取具体的内容,先获取大小,再分配线程
    • 状态码:
    • 200-206 请求成功
    • 300-305 重定向 www.baidu.com->www.qq.com(就是进入另外一个网址)
    • 400-405 客户端错误
    • 500-505 服务器端错误
    • 请求头:请求的方式、地址,上传的参数 。请求体:提交的是什么里面就是什么。
    • 响应头:回调给我们的数据,包含一些状态码和一些资源信息。响应体:包括具体的资源。

    三、IP地址、DNS和端口号

    1.IP地址:就是用来唯一标识网络中的一台设备(192.10.121这种类型)
    2.域名:www.baidu.com
    3.DNS:域名解析器。可以通过域名解析出它的IP地址
    4.一个网址http://127.0.0.1/login.php?user_name=jack&user_pwd=123
    • http:表示数据传输使用的具体协议
    • 127.0.0.1:访问的主机地址
    • login.php:表示访问主机的文件或者目录
    • ?(分隔符):表示需要向服务器提交数据,提交方式GET。后面就是具体提交的数据。
    • user_name=:表示提交的数据,user_name是服务器定义的字段,jack是字段对应的数据
    • 使用&来链接多个字段
    5.端口号
    • 端口对应的是一种服务
    • 80端口对应的是网络服务
    • 公认端口:0-1023 一些特定的服务
    • 注册端口:1024-49151 应用程序使用该范围端口
    • 动态私有端口:49152-65535

    四、使用URLConnection获取网络数据

    1.Java使用URL来封装网络数据的地址
    2.url地址里面不能包含中文或者其他一些特殊的字符,对于这些字符需要进行编码或者解码
    使用方法如下
    public class MyClass {
        public static void main(String[] args) throws Exception {
            String str ="http://127.0.0.1/login.php?user_name=jack&user_pwd=123&
    submit=%E7%99%BB%E5%BD%95";
            URL url =new URL(str);
    
           String word= URLDecoder.decode("%E7%99%BB%E5%BD%95","utf-8");
           String code=URLEncoder.encode("登录","utf-8");
           System.out.println(code);
          System.out.println(word);
        }
    }
    
    3.通过以下方式获取主机地址
    System.out.println(url.getHost());
    
    4.通过以下方式获取使用的协议
    System.out.println(url.getProtocol());
    
    5.通过以下方式获取具体提交的数据
    System.out.println(url.getQuery());
    
    6.向网络中发起请求
    • (1).获取对应的url地址
    String str ="http://127.0.0.1/login.php?user_name=jack&user_pwd=123";
            URL url =new URL(str);
    
    • (2).使用URLConnection发起链接
    HttpURLConnection coon=(HttpURLConnection)url.openConnection();
            coon.setRequestMethod("GET");
            coon.setDoInput(true);/设置是否打开接收的流
            coon.setDoOutput(true);/设置是否打开输出流,POST必须打开
            coon.setConnectTimeout(5*1000);/链接请求时间
            coon.setUseCaches(false);/不需要缓存
    
    • (3).数据接收和发送都是使用输入输出流
            /url.openStream()从服务器端获取数据
            /coon.getOutputStream();向服务器端发送数据
          InputStream is= url.openStream();
    
    • (4).处理输入流的数据
    byte[] buffer= new byte[1024];
          int len=0;
          while((len=is.read(buffer))!=-1){
              String content= new String(buffer,0,len);
              System.out.println(content);
          }
    
    • (5).关闭流
    is.close();
    

    五、实现下载文件

    1.新建一个URL对象
    URL url=new URL("http://127.0.0.1/upload/vedio/test.mp4");
    
    2.连接
    HttpURLConnection coon=(HttpURLConnection)url.openConnection();
    
    3.获取输入流
    InputStream inputStream=url.openStream();
    BufferedInputStream bis=new BufferedInputStream(inputStream);
    
    4.创建输出流
            String des= "C:\\Users\\86178\\Desktop\\gg.mp4";
            FileOutputStream fos=new FileOutputStream(des);
            BufferedOutputStream bos= new BufferedOutputStream(fos);
    
    5.将数据写入磁盘
            byte []  buffer= new byte[1024];
            int len=0;
            while ((len=bis.read(buffer))!=-1){
                bos.write(buffer,0,len);
            }
            bos.close();
            fos.close();
            bis.close();
            inputStream.close();
    

    六、多线程下载获取文件

    1.创建一个DownloadManager类,并采用单例设计模式
    public class DownloadManager {
        private Map<String,String>[] source;
        private static DownloadManager manager;
        private DownloadManager(){}
        private static Object obj=new Object();
    
        public static  DownloadManager getInstance(){
            if(manager==null){
                synchronized (obj){
                    if (manager==null){
                        manager=new DownloadManager();
                    }
                }
            }
            return manager;
        }
        public void loadData(String urlString,String filePath){
            DownOperation  downloader= new DownOperation(urlString,filePath,3);
            downloader.download();
        }
    }
    
    2.创建一个DownOperation类,创建它的构造方法,并提供获取文件大小以及链接数据的函数
    public class DownOperation {
        private URL url;
        private String filePath;
        private int threadCount;
        private DownThread [] tasks;
        private long size;
    
        public DownOperation(String urlString,String filePath, int threadCount)  {
            try {
                this.url=new URL(URLEncoder.encode(urlString,"utf-8"));
            } catch (Exception e) {
                e.printStackTrace();
            }
            this.filePath = filePath;
            this.threadCount = threadCount;
          tasks= new DownThread[threadCount];
        }
      public void download(){
             //获取文件的大小
             getFileSize();
             System.out.println(size);
      }
      private void getFileSize(){
         //获取连接
          HttpURLConnection coon=null;
          try {
              coon=(HttpURLConnection) url.openConnection();
              coon.setRequestMethod("HEAD");
              //获取资源大小
             size= coon.getContentLengthLong();
    
          } catch (IOException e) {
              e.printStackTrace();
          }finally {
              //关闭链接
              coon.disconnect();
          }
    
      }
    }
    
    3.在主函数中,直接通过DownloadManager实现文件的下载
    public class MyClass {
        public static void main(String[] args) {
            String url="http\\127.0.0.1\\upload\\video\\test.mp4";
            String filePath="C:\\Users\\86178\\Desktop";
            //下载一个文件
         DownloadManager.getInstance().loadData(url,filePath);
        }
    }
    

    七、多线程下载结构分析

    1.首先需要知道文件大小->知道下载线程数量->准备文件接收数据->通过RandomAccessFile访问数据

    八、分配线程下载任务

    1.对前面的代码进行改写,在download函数里面添加一些内容
    public void download(){
             //获取文件的大小
             getFileSize();
            //创建文件,用于保存下载的数据
          createFile();
          //分配线程下载数据
          dispatch();
      }
    
    2.分别实现后面两个函数,这一切都在DownOperation类里面
    private void dispatch(){
            //计算每个线程下载的平均值
          long average=size/threadCount;
          long start =0;
          long donwloadsize=average;
          for(int i=0;i<threadCount;i++){
             start=i*average;
             //最后一个线程,可能下载的数量要大于平均值
              if(i==threadCount-1){
                  donwloadsize=size-start;
              }
              //创建线程,执行对应的模块进行下载
              DownThread dt =new DownThread(url,filePath,start,donwloadsize);
              //添加保存线程对象
              tasks[i]=dt;
              //启动下载
              dt.start();
    
          }
      }
    
      private void createFile(){
          File file =new File(filePath);
          try {
              file.createNewFile();
          } catch (IOException e) {
              e.printStackTrace();
          }
    
          //设置文件大小
          RandomAccessFile rac=null;
          try {
              rac= new RandomAccessFile(file,"rw");
              rac.setLength(size);
          } catch (Exception e) {
              e.printStackTrace();
          }finally {
              try {
                  rac.close();
              } catch (IOException e) {
                  e.printStackTrace();
              }
    
          }
      }
    
    3.创建一个DownThread类,在它的run方法里面真正实现文件的多线程下载
    public class DownThread extends Thread {
        private URL url;
        private String filePath;
        private long startPosition;
        private long size;
        private long downloadedLength;
       public DownThread (URL url,String filePath,long startPosition,long size){
           this.url=url;
           this.filePath=filePath;
           this.startPosition=startPosition;
           this.size=size;
       }
    
        @Override
        public void run() {
           //定位文件到这个线程应该写入的位置
            try {
                RandomAccessFile raf=new RandomAccessFile(filePath,"rw");
                raf.seek(startPosition);
    
                //开始下载
                HttpURLConnection coon=(HttpURLConnection) url.openConnection();
                coon.setRequestMethod("GET");
               coon.setDoInput(true);
               coon.setConnectTimeout(5*1000);
    
               //获取输入流
                InputStream is= url.openStream();
              //设置数据读取位置
                is.skip(startPosition);
    
                //开始读取数据,写入到文件
                byte[]  buffer=new byte[1024];
                int len=0;
                while ((len=is.read(buffer))!=-1){
                    raf.write(buffer,0,len);
                    //记录当前下载长度
                    downloadedLength+=len;
                  //判断当前线程下载的范围是否结束
                    if(downloadedLength>size){
                        break;
                    }
                }
                is.close();
                coon.disconnect();
                raf.close();
    
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
    
    其他的和第六的代码一样

    九、实现进度监听

    1.在DownOperation类里面添加一个currentRate方法来记录当前的下载进度
    public float currentRate(){
            long len=0;
            for(DownThread dt:tasks){
               len=dt.downloadedLength;
            }
            return (float)len/size;
      }
    
    2.在DownloadManager类里面的loadData函数里面,new 一个Thread对象,然后实现runnable接口
    public void loadData(String urlString,String filePath){
        final DownOperation  downloader= new DownOperation(urlString,filePath,3);
            downloader.download();
            new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        float rate=downloader.currentRate();
                        if(rate<1.0000001) {
            System.out.println("当前下载比例:" + downloader.currentRate());
                            try {
                                Thread.sleep(100);
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }else {
                            break;
                        }
                    }
                }
            }).start();
        }
    
    OK,以上就是今天的全部内容了,再见。

    相关文章

      网友评论

          本文标题:网络编程(3)——网络基础文件下载,多线程下载

          本文链接:https://www.haomeiwen.com/subject/myyvghtx.html