V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
novato
V2EX  ›  分享创造

写了一个服务框架,让大家可发布自己的网站到安卓手机

  •  
  •   novato · 2019-11-12 16:50:37 +08:00 · 1503 次点击
    这是一个创建于 1837 天前的主题,其中的信息可能已经有所发展或是发生改变。

    之前写的这个安卓文件+代理服务器界面太弱,我想干嘛不让别人自己做网站传上去?所以就加了这个功能,可以把整个网站打包成 zip 文件(这个 zip 里有个 index.html ),通过“上传网站”页面传到 Android 手机上,后台会自动解压,这样别人就能访问你的网站了。但是只传个静态网站上去没意思,一般做网站都是有前 /后台的,后台与数据库交互,前台通过 ajax 或 websocket 与后台交互,实际上就是间接与数据库交互。我直接提供存取数据库的接口出来,让前端 post SQL 语句到后台去执行,后台用的是 sqlite,只要 sqlite 支持的语句都行,但起码也要加个验证吧,不然不是谁都可以改你的数据库了?我想了一下,分两级密码,一个给客户端网页用的,只能查询(除了 user 表),就是只能执行 select 语句,另一个是管理员密码,可以执行任何 sql 语句,包括建 /删表,增删改记录。玩过类似 wordpress 这样的 cms 都知道,它有个管理后台,登录进去后可以执行管理操作,实际就是改数据库内容,改完后前端页面呈现的就是查询结果。所以我又加了个登录接口,post 登录用户名 /密码过去,登录成功后返回管理员密码,就相当于进入管理后台了,随便你怎么操作数据库。
    我把这些密码和登录账号存在一个 user 表里面,表结构如下:

    -----------------------
    |admin|client|usr|pass|     ----字段名
    -----------------------
    |root|guest|mystore|letmein|----初始默认值
    

    所以程序装好后,先用 postman 之类的把这些默认值改了,然后做自己的网站时就用修改后的密码存取数据库。
    后台提供的接口类似这样

    请求 /返回的数据都是 json 格式,用 post 方式调用
    ①:执行 sql 语句接口
    请求格式:pass 为数据库访问密码,sql 为 sqlite 支持的所有 sql 语句
    URL: http://手机 ip:端口(默认 57001 )/sql。如果在站点页面中访问,请用相对地址:/sql
    { 
      "pass": "root",
      "sql": "update user set admin='my-password';"
    }
    返回格式:ret 为 0 代表执行成功,-1 代表失败,同时带回 msg 字段标示失败原因, 
    如果 sql 为 select 语句,result 表示返回的查询结果
    {
      ret: 0,
      msg: "错误描述,失败时返回",
      result: [{"字段 1":value1, "字段 2":value2, ...}, ...]
    }
    ②:登录接口
    URL: http://手机 ip:端口(默认 57001 )/login。相对地址:/login
    { 
      "usr": "mystore",
      "pass": "letmein"
    }
    返回格式:ret 为 0 代表登录成功,-1 代表失败,同时带回 msg 字段标示失败原因, 
    如果成功,返回的 admin 字段带回管理员密码,其后用这个密码可进行任何数据库操作
    {
      ret: 0,
      msg: "错误描述,失败时返回",
      admin: "管理员密码"
    }
    
    提示:
    这个接口已开启了 cors,可在其它站点的 js 中调用,不存在跨域问题。所以也可把手机当做数据库服务器使用。
    如果误操作把 user 表删除了,或修改后忘记密码,那就只能重装软件才能使用数据库了。
    

    前端界面 SPA (一般用 vue or react 做)和数据库访问都有了,那么一个网站还差什么,对了,还差实时交互,这就要用到 websocket 了,因为要让别人可以做实时聊天或在线游戏网站呀。
    就像上次用 unity3d 写的webgl 手势游戏网站,就可以做成一个多人实时在线交互的网站。因此我又提供了一个 websocket 广播接口
    这样调用

    websocket 地址为:ws://手机 ip:端口(默认 57001 )/broadcast
    // 在网站中建立 websocket 的示例代码:
    const url = `ws://${location.host}/broadcast`;
    const ws = new WebSocket(url);
    ws.onmessage = (evt)=>{
      // 接收其它浏览器发送的广播数据
      const msg = evt.data;
    }
    ws.onopen = ()=>{
      console.log(`ws.onopen`)
      const msg = "hello everyone";
      // 这个消息会广播至所有 websocket 客户端
      ws.send(msg);
    };
    //或者角色施放某个技能时,调用 ws.send(msg)广播到其它所有网页同步播放动画
    

    一个网站所需的基本接口具备了,下面来测一下服务器性能,祭出 wrk:

    测试环境,服务端跑在一加 5t 手机上,测试机用一台 Linux,都是通过 wifi 连同一个路由器

    先是一个简单的 hello world 页面测一下 get 请求

    <!-- get index.html -->
    wrk -t10 -c100 -d30s  http://192.168.1.96:57001/
    Running 30s test @ http://192.168.1.96:57001/
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     8.30ms    2.83ms  87.65ms   86.40%
        Req/Sec     1.22k   113.53     1.46k    81.23%
      365176 requests in 30.03s, 40.05MB read
    Requests/sec:  12162.22
    Transfer/sec:      1.33MB
    

    再测一下读写数据库接口
    先建一张 product 表吧,用 postman 或 jquery 的 ajax 执行下面语句

    create table if not exists product (
        _id integer primary key autoincrement not null,
        name text,
        price real default 0.0,
        desc text default '',
        inventory INTEGER default 0
    );
    

    然后用 wrk 执行插入记录测试

    <!-- {"pass": "root", "sql":"insert into product (name) values ('iphone7');"} -->
    wrk -t10 -c100 -d30s --timeout 5s -s ./post.lua http://192.168.1.96:57001/sql
    Running 30s test @ http://192.168.1.96:57001/sql
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency   911.85ms  378.13ms   4.82s    81.03%
        Req/Sec    11.48      7.30    70.00     67.91%
      2414 requests in 30.06s, 275.82KB read
      Socket errors: connect 0, read 0, write 0, timeout 55
    Requests/sec:     80.31
    Transfer/sec:      9.18KB
    

    插入记录的 rps 才 80,应该是手机的 sd 卡写入速度太慢,现在表里有 2500 条记录了,再测下查询

    <!-- {"pass": "guest", "sql":"select * from product where _id=79;"}-->
    <!-- app run foreground -->
    wrk -t10 -c100 -d30s --timeout 5s -s ./post.lua http://192.168.1.96:57001/sql
    Running 30s test @ http://192.168.1.96:57001/sql
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    12.34ms    6.26ms 190.62ms   96.82%
        Req/Sec   831.85     83.18     1.01k    76.00%
      248092 requests in 30.03s, 30.76MB read
    Requests/sec:   8260.65
    Transfer/sec:      1.02MB
    

    这个数据还不错,再测下修改记录

    <!-- {"pass": "root", "sql":"update product set price=79.0,inventory=2018 where _id=79;"} -->
    wrk -t10 -c100 -d30s --timeout 5s -s ./post.lua http://192.168.1.96:57001/sql
    <!-- app run foreground -->
    Running 30s test @ http://192.168.1.96:57001/sql
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    13.46ms    5.01ms 113.13ms   87.86%
        Req/Sec   755.64    112.81     0.95k    75.02%
      225685 requests in 30.03s, 25.18MB read
    Requests/sec:   7515.30
    Transfer/sec:    858.68KB
    

    这里需要说明一下,上面两项数据都是 app 运行在“前台”时的效果,如果在手机上按 home 键,把 app 切换到后台运行,那 rps 只有 1500 左右了。可能手机系统对后台进程做了什么节能处理。
    另外需要说明的是,每个 sql 执行前都会先验证密码,就是先查 user 表里的密码是否匹配,再执行 sql。相当于每个请求执行了两次 sql 操作。
    那我故意输错密码,让它只查一次数据库看看

    <!-- wrong pass -->
    {"pass": "admin1", "sql":"insert into user (pass) values ('novice')"}
    wrk -t10 -c100 -d30s -s ./post.lua http://192.168.1.96:57001/sql
    Running 30s test @ http://192.168.1.96:57001/sql
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    10.83ms    4.48ms 227.74ms   86.37%
        Req/Sec     0.94k   164.75     1.23k    73.30%
      281577 requests in 30.03s, 37.59MB read
    Requests/sec:   9375.64
    Transfer/sec:      1.25MB
    

    上面这些数据到底是什么水平?再跟 PC 上的服务比较一下吧。我找了台 PC 通过 wifi 连同一台路由器,与手机同样的距离。
    run 起一个 nginx 官方的 docker,就比较 get 请求吧,懒得再去写个读写数据库的服务了。

    <!-- docker nginx hello world -->
    <!-- worker_processes 1 ; worker_connections 1024 -->
    <!-- cpu i7-7700HQ 2.8GHz, 4 核,单核双线程,8 个虚拟 cpu,16G 内存 -->
    wrk -t10 -c100 -d30s  http://192.168.1.46:8080/
    Running 30s test @ http://192.168.1.46:8080/
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency   130.61ms  134.25ms   2.00s    96.38%
        Req/Sec    89.00     31.79   454.00     83.71%
      25386 requests in 30.04s, 6.22MB read
      Socket errors: connect 0, read 14, write 0, timeout 40
    Requests/sec:    844.95
    Transfer/sec:    212.04KB
    

    让我吓了一跳,才 800 多 rps,我进 docker 去看一下 nginx 配置:单进程,工作连接数 1024。这个配置低了。我把它改为 8 进程,4096 连接数

    <!-- worker_processes 8 ; worker_connections 4096 -->
    Running 30s test @ http://192.168.1.46:8080/
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    84.82ms   27.78ms 389.33ms   80.86%
        Req/Sec   118.44     22.21   280.00     74.40%
      35257 requests in 30.04s, 8.64MB read
      Socket errors: connect 0, read 23, write 0, timeout 0
    Requests/sec:   1173.56
    Transfer/sec:    294.51KB
    

    怎么搞的,才一千多 rps。对了,我想 windows 下的 docker 是在虚拟机里运行,应该找一个 windows 版的 nginx 测试。然后我去官网下了个 windows 版的 nginx,把配置改为 8 进程,4096

    <!-- windows native nginx -->
    <!-- worker_processes 8 ; worker_connections 4096 -->
    wrk -t10 -c100 -d30s  http://192.168.1.46
    Running 30s test @ http://192.168.1.46
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    39.75ms   65.90ms   1.36s    98.12%
        Req/Sec   303.24     56.57     1.26k    83.95%
      89639 requests in 30.04s, 21.97MB read
    Requests/sec:   2983.50
    Transfer/sec:    748.65KB
    

    这个数据虽高了一点,还是不行啊。这就是号称高性能的 nginx ?对了,应该是 nginx 不适合在 windows 下运行。还是换 nodejs 测试吧
    我用 npm install http-server -g,装了个 nodejs http 服务应用,在一个有 index.html 的目录下 run 起这个服务

    <!-- nodejs http-server 1 thread -->
    wrk -t10 -c100 -d30s  http://192.168.1.46:3000/
    Running 30s test @ http://192.168.1.46:3000/
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency    74.71ms   12.31ms 342.51ms   90.75%
        Req/Sec   135.01     40.17   212.00     72.61%
      40230 requests in 30.04s, 12.09MB read
    Requests/sec:   1339.05
    Transfer/sec:    411.91KB
    

    这不行啊,应该是这个模块太老了。我就不信邪了,自己写一段 nodejs 代码测试

    <!-- 自己写的 nodejs app -->
    wrk -t10 -c100 -d30s  http://192.168.1.46:3000/
    Running 30s test @ http://192.168.1.46:3000/
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     8.96ms    4.89ms 220.92ms   96.27%
        Req/Sec     1.14k   105.76     1.35k    78.47%
      339952 requests in 30.03s, 47.01MB read
    Requests/sec:  11320.87
    Transfer/sec:      1.57MB
    

    唉,这个数据终于正常了,但这只是单进程啊。用 cluster 模式运行试下,把 cpu 全部利用上。

    <!-- nodejs cluster 9 进程,1 master+8 slave 直接返回 hello world-->
    wrk -t10 -c100 -d30s  http://192.168.1.46:3000/
    Running 30s test @ http://192.168.1.46:3000/
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     7.58ms    2.58ms  61.14ms   88.54%
        Req/Sec     1.34k   111.05     1.57k    77.80%
      399901 requests in 30.03s, 53.01MB read
    Requests/sec:  13318.53
    Transfer/sec:      1.77MB
    

    cluster 模式运行,cpu 基本上全程跑满。好了,这个数据终于超出我的一加 5t 手机了,不然会让人感觉不科学。
    等等,上面那个数据是直接返回内存中的“hello world”,而我手机上是读取 sd 卡 html 文件里的内容再返回的。它这还没算上读取文件的损耗呢。把 nodejs 也改为读取文件试试

    <!-- nodejs cluster 9 进程,1 master+8 slave 读取硬盘上的 html 文件返回 hello world-->
    Running 30s test @ http://192.168.1.46:3000/
      10 threads and 100 connections
      Thread Stats   Avg      Stdev     Max   +/- Stdev
        Latency     9.39ms   13.01ms 267.41ms   98.77%
        Req/Sec     1.21k   138.65     1.43k    88.66%
      360032 requests in 30.03s, 48.07MB read
    Requests/sec:  11989.87
    Transfer/sec:      1.60MB
    

    以上就是

    高通骁龙 835+6G RAM 的一加 5t vs i7-7700HQ + 16G RAM + 全固态硬盘 的 联想拯救者 r720

    火力全开下的数据对比。不过一加 5t 有时会遇到打开网站很慢,不知道是我压的太猛,还是它那个 sd 卡或系统本身的 bug。重启手机就好了。

    之前这篇帖子里也有个测试数据,但那是单线程服务器。现在这个用了多线程。开了 6 个监听端口( 5 个 tcp,一个 udp ),udp 是跟 signaling server 发心跳用的,一个本地 socks5 代理端口,一个本地文件服务 http 端口,一个本地用户网站端口,一个远程 socks5 端口,一个远程用户网站端口。用户网站用了 N 个线程( N 根据 cpu 个数而定),外加一个 socks5 代理线程,和文件 http 服务线程(这个线程开了 3 个端口),所以总共是 N+2 个 c++线程,还不算 java 端和 webview 的线程。


    好了,上面的开胃菜已经上完了。下面我想说的是,难道这个网站只能在局域网内访问吗?
    实际上它可以 http 协议走 webrtc 通道,被远程的玩家穿透访问。原理就跟我在上一篇文章里画的示意图是一样的。
    更有意思的是,如果另一个远程局域网内的玩家穿透进来访问你的网站,它那个局域网内的所有其它设备都可以通过它的手机访问你的网站,而且在一个网页里发 websocket 广播,会广播到不同局域网内的所有浏览器,其它内网的用户也同样可以读取你手机上的数据库内容。

    最后,这个 app 为什么这么大?

    这个 app 多大呢? arm 32 位的 5.8M ,64 位的 6.9M 。

    1. 因为加了 linux 的 magic.mgc 进去以识别文件类型,就是它不是根据后缀名来判断类型的。比如你把图片文件名的.jpg 改为.mp3 ,然后在管理界面点“图片”过滤,它还是会显示这个文件出来。
    2. 因为加了 google 的 admob SDK 进去,打算赚点广告费。如果只是用作代理,或跟别人聊天,查看其它人网站,不会弹出任何广告。只有上传的文件在 mystore 目录下存在超过一小时,才会显示个 banner 广告。除此之外没其它任何乱七八糟的东西。

    目前在家待业,如果有“不重形式、重实效、有活力”的公司招聘远程前端开发( vue/react ),我认为可以胜任。
    这是 [我的求职帖] 。之前说的太泛,还是 具体、简单 点好。

    armeabi-v7a App 下载( 5.8M )

    arm64-v8a App 下载( 6.9M )

    10 条回复    2019-11-15 07:54:34 +08:00
    crs0910
        1
    crs0910  
       2019-11-13 00:31:57 +08:00 via iPhone
    老哥真棒
    xiaotuzi
        2
    xiaotuzi  
       2019-11-13 07:52:10 +08:00 via iPhone
    webrtc 的实例,这个才是关键吧。
    star7th
        3
    star7th  
       2019-11-13 16:41:23 +08:00
    作为技术练习是没问题的,只是应用场景受限。除了玩一下外,没什么人真的在安卓手机上跑一个网站吧。现在随着公有云 /树莓派等的流行,web 服务已经很廉价且性能更好。何必去用一个性能低下的各方面续航等也受限制的服务器。
    novato
        4
    novato  
    OP
       2019-11-13 17:48:45 +08:00
    @star7th 基本上没多少人会带树莓派到处走,而且就算带了 pi 还要带鼠标、显示器,而且还没有续航( pi 有电池吗),或者是另加个一个笔记本。公有云是便宜,但是管理云服务器还是要带个 laptop。而且不论带什么别的还是会带个手机。那何不手机对手机直接扫码传东西呢,还可以玩远程穿透。
    novato
        5
    novato  
    OP
       2019-11-13 18:36:50 +08:00
    @star7th 而且这个手机性能测试数据不低啊,我刚测了一台附近的云服务器 32 核 16G (网络延迟 7ms 左右),ubuntu14.04+nginx(1.9.15),就返回静态页面 hello world,5700 左右的 rps,貌似比一加 5t 1w2 的 rps 还差一大截。
    star7th
        6
    star7th  
       2019-11-13 18:52:16 +08:00
    @novato
    “那何不手机对手机直接扫码传东西呢,还可以玩远程穿透” ——有需要这么做的人一般将之做成 app 或者是混合 app。实际上现在确实也有类似的打包工具。
    客户端干客户端的活,手机作为客户端是极为优秀的。当服务器不行。我看不到把服务器放到手机上比放到公有云上有明显的好处。维护的时间精力成本 /运行的稳定性,肯定是比不过的。
    narmgalaxy
        7
    narmgalaxy  
       2019-11-13 19:32:16 +08:00
    老哥的这份爱折腾的心才是最重要的。
    robinchina
        8
    robinchina  
       2019-11-14 10:57:58 +08:00
    放手机里的网站。。。。反审查?
    novato
        9
    novato  
    OP
       2019-11-14 15:11:19 +08:00
    @robinchina 如果你打算被审查也可以不放手机里,如果想被过滤也可以不穿透。关键不在工具本身,就看你怎么用。就像上次我说的,也可以穿透进公司内网远程维护服务器用,这个很正当啊。
    rykka
        10
    rykka  
       2019-11-15 07:54:34 +08:00 via Android
    优秀
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   2781 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 13:06 · PVG 21:06 · LAX 05:06 · JFK 08:06
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.