# chat-room **Repository Path**: furtherbank/chat-room ## Basic Information - **Project Name**: chat-room - **Description**: 一个nodejs聊天室,由于做的时候上头,已经向QQ的方向发展了 - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: master - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2021-12-05 - **Last Updated**: 2022-06-06 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # 通过python的socket标准库进行简单的socket回环通信 ## 信息发送 通过socket进行通信大致经过以下过程: 1. 服务端开启套接字端口(需要指定端口号) 2. 客户端连接服务端(需要提供对方套接字地址)开启的套接字 3. 双方互相连接之后,可以互相发送数据、接收数据 4. 一方断开连接,之后另一方也断开连接 在服务端需要指定服务端端口号,客户端需要指定服务端的套接字地址。这里服务端开启的端口号为8080,客户端连接的套接字为`127.0.0.1:8080`(回环地址) 接下来进行实验: 1. 连接服务端和客户端 ![image-20211122165801133](readme.assets/image-20211122165801133.png) ![image-20211122165726032](readme.assets/image-20211122165726032.png) 2. 客户端发送信息,服务端回复**收到** ![image-20211122165907488](readme.assets/image-20211122165907488.png) ![image-20211122165935653](readme.assets/image-20211122165935653.png) 3. 客户端退出,服务端收到退出信息 ![image-20211122170022298](readme.assets/image-20211122170022298.png) 实验结果符合预期。 ## 文件传输 部分代码参考:[python socket 传输文件 - Lucky& - 博客园 (cnblogs.com)](https://www.cnblogs.com/xiaokang01/p/9069048.html) 通过socket进行文件传输比单纯的发送/接收信息还具有以下特点: - 除文件内容之外,还需发送信息(文件名、文件大小等校验数据) - 最好可以进行文件内容校验(文件大小校验、md5校验) - 发送/接收信息的同时进行读取/写入文件的操作 接下来进行实验: 1. 连接服务器和客户端 ![image-20211122163848178](readme.assets/image-20211122163848178.png) ![image-20211122163904931](readme.assets/image-20211122163904931.png) 2. 服务器发送`TroubleMaker.mp3` ![image-20211122163932290](readme.assets/image-20211122163932290.png) 3. 客户端收到文件,并进行校验 ![image-20211122163955909](readme.assets/image-20211122163955909.png) 实验结果符合预期。 # nodejs聊天室项目 该聊天室项目fork自仓库 [https://gitee.com/wenqiguai/chat_room/tree/master](https://gitee.com/wenqiguai/chat_room/tree/master) ,是一个 nodejs 项目。 该实验以该项目为基础进行了拓展。 ## 安装与使用 首先拷贝代码文件并解压。在进行项目安装之前,请确保设备上安装了nodejs。 接下来通过`npm i`命令安装项目使用的所有依赖,并通过`npm install -g nodemon`安装自动调试工具。 安装完成后,通过`npm run dev`进行启动,然后在浏览器中输入`localhost:3000`访问聊天室。 ## 关于WebSocket和socket.io [**WebSocket**](https://baike.baidu.com/item/WebSocket/1953845?fr=aladdin) 是一种在单个 [TCP](https://baike.baidu.com/item/TCP) 连接上进行 [全双工](https://baike.baidu.com/item/全双工) 通信的协议。它用于客户端浏览器和服务器进行双向通信。在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。 该聊天室系统使用了 [socket.io](https://socket.io/) 库作为socket通信的解决方案。以下是相应的原因: - 前后端配套支持:在浏览器中,只提供了客户端的[**WebSocket API**](https://baike.baidu.com/item/WebSocket/1953845?fr=aladdin),通过WebSocket进行通信还需要在服务端有配套支持。该项目使用nodejs作为后端的解决方案。socket.io在浏览器客户端和nodejs服务端都可以进行通信支持。 - 可靠性:如果浏览器不支持WebSocket,socket.io会使用HTTP长轮询作为通信的替代方案。 - [广播](https://socket.io/docs/v4/broadcasting-events/):可以通过socket.io提供的API快捷的将信息广播到所有client。在项目中,使用了广播来通知成员加入、成员发言、成员离开的消息 ## 业务逻辑流程 用户登录网站后,需要手动输入一个用户名作为聊天室的用户名称。这一步通过表单+url请求实现。 服务器收到用户的login请求后,将用户名存入cookie,下次进入就无需再次输入用户名。 进入聊天界面后,客户端即开始向服务端建立websocket链接,进行具体的聊天室通信逻辑。 通过socket通信的生命周期和聊天室的业务逻辑流程来看,socket通信的具体流程如下: 1. 浏览器客户端向服务端建立WebSocket链接 2. 浏览器客户端向服务端发送加入事件`join`,消息内容为客户端用户名 3. 服务端收到`join`消息后,将广播`join`事件,消息内容为新用户用户名以及在线用户列表 浏览器客户端收到服务端的`join`广播后,同步成员加入消息和群聊用户信息(成员列表、群聊人数) 4. 用户可以输入并发送消息。此时客户端发送`message`事件,消息内容为发送用户名和消息。服务端收到消息会广播`message`事件,将发送的消息广播至所有客户socket 5. 客户端关闭网页,WebSocket链接断开 6. 某个客户socket断开后,服务端广播`leave`事件,消息内容为离开用户用户名以及在线用户列表 浏览器客户端收到服务端的`leave`广播后,同步成员离开消息和群聊用户信息(成员列表、群聊人数) ## file对象 ``` js let file = { lastModified: 1635917707820, lastModifiedDate: 'Wed Nov 03 2021 13:35:07 GMT+0800 (中国标准时间) {}' name: "2-数据采集-2 (1).pdf", size: 2448584, type: "application/pdf", webkitRelativePath: "" } ``` js路径: 1. __dirname 是脚本js文件所在的绝对路径 2. ./ 是你执行node命令的路径,一般是项目路径 3. 在require内,./是做require的脚本文件所在的路径 ## wireshark抓取 接下来运行服务端,开启wireshark对本地回环通信进行监听,从`localhost:3000`登入,并发送以下信息: ![image-20211122104112392](readme.assets/image-20211122104112392.png) 发送信息完毕后,关闭网页,打开wireshark,通过`ipv6.addr == ::1`进行筛选,观察抓包结果: 具体来看,结果分为以下几类: 1. 浏览器通过不同端口,建立tcp链接并通过http协议请求网页资源(index.css,favicon.ico等) 从抓包结果可以看出,经过三次握手和http GET/POST等操作完成资源传输的任务之后,这些tcp链接马上通过四次挥手断开了。 2. 浏览器和服务端建立WebSocket通信 从抓包结果来看,浏览器通过WebSocket与服务器进行通信的端口号为50676。可以看出,经过三次握手之后,经由http协议转换为websocket协议进行通讯。 接下来加入额外筛选条件`tcp.port == 50676`,分析websocket具体的通信过程 在建立连接后,可以看出双方发送的websocket信息以及对应的TCP ACK信息。在这里我们服务端发送了一个`join`事件和两个`message`事件。从抓包结果中去寻找,找到了以下四个项目: ![image-20211122170811577](readme.assets/image-20211122170811577.png) ![image-20211122170827070](readme.assets/image-20211122170827070.png) ![image-20211122170927226](readme.assets/image-20211122170927226.png) ![image-20211122170940155](readme.assets/image-20211122170940155.png) 可以看出,在这四个WebSocket项目里面分别记录了用户两次发言中客户端发送的`message`事件和服务端广播的`message`事件消息。 完成通信之后,关闭浏览器,WebSocket协议终止。按照抓包结果来看,WebSocket协议的终止过程和TCP四次挥手的方式是不同的: ![image-20211122171336512](readme.assets/image-20211122171336512.png) 不过在websocket内并没有找到`join`事件的发送信息。经过进一步查找之后,在另两个端口的http通信中找到了join事件的信息,如下图: ![image-20211122171428664](readme.assets/image-20211122171428664.png) ![image-20211122171538932](readme.assets/image-20211122171538932.png) 可以看出,`join`发送的信息在http协议转换为WebSocket协议之前,这些信息是被socket.io通过57016和61916两个端口,以http协议发送。 # 基于udp的视频通话 视频通话的场景需要较高的传输速度,而对于传输的绝对可靠性没有高要求。 鉴于此,这里采用udp协议进行视频的传输。udp相对于tcp,报头更短,效率更高。 ## 图片的处理 这里使用了 cv2 库来从摄像头中截取图片,并对图片进行编码,转为numpy格式后最终转为bytes类型。 服务端可以通过 cv2 对传回的图片进行解码,还原图片。 服务端在接收到客户端发送的图片之后,会弹出视频窗口。 视频画面如下所示: ![image-20211124101544081](readme.assets/image-20211124101544081.png) ## 速率功能 在该项目中,在服务端可以统计视频发送的实时速率。 具体的做法是,在每一次接收到客户端的视频流时,通过其长度和此刻和上一次接收到客户端视频流的时间差,计算出视频流的传输速率。 为了使得速率值减小来自于波动性的干扰,计算出即时的速率后,会存入一个长度为1000的存储区内。该存储区保存最近1000次的速率计算结果,并将其记录的速率平均值作为最终显示的速率。 通过回环地址传输作为实验,得到的速率折线图如下: ![image-20211123222050455](readme.assets/image-20211123222050455.png) 通过两台主机传输作为实验,得到的速率折线图如下: ## 延时功能(失败方案) 此外,该项目还具备视频传输延时的计算功能。 具体的做法是,在发送视频前,客户端记录此刻时间戳。服务端接受到视频后,会将接收到视频的时间戳作为回应。客户端收到回应后可以直接和之前记录的时间戳作差,即可得到传输延时。 通过回环地址传输作为实验,得到的延时如下: ![image-20211123222103928](readme.assets/image-20211123222103928.png) 但是在**两台主机**之间的延时测试中出现了问题,出现了负延迟3万+毫秒的情况。起初认为应当让客户端发送时间戳然后服务器接收判断延时。但是发送时机不是根本问题所在。而是**两台主机各自的本地时间**并不完全同步。 也就是说,两个主机**同时运行的**`time.time()`**得到的值不同**,该方式不能当作时间的统一参考系。 ## 延时功能(成功方案) 该方案参考了这篇文章:https://www.zhihu.com/question/266846392 从文章中可以得知,计算单向的延时实际上是一个非常困难的任务。可用的方法如NTP、PTP协议同步时间,物理方法校准等。 反而,通过在同一台机器上计算RTT,然后除以2作为单向时延反而是最简单的方法。 通过回环地址传输作为实验,得到的延时如下: ![image-20211124101354360](readme.assets/image-20211124101354360.png) 通过两台主机传输作为实验,得到的延时如下: 可以看出,通过这样的方式,可以得出一个正常的延时数值。虽然这个数值会具有一定的误差。