论一个合格的车载手机支架的自我修养之什么是最好的车载手机支架

作为一个酷爱驾驶的人和一名手机党,给座驾安装一个手机支架是非常重要的事情。然而,要寻找一个还用的车载手机支架真的是一件比想象要困难的多的事情。

手机支架自己买过很多了。但是都在使用一段时间后(甚至体验几分钟后)发现不能接受的设计问题而丢垃圾桶。

大概5年前,购买了第一个手机支架,他大概长这样(姑且称之为第一代车载手机支架吧):

这个手机架使用上没有什么太大问题,功能上一直表现不错,因此使用了挺长一段时间。但是,后来换的手机屏幕更大了,继续使用这个手机支架的话会使得手机上沿会挡住汽车自己的一部分显示屏。此外,这个手机支架下端只依靠一个塑料架作为之巅,把出风口的下方已经磕出了历史的痕迹?

于是换了当时最流行的磁吸式车载手机支架:

这种支架需要在手机或者手机壳上安装一个贴片(也叫做引磁片),然后往空调出风口上的支架一放就可以靠磁力吸住。应该说,使用传统的支架换到这个磁吸支架的头两天,心里还是默默为发明这种之家的人点赞的。

它的支架结构非常简单,在车里非常简洁,而且调整引磁片的位置是的手机固定在一个自己最喜欢的位置。但是,这种简洁却是以牺牲手机的外观为代价的。且不说很多引磁片上面有品牌logo, 且logo极丑,关键是无论多么薄的引磁片贴在手机上以后都让以前光滑平整的手机背面不在平整,非常变扭。

另一方面,在使用一段时间以后,发现指南针和陀螺仪都不再准确,不过导航似乎一直都还正常。推测是这两个部件因为长期使用这种强磁设备影响已经被磁化。作为一个相信科学的人居然在这里翻车,想来也是挺自嘲的一件事情。

然而,事实是「磁吸式手机之间是否影响手机」这个事情目前都没有定论。我在购买前也担心过这个事情。专门在蛤乎还转了一圈,我估计大家找到的大概也是这个答案:

然是有影响的,表面磁力超过一定范围.现在市面上的手机基本上都用到了磁铁,其内部隔磁都做到了200GS以下,越好的手机内部隔磁越好.

所以,对于手机支架,表面磁力(接触手机的部分)超过200GS就会对手机产生影响,甚至损害.就现在市面上的磁性手机支架而言,都在2000GS以上,是标准值的10倍以上.

这样导致的结果就是干扰手机的电磁信号和磁化手机内部的含铁部件,逐渐导致收集导航的失准和失灵、通话音质的失真、运行卡顿、数据丢失、屏幕花屏或者黑屏、间歇性死机,直至报废。

以上的现象一般会在1到4个月内相继出现,根据手机的不同也会出现不同的情况。这个也有专业的测试机构,但鉴定需要最少一年的时间才能判别对手机的影响到底多大,只有专业的磁铁生产厂家和手机生产厂家能自己用仪器测出具体值。

建议购买磁性支架应选择盖上铁片(贴在手机背面的那片,没有不行)后表面磁力在200GS以下的。

给一个简单的测试方法:在手机支架上盖上铁在手机背面的铁片,把一圆硬币从上面滚落,不会吸住的基本上都只有几百GS,过大的都会吸住硬币,则不建议购买。

但是,请相信我,我也做过这个硬币实验,硬币滚落了,于是我以为自己购买的产品是OK的。但是现实却是打脸的……

我看过很多所谓汽车媒体的评测,结论这种磁铁不影响手机。理由无非亮点:

  1. 厂家使用了多颗磁铁,形成的是闭合磁场,因此厂家肯定是做了充分测试的。
  2. 苹果的smart cover外设也是使用磁铁作为屏幕感应开关的,因此磁铁是不会影响电子设备的。

最开始,我还挺相信上面的结论的。但是,后来一想两个理由都有点滑稽:

  1. 学过初中物理的都应该知道,这个所谓的闭合磁场是限制磁场在一个立体区域,引磁片才一毫米厚,闭合个屁呢。另外,生产这这些支架的厂商都是一些作坊而已,这里根本没有什么信用可以背书。
  2. 苹果的确在smart cover中使用了磁铁,但是你发现那个磁铁在iPad屏幕的最右侧边缘位置了吗?而你的引磁片是贴在近乎手机中心的位置……而长期跟磁场如此近距离接触,含铁的零件被磁化无非是快慢的事罢了

抛弃了磁吸式手机支架后,我有购买了最时尚的重力感应手机支架:

第一眼看到的时候,有没有觉得创意很赞呀?最开始我也是这样认为的。于是我一股脑买了两个。但是……拿到手后就不得不说,这什么鬼呀……

所谓成也重力,败也重力。因为使用重力收紧两边的手臂夹紧手机,因此三个臂都是可以活动的。由于,手机重量一般只有一百多克,因此三个臂基本没有阻尼。这就意味着,整个手机支架非常松垮。尤其是你没有在支架上放手机的时候,开起来的过坑洼路段的时候感觉整个车都要散架一样。

这个结构的手机支架跟品牌没有关系,设计原理导致它的噪音是无解的。淘宝上到处刷评的第一卫一样垃圾,一样有这个问题。

找呀找呀找支架,于是我找到了这个目前为止最满意的车载手机支架

有没有感觉又回到了最古老的那款手机支架的感觉?这就对了。那款支架我使用时间最长,而这款支架把我之前认为的设计缺陷都解决掉了:

  1. 从结构看,它把第一款手机支架的固定方式修改为与磁吸式的固定方式一致,加入了软性塑料的夹子及不会产生异响,同时也不会在支架下部支点位置处留下历史的痕迹。
  2. 将固定手机的方框结构修改为圆形无下部支脚的弹力感应结构,这就解决了老款支架手机上下不可调,导致遮挡车载屏幕的问题。

总结下来就是,它既有普通支架的通用可靠,同时又有磁吸式支架的简洁灵活,并且不需要在手机上安装引磁片,免去磁铁影响手机的担忧。

这款手机支架使用已经有段时间,等过段时间有一次长途体验以后在来盖棺定论。

100行代码实现基于 QUIC 的 http 代理

本站开启支持 QUIC 的方法与配置后,主观感觉从国内访问快了很多。看了一下Chrome的timing, 大部分建立连接都能够做到0-RTT:

既然这样,顺手实现一个基于QUIC的http代理,把平时查资料时使用的网络也顺带加速一下。(对了,前两天看到Google发布了Outline, 看来这项运动从来都不缺少运动员哪……)

http 代理原理

http 代理处理http和https请求的方式有所不同。对于http请求:

  1. 浏览器与代理服务器建立TCP连接后,将http请求发送给代理服务器。
  2. 代理服务器将http请求发送给目标服务器。
  3. 代理服务器获取到相应结果以后,将结果发送给浏览器。

这里有一个细节需要注意,浏览器向代理服务器发送的http请求URI与直接访问有所不同。

浏览器直接访问 GET http://www.yahoo.com 的http请求格式为:

GET / HTTP/1.1
User-Agent: Quic-Proxy
...

而向代理服务器发送的http请求格式为:

GET http://www.yahoo.com HTTP/1.1
User-Agent: Quic-Proxy
...

也就是浏览器想代理服务器发送的http请求URI中包含了scheme和host,目的是为了让代理服务器知道这个代理请求要访问的目标服务器地址。

对于https请求,一般是通过CONNECT建立隧道:

  1. 浏览器向代理服务器建立TCP连接,发送CONNECT请求。
  2. 代理服务器根据CONNECT请求中包含的host信息,向目标服务器建立TCP连接,然后向浏览器返回200连接成功的响应。
  3. 这时代理服务器同时维持着连接浏览器和目标服务器的TCP连接。
  4. 从浏览器的角度看,相当于建立了一条直连目标服务器的TCP隧道。然后直接在该隧道上进行TLS握手,发送http请求即可实现访问目标服务器的目的。

QUIC Proxy的设计与实现

QUIC Proxy 部署结构图

QUIC Proxy的部署结构与上面http代理原理稍微有所不同。主要区别是增加了qpclient。主要原因是应用程序与代理服务器支架的请求是明文传输(http请求代理是全明文,https请求代理时的CONNECT头会泄露目标服务器信息)。我们是要隐私的人(虽然小扎可能并不care),因此,在应用程序与qpserver之间加了一个qpclient,之间使用QUIC作为传输层。

实现

QUIC Proxy使用Go实现,猴急的同学可以直接到github看源码:Quic Proxy, a http/https proxy using QUIC as transport layer.

代码比较简单,基于标准库的http.Server根据http代理的原理进行了一点http请求的修改。然后,因为qpclientqpserver之间使用QUIC作为transport,而QUIC上的每一个connection都是可以多路复用(multiplexing)的,因此,对于qpserver需要自己实现一个传入http.Server的listener:

type QuicListener struct {
    quic.Listener
    chAcceptConn chan *AcceptConn
}

type AcceptConn struct {
    conn net.Conn
    err  error
}

func NewQuicListener(l quic.Listener) *QuicListener {
    ql := &QuicListener{
        Listener:     l,
        chAcceptConn: make(chan *AcceptConn, 1),
    }
    go ql.doAccept()
    return ql
}

func (ql *QuicListener) doAccept() {
    for {
        sess, err := ql.Listener.Accept()
        if err != nil {
            log.Error("accept session failed:%v", err)
            continue
        }
        log.Info("accept a session")

        go func(sess quic.Session) {
            for {
                stream, err := sess.AcceptStream()
                if err != nil {
                    log.Error("accept stream failed:%v", err)
                    sess.Close(err)
                    return
                }
                log.Info("accept stream %v", stream.StreamID())
                ql.chAcceptConn <- &AcceptConn{
                    conn: &QuicStream{sess: sess, Stream: stream},
                    err:  nil,
                }
            }
        }(sess)
    }
}

func (ql *QuicListener) Accept() (net.Conn, error) {
    ac := <-ql.chAcceptConn
    return ac.conn, ac.err
}

同样的,qpclientqpserver建立连接也需要考虑到多路复用的问题,实现实现一个基于QUIC的dialer:

type QuicStream struct {
    sess quic.Session
    quic.Stream
}

func (qs *QuicStream) LocalAddr() net.Addr {
    return qs.sess.LocalAddr()
}

func (qs *QuicStream) RemoteAddr() net.Addr {
    return qs.sess.RemoteAddr()
}

type QuicDialer struct {
    skipCertVerify bool
    sess           quic.Session
    sync.Mutex
}

func NewQuicDialer(skipCertVerify bool) *QuicDialer {
    return &QuicDialer{
        skipCertVerify: skipCertVerify,
    }
}

func (qd *QuicDialer) Dial(network, addr string) (net.Conn, error) {
    qd.Lock()
    defer qd.Unlock()

    if qd.sess == nil {
        sess, err := quic.DialAddr(addr, &tls.Config{InsecureSkipVerify: qd.skipCertVerify}, nil)
        if err != nil {
            log.Error("dial session failed:%v", err)
            return nil, err
        }
        qd.sess = sess
    }

    stream, err := qd.sess.OpenStreamSync()
    if err != nil {
        log.Info("[1/2] open stream from session no success:%v, try to open new session", err)
        qd.sess.Close(err)
        sess, err := quic.DialAddr(addr, &tls.Config{InsecureSkipVerify: true}, nil)
        if err != nil {
            log.Error("[2/2] dial new session failed:%v", err)
            return nil, err
        }
        qd.sess = sess

        stream, err = qd.sess.OpenStreamSync()
        if err != nil {
            log.Error("[2/2] open stream from new session failed:%v", err)
            return nil, err
        }
        log.Info("[2/2] open stream from new session OK")
    }

    log.Info("addr:%s, stream_id:%v", addr, stream.StreamID())
    return &QuicStream{sess: qd.sess, Stream: stream}, nil
}

好吧,我承认实现代码似乎在200行左右……但是,我们实现了一个client和一个server, 平均下来基本控制在100行左右,对吧……(?逃……)

部署

:需要golang版本 >= 1.9

1. 在远程服务器上安装 qpserver

go get -u github.com/liudanking/quic-proxy/qpserver

2. 启动qpserver:

qpserver -v -l :3443 -cert YOUR_CERT_FILA_PATH -key YOUR_KEY_FILE_PATH

3. 在本地安装 qpclient

go get -u github.com/liudanking/quic-proxy/qpclient

4. 启动 qpclient:

qpclient -v -k -proxy http://YOUR_REMOTE_SERVER:3443 -l 127.0.0.1:18080

5. 设置应用程序代理:

以 Chrome with SwitchyOmega 为例:

Enjoy!

本站开启支持 QUIC 的方法与配置

在越来越讲究用户体验的今天,网络带宽的提高已经很难有显著的页面加载改善,而低延迟的优化往往能够起到意想不到的效果。在《TLS1.3/QUIC 是怎样做到 0-RTT 的》中我们分析了TLS1.3和QUIC在低延迟方面的原理和低延迟优势。在从源代码编译 nginx docker 镜像开启 TLS 1.3中我们已经把玩了TLS1.3,没有理由不把玩一下QUIC,对吧?

起初以为,在普及程度上,QUIC因为主要是Google主导,会曲高和寡。但是,查了一下,发现腾讯早在2017年就在生产环境应用了QUIC:让互联网更快的协议,QUIC在腾讯的实践及性能优化. 结果显示:

灰度实验的效果也非常明显,其中 quic 请求的首字节时间 (rspStart) 比 http2 平均减少 326ms, 性能提升约 25%; 这主要得益于 quic 的 0RTT 和 1RTT 握手时间,能够更早的发出请求。

此外 quic 请求发出的时间 (reqStart) 比 h2 平均减少 250ms; 另外 quic 请求页面加载完成的时间 (loadEnd) 平均减少 2s,由于整体页面比较复杂, 很多其它的资源加载阻塞,导致整体加载完成的时间比较长约 9s,性能提升比例约 22%。

既然大厂都已经发车,我司也就可以考虑跟进了。稳妥起见,决定先在自己的博客开启QUIC,然后再逐步在线上业务进行推广。

方案概览

方案非常简单:不支持QUIC的浏览器依旧通过nginx tcp 443访问;支持QUIC的浏览器通过caddy udp 443访问。

由于nginx近期没有支持QUIC的计划, 作为一名gopher, 因此这里选择caddy作为QUIC的反向代理。后面会介绍caddy的具体安装和配置方法。

对于支持QUIC的浏览器来说,第一次访问支持QUIC的网站时,会有一次服务发现的过程。服务发现的流程在QUIC Discovery
有详细介绍。概括来说,主要有以下几步:

  1. 通过TLS/TCP访问网站,浏览器检查网站返回的http header中是否包含alt-svc字段。
  2. 如果响应中含有头部:alt-svc: 'quic=":443"; ma=2592000; v="39"',则表明该网站的UDP 443端口支持QUIC协议,且支持的版本号是draft v39; max-age为2592000秒。
  3. 然后,浏览器会发起QUIC连接,在该连接建立前,http 请求依然通过TLS/TCP发送,一旦QUIC连接建立完成,后续请求则通过QUIC发送。
  4. 当QUIC连接不可用时,浏览器会采取5min, 10min的间隔检查QUIC连接是否可以恢复。如果无法恢复,则自动回落到TLS/TCP。

这里有一个比较坑的地方:对于同一个域名,TLS/TCP和QUIC必须使用相同的端口号才能成功开启QUIC。没有什么特殊的原因,提案里面就是这么写的。具体的讨论可以参见Why MUST a server use the same port for HTTP/QUIC?

从上面QUIC的发现过程可以看出,要在网站开启QUIC,主要涉及两个动作:

  1. 配置nginx, 添加alt-svc头部。
  2. 安装和配置QUIC反向代理服务。

配置nginx, 添加alt-svc头部

一行指令搞定:

安装QUIC反向代理服务器caddy

上面我们提到对于同一个域名,TLS/TCP和QUIC必须使用相同的端口号才能成功开启QUIC。然而,caddy服务器的QUIC特性无法单独开启,必须与TLS一起开启,悲剧的是TLS想要使用的TCP 443端口已经被nginx占用了?

场面虽然有点尴尬,但是我们有docker:将caddy安装到docker中,然后只把本地的UDP 443端口映射到容器中即可。

于是我们创建了一个docker-caddy项目。Dockerfile 10行内搞定:

caddy 服务配置文件/conf/blog.conf:

启动docker:

开启Chrome浏览器QUIC特性

chrome://flags/中找到Experimental QUIC protocol, 设置为Enabled. 重启浏览器生效。

测试QUIC开启状态

重新访问本站https://liudanking.com, 然后在浏览器中打开:chrome://net-internals/#quic。如果你看到了QUIC sessins,则开启成功:

当然,你也可以给Chrome安装一个HTTP/2 and SPDY indicator(An indicator button for HTTP/2, SPDY and QUIC support by each website) 更加直观的观察网站对http/2, QUIC的支持情况。