聊天室模块用到了socket.io
包, 遇到的坑还是挺多的, 特此记录于此.
更新
[2019-7-27]
- Initial release
[2019-7-29]
Changed
- 更新代码格式
[2019-7-30]
Added
- 新增问题
刷新服务器后, socket.io会发送多次数据
- 新增问题
socket.io无法实时接收发送信息, 需要同时刷新服务器和客户端
[2019-8-12]
Added
- 新增问题
客户端的socket监听事件会触发 2^n 次
- 新增问题
在componentDidMount中设置的socket.on监听, 只能作用于一个会话, 因为componentDidMount只执行了一次, 变化的只是传递的路由参数.
[2019-8-19]
Added
- 新增问题
一次单聊会话中, 接收方的所有好友都能收到本次独立会话的消息
[2019-8-31]
Added
- 新增问题
Firefox下报Websocket无法连接的错误
[2019-9-22]
Added
- 新增问题
重复创建了多个重复的websocket连接
[2019-9-25]
Added
- 新增问题
socket.removeAllEventListener引发的惨案
记录
1. Access to XMLHttpRequest at ‘http://localhost:8888/socket.io/?EIO=3&transport=polling&t=MmmlqWJ' from origin ‘http://localhost:3000' has been blocked by CORS policy: The value of the ‘Access-Control-Allow-Origin’ header in the response must not be the wildcard ‘*’ when the request’s credentials mode is ‘include’. The credentials mode of requests initiated by the XMLHttpRequest is controlled by the withCredentials attribute.
相关依赖:
- socket.io@^2.2.0,
- socket.io-client@^2.2.0,
- @types/socket.io@^2.1.2,
- @types/socket.io-client@^1.4.32,
产生原因:
- 原因一
使用koa-cors
库的时候, 如果内部设置了credentials: true
和origin: *
, 则会出现此报错.
- 原因二
server.js
开启服务器并监听端口的方式有误, 如果使用了socket.io
, 会使用另外一种监听方式.
解决方式:
- 解决一
参考思否
的文章: axios的cookie跨域以及相关配置.
展开代码
1 | app.use(require('koa-cors')({ |
- 解决二
参考脚本之家
的相关文章: 详解如何使用koa实现socket.io官网的例子
不使用socket.io
的例子:
展开代码
1 | import * as Koa from 'koa'; |
使用了socket.io
的例子:
展开代码
1 | import * as Koa from 'koa'; |
附上前台的socket.io-client
的连接方式:
展开代码
1 | import * as IO from 'socket.io-client'; |
2. 刷新服务器后, socket.io会发送多次数据(前台会打印多次)
相关依赖:
- socket.io@^2.2.0
- socket.io-client@^2.2.0
- @types/socket.io@^2.1.2
- @types/socket.io-client@^1.4.32
产生原因:
起初是想将socket
有关的逻辑抽离成单独的模块, 所以直接按照如下的方式来使用:
- server.ts
展开代码
1 | import * as IO from 'socket.io'; |
- chatCreate.ts
展开代码
1 | import * as IO from 'socket.io'; |
这样就导致了一个问题, 那就是每次刷新服务器, 都会追加一个新的connection
监听事件, 逐步递增, 所以就出现了每次刷新之后, 请求执行多次的问题.
解决办法:
参考cnode社区的一篇讨论: https://cnodejs.org/topic/518e0a7563e9f8a5427cefa6
很简单, 只需要将io.on('connection', xxx)
提取到server.ts
中就行了:
展开代码
1 | io |
3. socket.io无法实时接收发送信息, 需要同时刷新服务器和客户端
相关依赖:
- socket.io@^2.2.0
- socket.io-client@^2.2.0
- @types/socket.io@^2.1.2
- @types/socket.io-client@^1.4.32
产生原因:
在前台使用socket.io-client
的方式有问题:
展开代码
1 | import * as IO from 'socket.io-client'; |
解决办法:
只需要将初始化新的连接
相关代码, 放入点击事件中即可:
展开代码
1 | import * as IO from 'socket.io-client'; |
4. 客户端的socket
监听事件会触发2^n
次
相关依赖:
- socket.io@^2.2.0
- socket.io-client@^2.2.0
- @types/socket.io@^2.1.2
- @types/socket.io-client@^1.4.32
产生原因:
每一次socket.on
, 都会添加一个新的callback
到事件列表, 进而导致执行多次.
解决办法:
在socket.on
监听之前, 使用socket.removeAllListeners()
移除掉所有的监听事件.
展开代码
1 | React.useEffect(() => { |
5. 在componentDidMount
中设置的socket.on
监听, 只能作用于一个会话, 因为componentDidMount
只执行了一次, 变化的只是传递的路由参数
相关依赖:
- socket.io@^2.2.0
- socket.io-client@^2.2.0
- @types/socket.io@^2.1.2
- @types/socket.io-client@^1.4.32
产生原因:
视图组件(单聊
或群聊
)只挂载了一次, 并且在componentWillUnmount
之时不会卸载, 所以componentDidMount
只执行一次, 进而监听的事件总是第一个聊天视图
.
解决办法:
在React.useEffect
中监听, 即在组件每次更新之后监听即可:
展开代码
1 | React.useEffect(() => { |
6. 一次单聊会话中, 接收方的所有好友都能收到本次独立会话的消息
相关依赖:
- socket.io@^2.2.0
- socket.io-client@^2.2.0
- @types/socket.io@^2.1.2
- @types/socket.io-client@^1.4.32
出现原因:
后端使用了io.emit
, 是向所有连接了(ws://localhost:8888/chat
)的socket发射消息, 所以导致所有的前台用户都能接收到消息
解决方式:
前台根据接收到的消息的chat_id
, 进行过滤, 并追加到自己的消息列表:
展开代码
1 | chatSocket.on('receiveChatSingleMessage', (message: any) => { |
7. Firefox
下报Websocket
无法连接的错误
相关依赖:
- socket.io@^2.2.0
- socket.io-client@^2.2.0
- @types/socket.io@^2.1.2
- @types/socket.io-client@^1.4.32
出现原因:
火狐处于自身的安全策略, 对Websocket
的连接较为严格, 所以会导致其出现下面的错误:
展开代码
1 | Firefox 无法建立到 ws://localhost:8888/socket.io/?EIO=3&transport=websocket&sid=XPfob2_McBjC9WniAAEl 服务器的连接。 |
解决方式:
- 首先按照网上的步骤来, 通过火狐的
about:config
页, 修改默认的设置 - 接着需要在代码上修改, 由于我在组件
componentWillUnmount
之时, 手动关闭了socket
连接, 所以导致了火狐报错, 但是Chrome
和IE
显示正常, 很奇怪. 如果想正常使用, 将componentWillUnmount
的代码删掉即可.
8. 重复创建了多个重复的websocket
连接
相关依赖:
- socket.io@^2.2.0
- socket.io-client@^2.2.0
- @types/socket.io@^2.1.2
- @types/socket.io-client@^1.4.32
问题描述:
之前一直没注意, 直到今天看了下Chrome
控制台有关Ws
的内容, 吓了一跳, 每一次刷新页面, 都会重复地创建多个websocket
连接, 赶紧看了下文档:
正常情况下, 只会创建一个连接, 可以通过
forceNew
配置项来强制创建新的连接.
出现原因:
看了下代码, 问题就出在前端代码的连接部分, 我在每个组件的state
中都初始化了一个Socket
, 详情可以看下: commit@6fe6437e146a472dcf398ecc4b919df1fe692b5c
导致每次组件componentDidMount
都会初始化一个新的链接
, 可以参考官方文档:
https://socket.io/docs/client-api/#io-url-options
文档有一句话说的很清楚:
Note: reusing the same namespace will also create two connections
解决方式:
很简单, 将所有的创建socket
相关的逻辑放置于单个文件, 使用时引入即可, 避免创建多个.
详情可查看: https://github.com/ddzy/ts-web-diary/blob/master/src/services/websocket.tsx
9. socket.removeAllEventListener
引发的惨案
相关依赖:
- socket.io@^2.2.0
- socket.io-client@^2.2.0
- @types/socket.io@^2.1.2
- @types/socket.io-client@^1.4.32
问题描述:
今天调试聊天室
功能的时候, 发现一个问题: 发送单聊消息的时候, 聊天历史列表不能同步更新
出现原因:
细心排查了一下, 发现了问题所在:
展开代码
1 | React.useEffect(() => { |
问题就处在chatIOClient.removeEventListener
这句代码上, 由于每次接收
或者发送
一个单聊消息, 都会监听一个新的事件回调
, 所以我在每次监听之前, 先清除掉所有旧的处理器. 而由于聊天历史
列表用的是同一个Socket
连接, 所以就导致了其中监听的事件被一并清除.
解决方式:
使用removeEventListener()
代替, 只注销掉当前的事件监听器即可.
修改后的代码:
展开代码
1 | function _setSingleChatMessageInfo() { |