写了一个服务框架,让大家可发布自己的网站到安卓手机 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
爱意满满的作品展示区。
novato
V2EX    分享创造

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

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

    之前写的这个安卓文件+代理服务器界面太弱,我想干嘛不让别人自己做网站传上去?所以就加了这个功能,可以把整个网站打包成 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 htp-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     Solana     5738 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 28ms UTC 01:54 PVG 09:54 LAX 17:54 JFK 20:54
    Do have faith in what you're doing.
    ubao msn snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86