网络框架原理以及手写一个基本的网络框架

为什么要手写框架?

  • 岗位需求
  • 提升必要
  • 减少资源浪费
  • 进阶的必经之路

掌握网络框架的原理

网络框架原理以及手写一个基本的网络框架_第1张图片
image.png

搭建网络访问框架

  • 首先创建一个网络Moudle模块


    网络框架原理以及手写一个基本的网络框架_第2张图片
    image.png

    网络框架原理以及手写一个基本的网络框架_第3张图片
    image.png

    网络框架原理以及手写一个基本的网络框架_第4张图片
    image.png
  • 主项目依赖我们的网络框架


    image.png
  • 搭建程序员请求类(入参,接受返回)JyfHttp

package com.example.http;

/**
 * 供程序员使用的类
 */
public class JyfHttp {
    // url
    // 请求参数
    // 接收结果的回调接口
    // 接收结果的参数类型
    public static void senRequest(String url, T requestData, Class response, IJsonDataListener iJsonDataListener) {
        // 请求对象的封装
        // 为什么使用父类呢?通过接口的父类的引用去指向子类的实例
        IHttpRequest httpRequest = new JsonHttpRequest();
        // 创建一个内部回调接口的实现类
        CallBackListener callBackListener = new JsonCallBackListener(response, iJsonDataListener);
        // 请求对象的二次封装,将请求对象封装成线程
        HttpTask httpTask = new HttpTask(httpRequest, callBackListener, url, requestData);
        // 将请求线程加入到请求队列中
        ThreadManager.getInstance().addTask(httpTask);
    }
}

(1)创建请求对象

package com.example.http;

/**
 * 请求对象的顶层接口
 * 封装我们的请求对象
 */
public interface IHttpRequest {
    // 设置Url
    void setUrl(String url);

    // 设置请求参数
    void setData(byte[] data);

    // 设置回调接口 两个回调接口来会调用,实现服务器返回的数据流直接解析为程序员需要的数据类型
    void setLisener(CallBackListener callBackListener);

    // 执行请求的方法
    void execute();
}
package com.example.http;


import java.io.BufferedOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;

/**
 * 请求接口的实现类 请求参数的封装类
 */
public class JsonHttpRequest implements IHttpRequest{
    private String url;

    private byte[] data;

    private CallBackListener callBackListener;

    // 执行请求的对象
    private HttpURLConnection httpURLConnection;

    @Override
    public void setUrl(String url) {
        this.url = url;
    }

    @Override
    public void setData(byte[] data) {
        this.data = data;
    }

    @Override
    public void setLisener(CallBackListener callBackListener) {
        this.callBackListener = callBackListener;
    }

    @Override
    public void execute() {
        URL url = null;
        try {
            url = new URL(this.url);
            httpURLConnection = (HttpURLConnection) url.openConnection();
            httpURLConnection.setConnectTimeout(6000); // 连接超时时间
            httpURLConnection.setUseCaches(false);  // 不使用缓存
            httpURLConnection.setInstanceFollowRedirects(true); // 是成员变量 仅作用域当前函数,设置当前这个对象
            httpURLConnection.setReadTimeout(3000); // 响应超时的时间
            httpURLConnection.setDoInput(true); // 设置这个链接是否可以写入数据
            httpURLConnection.setDoOutput(true);    // 设置这个链接是否可以输出数据
            httpURLConnection.setRequestMethod("POST"); // 设置这个请求的方法
            httpURLConnection.setRequestProperty("Content-Type", "application/json;charset=UTF-8");
            httpURLConnection.connect();    // 建立链接

            // --------------------使用字节流发送数据---------------------
            OutputStream out = httpURLConnection.getOutputStream();
            // 缓冲字节流 包装字节流
            BufferedOutputStream bos = new BufferedOutputStream(out);
            // 把字节流数组写入缓冲区中
            bos.write(data);
            // 刷新缓冲区 发送数据
            bos.flush();
            out.close();
            bos.close();

            // 如果响应码为200代表访问成功
            if (httpURLConnection.getResponseCode() == HttpURLConnection.HTTP_OK) {
                InputStream in = httpURLConnection.getInputStream();
                // 先回调内部的回调接口 让内部接口去转换为程序员需要的Class
                callBackListener.onSuccess(in);
            } else {
              throw new RuntimeException("请求失败!");
            }
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("请求失败!");
        } finally {
            // 关闭 HTTPConnection 对象
            httpURLConnection.disconnect();
        }
    }

    public String getUrl() {
        return url;
    }

    public byte[] getData() {
        return data;
    }

    public CallBackListener getCallBackListener() {
        return callBackListener;
    }

    public HttpURLConnection getHttpURLConnection() {
        return httpURLConnection;
    }
}

(2)创建一个内部回调接口

package com.example.http;

import java.io.InputStream;

/**
 * 请求接口的回调 给框架层调用的接口
 * @param 
 */
public interface CallBackListener {
    // 请求成功
    void onSuccess(InputStream inputStream);

    // 请求失败
    void onFailed();
}

package com.example.http;

import android.os.Handler;
import android.os.Looper;

import com.alibaba.fastjson.JSON;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;

/**
 * 内部接口的实现类 将流转为用户想要的类型
 * 实现接口,则接口调用的时候,持有的实例就直接是接口的实现类
 */
public class JsonCallBackListener implements CallBackListener{

    // 知道用户需要的请求结果的类型
    private Class response;

    // 外部的回调接口类
    private IJsonDataListener iJsonDataListener;

    // 切换线程的Handler
    private Handler handler = new Handler(Looper.getMainLooper());

    /**
     * 构造方法
     * 在创建的时候就把需要的参数传递进来
     * @param response
     * @param iJsonDataListener
     */
    public JsonCallBackListener(Class response, IJsonDataListener iJsonDataListener) {
        this.response = response;
        this.iJsonDataListener = iJsonDataListener;
    }

    @Override
    public void onSuccess(InputStream inputStream) {
        // 解析结束 回调给外部的回调接口
        // 使用阿里巴巴的fastjson来解析即可
        // 第一步 将流转化为json字符串
        String content = getContent(inputStream);
        // 第二步 使用阿里巴巴的库将JSON字符串转化为程序员想要的对象类型
        final T t = JSON.parseObject(content, response);
        // 第三部 通过主线程将数据返回出去供外界使用
        handler.post(new Runnable() {
            @Override
            public void run() {
                iJsonDataListener.onSuccess(t);
            }
        });
    }

    @Override
    public void onFailed() {
        handler.post(new Runnable() {
            @Override
            public void run() {
                iJsonDataListener.onFailed();
            }
        });
    }

    /**
     * 讲inputStream转化为String类型
     * @param inputStream
     * @return
     */
    private String getContent(InputStream inputStream) {
        BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
        StringBuilder sb = new StringBuilder();
        String line = null;
        try {
            while ((line = bufferedReader.readLine()) != null) {
                sb.append(line + "/n");
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return sb.toString().replace("/n", "");
    }
}

(3)请求对象再次封装为Task继承Runable() 任务即可,也就是一个任务就是为一个线程

package com.example.http;

import com.alibaba.fastjson.JSON;

import java.io.UnsupportedEncodingException;
import java.util.concurrent.Delayed;
import java.util.concurrent.TimeUnit;

/**
 * 请求对象的二次封装,将请求对象封装成线程,在线程中来执行请求
 */
public class HttpTask implements Runnable, Delayed {

    public IHttpRequest getiHttpRequest() {
        return iHttpRequest;
    }

    public void setiHttpRequest(IHttpRequest iHttpRequest) {
        this.iHttpRequest = iHttpRequest;
    }

    // 请求对象
    private IHttpRequest iHttpRequest;

    // 当前失败的次数
    private int failNum;

    public long getDelayTime() {
        return delayTime;
    }

    public void setDelayTime(long delayTime) {
        this.delayTime = delayTime + System.currentTimeMillis();    // 把当前的时间以时间戳的形式记录下来
    }

    // 重试的延迟时间
    private long delayTime;

    /**
     *
     * @param iHttpRequest  请求对象
     * @param callBackListener  请求结果的回调接口
     * @param url   请求路径
     * @param requestData   请求数据的对象
     */
    public HttpTask(IHttpRequest iHttpRequest, CallBackListener callBackListener, String url, T requestData) {
        this.iHttpRequest = iHttpRequest;
        this.iHttpRequest.setUrl(url);
        this.iHttpRequest.setLisener(callBackListener);

        // 判断是否拥有请求数据
        if (requestData != null) {
            // 将请求对象转为Json字符串
            String dataStr = JSON.toJSONString(requestData);
            try {
                this.iHttpRequest.setData(dataStr.getBytes("utf-8"));
            } catch (UnsupportedEncodingException e) {
                e.printStackTrace();
            }
        }
    }

    @Override
    public void run() {

        try {
            this.iHttpRequest.execute();
        }catch (Exception e) {
            // 捕捉到异常,然后将请求线程丢入到重试机制中
            ThreadManager.getInstance().addFailedTask(this);
        }
        this.iHttpRequest.execute();
    }

    /**
     * 队列的执行间隔时间
     * @param unit
     * @return
     */
    @Override
    public long getDelay(TimeUnit unit) {

        // 取出时间 减去时间戳
        return unit.convert(getDelayTime() - System.currentTimeMillis(), TimeUnit.SECONDS);
    }

    @Override
    public int compareTo(Delayed o) {
        return 0;
    }

    public int getFailNum() {
        return failNum;
    }

    public void setFailNum(int failNum) {
        this.failNum = failNum;
    }
}

(4)直接使用task.execute执行即可
(5)创建队列管理类,添加进队列即可 ThreadManager().getInstance().addTask(task)

package com.example.http;

import android.util.Log;

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.DelayQueue;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.RejectedExecutionHandler;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

/**
 * 队列管理类 线程管理类
 * 使用单例来实现
 */
public class ThreadManager {

    private static ThreadManager threadManager = new ThreadManager();

    // 请求线程的阻塞队列
    // 为什么使用Runnable而不是HttpTask是因为后面更好的扩展,比如再有一个其他的继承Runnable类
    private LinkedBlockingDeque mQueue = new LinkedBlockingDeque<>();
    // 线程池
    private ThreadPoolExecutor threadPoolExecutor;

    // 创建重试队列
    // 用到DelayQueue,必须要实现一个Delay的接口,有一个时间的概念
    private DelayQueue failedQueue = new DelayQueue<>();

    private ThreadManager(){
        // 初始化线程池
        // 解释一下线程池各个参数的意思
        /**
         * 1: 核心线程数
         * 2:最大线程数
         * 3:空闲线程的保留时间
         * 4:单位:秒
         * 5:队列对象,它决定了缓存任务的排队策略,用来装载被拒绝的对象,以及最大数量
         * 6:被拒绝之后
         */
        threadPoolExecutor = new ThreadPoolExecutor(3, 10, 15, TimeUnit.SECONDS, new ArrayBlockingQueue(4),
                new RejectedExecutionHandler() {
                    @Override
                    public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
                        // 被拒绝后再次加入到线程池中,保证每一个请求都执行到位
                        addTask(r);
                    }
                });

        // 通过线程池,来执行我们的核心线程
        threadPoolExecutor.execute(runnable);
        // 执行重试队列的核心线程
        threadPoolExecutor.execute(failedRunnable);
    }

    public static ThreadManager getInstance() {
        return threadManager;
    }

    /**
     * 将请求线程加入到队列中的方法
     */
    public void addTask(Runnable runnable) {
        if (runnable == null) return;
        try {
            mQueue.put(runnable);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 将失败的请求线程加入到重试队列中
     * @param httpTask
     */
    public void addFailedTask(HttpTask httpTask) {
        if (runnable == null) return;
        failedQueue.offer(httpTask);
    }

    /**
     * 核心线程 一直去队列中获取到请求线程,然后让线程池去执行
     */
    public Runnable runnable = new Runnable() {
        @Override
        public void run() {
            while (true) {
                try {
                    // 拿出请求线程
                    Runnable take = mQueue.take();
                    // 通过请求线程去执行
                    threadPoolExecutor.execute(take);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };

    /**
     * 重试的核心线程
     */
    public Runnable failedRunnable = new Runnable() {
        @Override
        public void run() {
            while (true) {
                try {
                    HttpTask httpTask = failedQueue.take();
                    if (httpTask.getFailNum() < 3) {
                        // 通过线程池执行
                        httpTask.setFailNum(httpTask.getFailNum() + 1);
                        threadPoolExecutor.execute(httpTask);
                        Log.e("请求重试机制==========", "我是次数" + httpTask.getFailNum());
                    } else {
                        JsonHttpRequest iHttpRequest = (JsonHttpRequest) httpTask.getiHttpRequest();
                        iHttpRequest.getCallBackListener().onFailed();
                        // 重试了三次依然失败
                        Log.e("请求重试机制=============", "失败超过3次");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };
}

  • 定义回调接口 CallBackListener(代码参照上面)
  • 内部接口接受返回并转化为程序员需要的类 JsonCallBackListener(参数为用户想要的数据类型、以及回调的Listener)
    (1) 将流转化为JSON字符串,再将JSON转为用户想要的类型
    (2) 使用Hander回到主线程回调给CallBackListener(代码参照上面)

高并发的实现思路

  • 上面把请求对象封装进入了一个线程进行执行,那么我们就可以随处来创建线程了么?不,我们需要创建一个线程池来管理所有的线程,即线程池
  • 创建请求队列管理类以及线程管理类
  • 我们一个框架,只需要创建一个队列即可
  • 使用单例来创建一个线程池管理器,ThreadManager(参照上面)
  • 创建一个LinkBlockingQueue队列,来管理Task即可
  • 创建线程池 ThreadPoorExecutor 来管理队列,(参数为核心线程数、最大线程数、存活时间)线程池可以处理任何线程情况,线程执行成功了,进行标记,回收掉。如果没有执行成功,则感知到没有执行成功,那么就会通过rejuctedExecution回调回来,我们再次添加task到队列中,重新执行即可,那么这个线程永远不会因为框架原因而执行失败。
  • 再写一个核心线程,一直去队列中获取请求人物,然后交给线程池去执行
  • 使用mQueue.take() 获取每个Task,交给线程池执行,threadPoorExector.execute(),核心线程什么时候执行?
  • 执行100次?闪退了?是因为高并发的情况下,往往造成有的请求执行不成功,那么我们怎么错?就是接下来的重试机制!!

断网或者弱网重试机制

  • 网络波动的时候
  • 各种意外被拒绝的请求再次执行
  • 三次机会之后,直接销毁
    (1)HttpTask的run方法中抛出了异常
    (2)使用try catch 捕捉到异常,再次添加到我们的线程池进行任务重试即可
 @Override
    public void run() {

        try {
            this.iHttpRequest.execute();
        }catch (Exception e) {
            // 捕捉到异常,然后将请求线程丢入到重试机制中
            ThreadManager.getInstance().addFailedTask(this);
        }
        this.iHttpRequest.execute();
    }

(2)不能使用之前的线程池来装,要使用DelayQueue,为什么呢?因为,这么队列,有延迟时间,也就是隔一段时间,我们去调用。所以使用这个队列来装之前失败的任务。
(3)让我们的HttpTask实现我们Delay方法,实现延迟时间方法,即可加入到DelayQueue failedQueue队列中。
(4)使用offer方法将失败的请求放入到队列中。

/**
     * 将失败的请求线程加入到重试队列中
     * @param httpTask
     */
    public void addFailedTask(HttpTask httpTask) {
        if (runnable == null) return;
        failedQueue.offer(httpTask);
    }

(5)注意设置的延迟时间为,当前延迟时间+系统时间

(6)我们还需要一个核心的线程来一直执行失败的队列即可

/**
     * 重试的核心线程
     */
    public Runnable failedRunnable = new Runnable() {
        @Override
        public void run() {
            while (true) {
                try {
                    HttpTask httpTask = failedQueue.take();
                    if (httpTask.getFailNum() < 3) {
                        // 通过线程池执行
                        httpTask.setFailNum(httpTask.getFailNum() + 1);
                        threadPoolExecutor.execute(httpTask);
                        Log.e("请求重试机制==========", "我是次数" + httpTask.getFailNum());
                    } else {
                        JsonHttpRequest iHttpRequest = (JsonHttpRequest) httpTask.getiHttpRequest();
                        iHttpRequest.getCallBackListener().onFailed();
                        // 重试了三次依然失败
                        Log.e("请求重试机制=============", "失败超过3次");
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    };

(7)我们需要设置一个参数,最多失败几次,比如3次
(8)每次拿到任务的时候,判断次数,每执行一次,修改次数,如果小于3次,则执行。

Demo地址:https://github.com/jiayuanfa/JyfNetwork/tree/master/http/src

你可能感兴趣的:(网络框架原理以及手写一个基本的网络框架)