HomeChat 跑起来之后,家里人要记的东西只有一样:
192.168.1.23:8787。后面那个 8787 是唯一一个端口,
而这一个端口上面挤了四件事。
这四件事
- HTTP 页面。手机浏览器打开的登录页、聊天界面,都是这个端口发的。
- WebSocket 聊天。消息是双向的,HTTP 那种一问一答不合适。
- 文件上传。发图片、发语音、发文件都从这里进来。
- UDP 设备发现。服务器往局域网里喊”我在这儿”,手机网页收到就自动填地址。
前三个其实是同一套东西:WebSocket 连接本身就是一个 HTTP 请求升级上去的, 文件上传就是一个普通的 POST。它们本来就是”一个 HTTP 服务”能干的活。 真正另外一套的只有最后那个 UDP。
为什么非要挤在一个口上
不是为了炫技,是因为我家里的情况:
我爸那台电脑上开着防火墙。每多开一个端口,就多一次”这个要不要允许”的弹窗, 他每次都点”取消”。而手机浏览器访问一个地址的时候, 如果网页和 WebSocket 不在同一个端口上,就变成”网页能开、但发不出消息”, 这种问题我自己都要查半天,别说他们了。
所以最后定下来:一个端口,一个地址,防火墙只放一个口。 家里人需要记的东西越少越好。
搞错的地方:TCP 和 UDP 可以同号
一开始我以为 UDP 得用别的端口号,比如 8788, 因为”8787 已经被占了啊”。结果写的时候端口冲突了,查了一下才明白:
TCP 的 8787 和 UDP 的 8787 是两个完全不同的东西, 操作系统里是两张独立的表,同一个数字不会打架。
所以我之前那个”只能选一个”的想法纯属自己想多了。现在两边都是 8787, 启动的时候会打印两行,看着挺整齐:
就绪 192.168.1.23:8787[udp] 设备发现已监听 8787/udp设备发现是怎么”发现”的
局域网里广播一个 UDP 包,是发给整个网段的 ——
255.255.255.255 这个地址谁都收得到,
不需要知道对方 IP。服务器每隔几秒喊一次,网页那边开着就听得到。
听着挺美好,实际上我第一次写完根本没反应。原因是广播包发出去之后,
收的那一端得先”加入”这个广播组、还得允许地址重用,不然第二次启动就报
EADDRINUSE。这两句是后来加上去的。
另外,很多人家里不止一层路由(比如光猫后面又接了一个路由器), 广播过不去,那就还是得手输地址。所以自动发现我一直留着”手动填”的备选, 没敢把它删掉。
一个端口带来的麻烦
好处说完了,说坏处,这个是真的:
一个端口同时管四件事,意味着服务端收到连接之后, 得先判断”这条连接到底想干嘛”。是来要网页的?来升级 WebSocket 的? 来传文件的?这部分的判断代码如果写乱了, 出问题的表现会非常奇怪 —— 比如”网页能打开,但一发图片就断”。
我的做法是:HTTP 那层只管发文件和维护页面, 所有需要”知道对方是谁”的东西全部走 WebSocket。 文件上传虽然走 HTTP,但必须先带着 WebSocket 那边发过来的票据, 没票据的一律拒掉。
还有一件事是后来才补的:网络看门狗。
家里 WiFi 偶尔会断一下,断了之后服务其实还在跑,
但已经跟外面失联了。现在有个 net.js 专门盯着这件事,
发现长时间不通就自己停掉再重来,不然手机那边会一直转圈。
这一段代码在仓库里的
server/src/
下面:http.js、hub.js、discovery.js、net.js。