您的位置:首页 > 理论基础 > 计算机网络

Android妙用SPDY协议提高移动端网络请求性能

2016-02-18 22:36 507 查看
本文旨在提出一种提高移动端网络性能的可行方案。我们知道目前移动端使用的网络请求协议基本上都是http。用的最多的是http/1.1,http/2.0正在逐渐壮大,实际上http/2.0是基于google提出的SPDY协议改进而来。废话不多说,马上进入正题。

关于SPDY协议的详细介绍,请参看 OkHttp完全解析(七)SPDY协议详细介绍

关于OkHttp的使用及源码分析网上相关的资料很多,推荐OkHttp完全解析(八)源码解析一 系列文章。写的很不错。

从速度上来讲,Http/1.1用明文传输,无需加密验证,速度快;SPDY加入了SSL加密握手,但是SPDY协议允许一个TCP连接复用,可以一个域名只提供一个TCP连接即可完成通信,虽然由于TCP连接复用,但是SSL加密握手协商过程又耗费了一定的时间;Http/2.0是Http的加密版本,也是SPDY的升级版本,如果追求高安全性,可以选用Http/2.0,速度上Http/2.0比Http/1.1稍慢。实验证明单纯的将Http/1.1升级到SPDY,速度提升并不明显,原因在于SSL加密层的添加,一定程度上拖慢了SPDY协议的效率。本文在基于OkHttp开源库的基础上,使用SPDY协议,但是强制忽略了SSL握手验证过程,强制使用SPDY/3.1协议,这样就将SPDY的速度优势发挥出来了。Okhttp的源码量比较大,逻辑比较复杂,我费了不少时间,看懂了大致的思路。请在阅读本文之前阅读前面的OkHttp源码系列文章。

针对某个公司的产品,服务器端可以针对域名进行配置SPDY协议的使用。针对自家产品完全可以免去SSL协商过程,直接默认使用SPDY协议。关于服务器怎么配置,在此不过多赘述,自行查阅相关文章。

客户端具体实现过程:

下载okHttp源码并进行修改,只需修改一处即可完成。直接上代码:

Connection.java中的connect()函数:

[code]  //当新建连接或者可用连接无效的时候进入此函数
  void connect(int connectTimeout, int readTimeout, int writeTimeout,Request request, List<ConnectionSpec> connectionSpecs,boolean connectionRetryEnabled) throws RouteException {
    if (connected)
      throw new IllegalStateException("already connected");
    SocketConnector socketConnector = new SocketConnector(this, pool);
    SocketConnector.ConnectedSocket connectedSocket;
    if (route.address.getSslSocketFactory() != null) {
      // https:// communication
      connectedSocket = socketConnector.connectTls(connectTimeout, readTimeout, writeTimeout, request, route, connectionSpecs, connectionRetryEnabled);
    } else {
      // http:// communication.
      if (!connectionSpecs.contains(ConnectionSpec.CLEARTEXT)) {
        throw new RouteException(new UnknownServiceException(
            "CLEARTEXT communication not supported: " + connectionSpecs));
      }
      connectedSocket = socketConnector.connectCleartext(connectTimeout,
          readTimeout, route);
    }

    socket = connectedSocket.socket;
    handshake = connectedSocket.handshake;
    //此处已完成协议的协商,protocol变量的值则决定了下面所走的协议。
    protocol = connectedSocket.alpnProtocol == null ? Protocol.HTTP_1_1
        : connectedSocket.alpnProtocol;
    /***********加入代码**************/
    String hostName = request.httpUrl().host();
    if ("xxx.com".equals(hostName) {
        protocol = Protocol.SPDY_3;
    }
    /***********加入的代码结束**************/
    try {
      if (protocol == Protocol.SPDY_3 || protocol == Protocol.HTTP_2) {
        socket.setSoTimeout(0); // SPDY timeouts are set per-stream.
        spdyConnection = new SpdyConnection.Builder(route.address.uriHost,
            true, socket).protocol(protocol).build();
        spdyConnection.sendConnectionPreface();
      } else {
        httpConnection = new HttpConnection(pool, this, socket);
      }
    } catch (IOException e) {
      throw new RouteException(e);
    }
    connected = true;
  }


由于时间有限,加上第一次正式写博客,很多地方语法用的不是太好,凑合看哈,connect()函数中加入的代码:

[code] String hostName = request.httpUrl().host();
    if ("xxx.com".equals(hostName) {
        protocol = Protocol.SPDY_3;
    }


只是通过很简单的逻辑即可实现,xxx.com代表的是服务器端支持SPDY的域名,此段代码的含义即为强制客户端针对域名选用SPDY协议。值得注意的是,在OkHttp框架中,若为https则默认会进行handshake过程,此时相当于会进行TLS握手过程,此时,这样添加实际上无法提高效率,且既然用了https加密协议,那TLS握手肯定是不能去掉的,否则https变的毫无意义。

具体的实现则需要追踪源码。这里给出简单的说明。Connection.java中的connectAndSetOwner函数是每次请求都会进来的函数,注意此处会进行判断,当第一次建立连接时,isConnected返回false,此时会进入connect函数,建立连接,选择协议,完成连接后,会进行isSpdy判断,如果是Spdy协议,那么会将此connnection放入连接池中进行共享,下次相同域名下的请求到来时,isConnected判断就会为true,则不需要进行重复创建连接,实现了连接的共享。而若是http/1.1,一般不会进行连接共享,每次都需要重建连接,效率较低。

[code]  void connectAndSetOwner(OkHttpClient client, Object owner, Request request)throws RouteException {
    setOwner(owner);
    if (!isConnected()) {
      List<ConnectionSpec>  connectionSpecs  =       route.address.getConnectionSpecs();
      connect(client.getConnectTimeout(), client.getReadTimeout(),
          client.getWriteTimeout(), request, connectionSpecs,
          client.getRetryOnConnectionFailure());
      if (isSpdy()) {
        client.getConnectionPool().share(this);
      }
      client.routeDatabase().connected(getRoute());
    }
    setTimeouts(client.getReadTimeout(), client.getWriteTimeout());
  }


下面给出一个简单的代码进行验证:

[code]public class MainActivity extends Activity {
    private Button myButton;
    private String[] urls = {
            "http://api.caipiao.163.com/clientCommonConfig_getNaviBottomAlert.html?mobileType=android&ver=4.13&channel=netease&apiVer=1.1&apiLevel=19",
            "http://api.caipiao.163.com/lottery_newActivity.html?mobileType=android&ver=4.13&channel=netease&apiVer=1.1&apiLevel=19",
            "http://api.caipiao.163.com/clientHall_hallInfoAll.html?mobileType=android&ver=4.13&channel=netease&apiVer=1.1&apiLevel=19",
            "http://api.caipiao.163.com/getClientNaviLogos.html?mobileType=android&ver=4.13&channel=netease&apiVer=1.1&apiLevel=19",
            "http://api.caipiao.163.com/classifyABTest.html?mobileType=android&ver=4.13&channel=netease&apiVer=1.1&apiLevel=19" };

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        myButton = (Button) findViewById(R.id.myBtn);
        myButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                new Thread() {
                    @Override
                    public void run() {
                        long start = System.currentTimeMillis();
                        for (int i = 0; i < urls.length * 4; i++) {
                            OkHttpClient okHttpClient = new OkHttpClient();
                            Request request = new Request.Builder().url(
                                    urls[i % urls.length]).build();
                            try {
                                Response okHttpResponse = okHttpClient.newCall(
                                        request).execute();
                                Log.i("SpdyTest", "i:" + i + "   protocol:"
                                        + okHttpResponse.protocol().name()
                                        + "code:" + okHttpResponse.code());
                            } catch (IOException e) {
                                e.printStackTrace();
                            }
                        }
                        long end = System.currentTimeMillis();
                        Log.i("aTest", "total time:  " + (end - start) + "ms");

                    }
                }.start();
            }
        });
    }
}


代码很简单,利用已配置好服务器端的url进行测试,每进行20次请求, 进行一次时间计算。

http协议:


Spdy协议:



测试环境为家里wifi信号,几乎同时进行测试,也切换过其他网络,同等环境下,去掉TLS握手的SPDY协议要比Http快大概40%。

当然了对于大多数应用来说,一般情况下不会产生明显的卡顿,但是效果总不怕好嘛,这里只给大家提供一个比较可行的思路而已。

最近在家里闲来无事,在看okhttp的源码,确实学到了很多东西,本来想写几篇源码分析的文章,发现写起来真是繁琐,且网上有很多博客解释的很清晰,这里就不做重复性工作了。

本文提供的思路几乎只用一行代码就能实现,不过很遗憾,okhttp框架并不能直接设置想要使用的协议,没有暴露接口给我们,所以我们只能把源码下载下来,在进行打jar包啦。

这是我第一次写博客,很多地方没注意到,看到的请见谅,关于OkHttp这个开源库,我强烈推荐,亲自实践了它失败重连等功能,有任何相关问题,欢迎留言交流。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: