OkHttp网络请求分析

http请求.gif

http请求如上所示:一个请求对应一个响应,一来一回。是一个上层协议,隐藏了许多细节。下面大概流程为:

  1. 地址解析 : 解析出协议,端口,主机IP
  2. 封装HTTP请求数据包
  3. 封装成TCP包,建立TCP连接(tcp三次握手)
  4. 客户机发送请求命令
  5. 服务器响应
  6. 服务器关闭TCP连接
    头信息加入了这行代码 Connection:keep-alive TCP连接在发送后将仍然保持打开状态,于是,浏览器可以继续通过相同的连接发送请求。保持连接节省了为每个请求建立新连接所需的时间,还节约了网络带宽。

注意:http协议中基于tcp/ip

OkHttp对以上步骤的实现解析

okhttp2.gif
  • RetryAndFollowUpInterceptor创建StreamAllocation对象,处理http的重定向及出错重试。对后续Interceptor的执行的影响为修改Request并创建StreamAllocation对象。

  • BridgeInterceptor补全缺失的一些http header。对后续Interceptor的执行的影响主要为修改了Request。Connection:keep-alive这里被默认添加

  • CacheInterceptor处理http缓存。对后续Interceptor的执行的影响为,若缓存中有所需请求的响应,则后续Interceptor不再执行。

  • ConnectInterceptor借助于前面分配的StreamAllocation对象建立与服务器之间的连接,并选定交互所用的协议是HTTP 1.1还是HTTP 2。对后续Interceptor的执行的影响为,创建了HttpStream和connection。

  • CallServerInterceptor作为Interceptor链中的最后一个Interceptor,用于处理IO,与服务器进行数据交换。
    https://www.jianshu.com/p/5c98999bc34f

参考:
OkHttp3中的代理与路由
OkHttp3 HTTP请求执行流程分析
Http工作原理

使用方法

//1.初始化OKHttp
OkHttpClient okHttpClient = new OkHttpClient()
                .newBuilder()
                .build();
//2.构建request
        final Request request = new Request.Builder()
                .get()
                .url(HttpUrl.parse("http://www.baidu.com/"))
                .build();
//3.发送请求(这里并没有执行,只是构建了`RealCall`对象)
        Call call = okHttpClient.newCall(request);
//4.接收响应(请求执行,并得到响应结果)
        Response execute = call.execute();
//5.输出
        System.out.println(execute.body().string());

分析

着重分析步骤3与4,步骤3是为了返回RealCall对象,步骤4则执行的是RealCall.execute()

RealCall.execute
@Override public Response execute() throws IOException {
    synchronized (this) {
      if (executed) throw new IllegalStateException("Already Executed");
      executed = true;
    }
    captureCallStackTrace();
    try {
      client.dispatcher().executed(this);
      Response result = getResponseWithInterceptorChain();
      if (result == null) throw new IOException("Canceled");
      return result;
    } finally {
      client.dispatcher().finished(this);
    }
  }
RealCall.getResponseWithInterceptorChain
Response getResponseWithInterceptorChain() throws IOException {
    // Build a full stack of interceptors.
    List<Interceptor> interceptors = new ArrayList<>();
    interceptors.addAll(client.interceptors());
    interceptors.add(retryAndFollowUpInterceptor);
    interceptors.add(new BridgeInterceptor(client.cookieJar()));
    interceptors.add(new CacheInterceptor(client.internalCache()));
    interceptors.add(new ConnectInterceptor(client));
    if (!forWebSocket) {
      interceptors.addAll(client.networkInterceptors());
    }
    interceptors.add(new CallServerInterceptor(forWebSocket));

    Interceptor.Chain chain = new RealInterceptorChain(
        interceptors, null, null, null, 0, originalRequest);
    return chain.proceed(originalRequest);
  }

.okhttp 地址解析

//解析得到host  ---  BridgeInterceptor
requestBuilder.header("Host", hostHeader(userRequest.url(), false));
//解析得到主机ip ---  StreamAllocation#findConnection
1. StreamAllocation#newStream()
RealConnection resultConnection = findHealthyConnection(connectTimeout, readTimeout,
          writeTimeout, pingIntervalMillis, connectionRetryEnabled, doExtensiveHealthChecks);

2.StreamAllocation#findHealthyConnection()
 RealConnection candidate = findConnection(connectTimeout, readTimeout, writeTimeout,
          pingIntervalMillis, connectionRetryEnabled);

3.StreamAllocation#findConnection()
 if (selectedRoute == null && (routeSelection == null || !routeSelection.hasNext())) {
      newRouteSelection = true;
      routeSelection = routeSelector.next();
    }
4.RouteSelector#next()
public Selection next() throws IOException {
...
      Proxy proxy = nextProxy();
...
    return new Selection(routes);
  }

5.RouteSelector#nextProxy()
private Proxy nextProxy() throws IOException {
    if (!hasNextProxy()) {
      throw new SocketException("No route to " + address.url().host()
          + "; exhausted proxy configurations: " + proxies);
    }
    Proxy result = proxies.get(nextProxyIndex++);
    resetNextInetSocketAddress(result);
    return result;
  }

6.RouteSelector#resetNextInetSocketAddress()
socketHost = getHostString(proxySocketAddress);

与服务器tcp连接

ConnectInterceptor#intercept

HttpCodec httpCodec = streamAllocation.newStream(client, chain, doExtensiveHealthChecks);

findHealthyConnection -> findConnection -> RealConnection#connect -> connectSocket ->

private void connectSocket(int connectTimeout, int readTimeout, Call call,
      EventListener eventListener) throws IOException {
    Proxy proxy = route.proxy();
    Address address = route.address();

    rawSocket = proxy.type() == Proxy.Type.DIRECT || proxy.type() == Proxy.Type.HTTP
        ? address.socketFactory().createSocket()
        : new Socket(proxy);

    eventListener.connectStart(call, route.socketAddress(), proxy);
    rawSocket.setSoTimeout(readTimeout);
    try {
      Platform.get().connectSocket(rawSocket, route.socketAddress(), connectTimeout);
    } catch (ConnectException e) {
      ConnectException ce = new ConnectException("Failed to connect to " + route.socketAddress());
      ce.initCause(e);
      throw ce;
    }

设置okhttp的超时,这里就可以看得很明白了,其实是设置socket的连接超时与读写超时
.connectTimeout(30*1000, TimeUnit.MILLISECONDS) .readTimeout(30*1000,TimeUnit.MILLISECONDS) .writeTimeout(30*1000,TimeUnit.MILLISECONDS)

发送请求命令

CallServerInterceptor#intercept 这是okhttp中的最后一个入拦截器,主要作用是 与服务器进行数据交换

httpCodec.writeRequestHeaders(request);
Http1Codec#writeRequestHeaders

  public void writeRequest(Headers headers, String requestLine) throws IOException {
    if (state != STATE_IDLE) throw new IllegalStateException("state: " + state);
    sink.writeUtf8(requestLine).writeUtf8("\r\n");
    for (int i = 0, size = headers.size(); i < size; i++) {
      sink.writeUtf8(headers.name(i))
          .writeUtf8(": ")
          .writeUtf8(headers.value(i))
          .writeUtf8("\r\n");
    }
    sink.writeUtf8("\r\n");
    state = STATE_OPEN_REQUEST_BODY;
  }

这里的IO操作都是OKIO实现

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,884评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,347评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,435评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,509评论 1 284
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,611评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,837评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,987评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,730评论 0 267
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,194评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,525评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,664评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,334评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,944评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,764评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,997评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,389评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,554评论 2 349

推荐阅读更多精彩内容

  • 简介 目前在HTTP协议请求库中,OKHttp应当是非常火的,使用也非常的简单。网上有很多文章写了关于OkHttp...
    第八区阅读 1,374评论 1 5
  • 一.网络通信概念理解 1.http协议概述 当我们在自己电脑的web浏览器地址栏敲入网址url,点击enter,...
    铜雀春深锁不住阅读 4,912评论 0 3
  • OkHttp解析系列 OkHttp解析(一)从用法看清原理OkHttp解析(二)网络连接OkHttp解析(三)关于...
    Hohohong阅读 20,971评论 4 58
  • 周六加班,此时的办公室里只有我一个人,虽然不提倡加班,但是负责的工作还是要按时完成,不要落下进度,突然间想起来昨天...
    3e81098bf2ef阅读 182评论 0 0
  • 女人,你为什么要做微商?你知道为什么吗? 栏目:行业动态2016-09-05 10:09:51 微商就是:“就算有...
    异葩阅读 527评论 0 0