nodejs 与webSocket 模拟简单的聊天室

1、场景

IM通讯

2、WebSocket 与 HTTP

WebSocket 协议诞生于2008年,2011年成为国际标准,目前所有浏览器都已经支持。WebSocket 的最大特点就是实现服务器端和客户端双向平等对话:服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息。

HTTP有1.0和1.1的说法,也就是所谓的keep-alive,把多个HTTP整合成一个。WebSocket是一个新的协议,跟HTTP协议基本没什么关系,只是为了更好的浏览器兼容,在握手阶段采用了HTTP协议。

3、HTTP 与 WebSocket 的主要区别(如下图)


4、WebSocket 的其他特点

1、建立在 TCP 协议之上,服务器端的实现比较容易

2、与 HTTP 协议有着良好的兼容性。默认端口也是80和443,握手阶段采用 HTTP 协议,握手时不容易屏蔽,能通过各种 HTTP 代理服务器

3、数据格式比较轻量,性能开销小,通信高效

4、可以发送文本,也可以发送二进制数据

5、没有同源限制,客户端可以与任意服务器通信

6、协议标识符是ws(如果加密,则为wss),服务器网址就是 URL

5、WebSocket 是什么样的协议,具体有什么优点

相对于 HTTP 这种非持久的协议来说,WebSocket 是一个持久化的协议。

HTTP 是通过 Request 来界定生命周期的,也就是一个 Request 一个 Response ,在 HTTP1.0 中,这次 HTTP 请求就结束了。在 HTTP1.1 中有了改进,存在一个 keep-alive:在一个 HTTP 连接中,可以发送多个 Request,接收多个 Response,需要注意的是 Request => Response, 在 HTTP 中永远是这样,也就是说一个 Request 只能有一个 Response,并且这个 Response 也是被动的,不能主动发起。

WebSocket 是基于 HTTP 协议的来完成一部分握手,先看一个典型的 WebSocket 握手

1、GET /chat HTTP/1.1

2、Host: server.example.com

3、Upgrade: websocket

4、Connection: Upgrade

5、Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==

6、Sec-WebSocket-Protocol: chat, superchat

7、Sec-WebSocket-Version: 13

8、Origin: http://example.com

上边那段类似 HTTP 协议的握手请求中,多了这么几个东西

1、Upgrade: websocket

2、Connection: Upgrade

这就是 WebSocket 的核心了,告诉服务器:我发起的请求要用 WebSocket 协议了,快点帮我找到对应的助理处理,而不是那个传统的 HTTP

3、Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==

4、Sec-WebSocket-Protocol: chat, superchat

5、Sec-WebSocket-Version: 13

首先, Sec-WebSocket-Key 是一个 Base64 encode 的值,这个是浏览器随机生成的,告诉服务器:我要验证你是不是真的是 WebSocket 助理

然后, Sec_WebSocket-Protocol 是一个用户定义的字符串,用来区分同 URL 下,不同的服务所需要的协议。

最后, Sec-WebSocket-Version 是告诉服务器所使用的 WebSocket Draft (协议版本)

然后服务器会返回下列东西,表示已经接受到请求, 成功建立 WebSocket 啦!

1、HTTP/1.1 101 Switching Protocols

2、Upgrade: websocket

3、Connection: Upgrade

4、Sec-WebSocket-Accept: HSmrc0sMlYUkAGmm5OPpG2HaGWk=

5、Sec-WebSocket-Protocol: chat

这里开始就是 HTTP 最后负责的区域了,告诉客户,我已经成功切换协议啦~

1、Upgrade: websocket

2、Connection: Upgrade

依然是固定的,告诉客户端即将升级的是 WebSocket 协议,而不是 mozillasocket,lurnarsocket 或者 shitsocket。

然后, Sec-WebSocket-Accept 这个则是经过服务器确认,并且加密过后的 Sec-WebSocket-Key 。 

后面的, Sec-WebSocket-Protocol 则是表示最终使用的协议。

至此,HTTP 已经完成它所有工作了,接下来就是完全按照 WebSocket 协议进行了。

6、WebSocket 的作用

在讲 WebSocket之前,我就顺带着讲下 ajax轮询 和 long poll 的原理

6-1、ajax轮询

ajax轮询的原理非常简单,让浏览器隔个几秒就发送一次请求,询问服务器是否有新信息,如以下场景

1、客户端:小哥,有没有新信息(Request)

2、服务端:没有(Response)

3、客户端:大哥,有没有新信息(Request)

4、服务端:没有...(Response)

5、客户端:大叔,有没有新信息(Request)

6、服务端:你好烦啊,没有啊...(Response)

7、客户端:呜呜呜...有没有新消息(Request)

8、服务端:好啦好啦,有啦给你。(Response)

9、客户端:哈哈...有没有新消息呀(Request)

10、服务端:...没...没...没有(Response) —- loop

6-2、long poll

long poll 原理跟 ajax轮询 差不多,都是采用轮询的方式,不过采取的是阻塞模型(一直打电话,没收到就不挂电话),也就是说,客户端发起请求后,如果没消息,就一直不返回 Response 给客户端。直到有消息才返回,返回完之后,客户端再次建立连接,周而复始,如以下场景

1、客户端:小哥,有没有新信息,没有的话就等有了才返回给我吧(Request)

2、服务端:嗯嗯... 等有消息的时候再给你(Response)

3、客户端:小哥,有没有新信息,没有的话就等有了才返回给我吧(Request) -loop

从上面可以看出ajax和long poll这两种方式,都是在不断地建立HTTP连接,然后等待服务端处理,可以体现HTTP协议的另外一个特点,被动性:服务端不能主动联系客户端,只能有客户端发起。

这两种都是非常消耗资源的,ajax轮询需要服务器有很快的处理速度和资源。long poll 需要有很高的并发性,也就是说同时接待客户端的能力。所以 ajax轮询 和 long poll 都有可能发生这种情况:

1、客户端:有新信息么?

2、服务端:正忙,请稍后再试(503 Server Unavailable)

3、客户端:...好吧,有新信息么?

4、服务端:正忙,请稍后再试(503 Server Unavailable)

注意:HTTP 是一个无状态协议。通俗的说就是,服务器因为每天要接待太多客户了,你一挂电话,他就把你的东西全忘光了,把你的东西全丢掉了,你第二次还得再告诉服务器一遍。

6-3、WebSocket

当服务器完成协议升级后(HTTP->Websocket),由被动变成主动,主动推送信息给客户端,模拟场景:

1、客户端:我要建立Websocket协议,需要的服务:chat,Websocket协议版本:17(HTTP Request)

2、服务端:ok,确认,已升级为Websocket协议(HTTP Protocols Switched)

3、客户端:麻烦你有信息的时候推送给我呀

4、服务端:ok,有的时候会告诉你的

5、服务端:今天小明上学迟到了

6、服务端:老师罚小明背诵唐诗宋词三百首

7、服务端:哈哈哈哈哈啊哈哈哈哈

8、服务端:笑死我了哈哈哈哈哈哈哈

这样,只需要经过一次 HTTP 请求,就可以做到源源不断的信息传送了

7、项目实战

工程说明需求

1、js文件:service.js

2、聊天室文件(html):chatRoom.html

3、需要引入的模块:http、fs、ws(http与fs为系统模块,可直接引入,ws为webSocket模块,需要npm或者cnpm下载到工程主目录中)

7-1-1、service.js文件中代码

开启聊天室服务

const http = require('http');

读取文件

const fs = require('fs');

客户端服务

var server = http.createServer((req,res)=>{

              res.writeHead(200,{'Content-Type':'text/html;charset=utf8'});

              //将页面显示到浏览器中

              res.end(fs.readFileSync('./chatRoom.html'));

});

引入socket服务,注意大小写,区分客户端服务

const Server = require('ws').Server;

建立好服务端,在这里监听客户端的服务(server)

var wss = new Server({server})

存放前端连接的socket

var clientMap = {};

记录进入聊天室的客户数量

var count = 0; //只做计数

var id = 0; //用作客户的id

当客户端连接上时就会触发,回调会接收一个scoket对象

wss.on('connection',function(socket){

           count ++;

           id ++;

           //给每个客服添加一个id编号,

           socket.id = id;

           //以id编号存储客户数据

           clientMap[socket.id] = socket;

           //在服务端监听上线客户

           console.log('用户' + id + '上线了');

           //当客户端向服务端发送数据时触发

           socket.on('message',function(msg){

                     broadcast(socket,msg);

            })

            //当客户端下线即断开时触发

           socket.on('close',function(){

                     count --;

                     //将下线的客户移出当前的聊天室

                    console.log('用户' + socket.id+ '下线了');

                    delete clientMap[socket.id];

           })

           //当客户连接错误时触发

          socket.on('error',function(err){

                    console.log('客户连接错误:'+err);

           })

})

创建广播函数,向所有的客户端发送某一个客户端说的话,模拟聊天室

function broadcast (socket,msg){

             for(var id in clientMap){

                     clientMap[id].send('用户'+socket.id+'说:'+msg);

              }

}

设置端口

server.listen(1314);

7-1-2、chatRoom.html文件中代码

聊天室简单样式,写到header标签中

.room{

       border: solid 1px #efefef;

       width: 300px;

       height: 300px;

       overflow-y: scroll;

}

聊天窗口

<body>

      <div class='room'></div>

      <input type='text' id='mes'>

      <button id='send' >发送</button>

</body>

7-1-3、script中的js代码

创建客户端的socket连接

注意:socket的IP地址要与服务端的一致,包括端口号

var wsc = new WebSocket('ws://172.16.92.71:1314');

检测聊天室的开关状态

var isOpen = true;

当客户端连接上的时候触发

wsc.onopen = function(e){

        console.log(e,"我成功进入聊天室了")

}

后台返回消息的时候触发

wsc.onmessage = function(e){

          console.log(e,"===后端消息==")

          document.getElementsByClassName('room')[0].innerHTML += '<p>'+e.data+'</p>';

}

当服务端关闭的时候触发

wsc.onclose = function(e){

        console.log(e,"聊天室已经关闭了")

        isOpen = false;

        alert('聊天室已经关闭了');

}

错误状况下触发

wsc.onerror = function(e){

       console.log(e,'连接错误');

}

向服务端发送数据

send.onclick = function(){

          if(!isOpen){

                  alert('聊天室已经关闭了,不能聊天了')

           }else{

                  if(mes.value == ''){

                         alert('发送的内容不能为空')

                  }else{

                         wsc.send(mes.value);

                  }

         }

}

7-1-4、结束

在终端执行下面的命令开启你的本地服务:

node    service.js

将你设置的ip地址发给别人,尝试聊天吧,本项目中的ip为:

http://172.16.92.71:1314

项目实战部分为作者原生编辑,其他为后期编辑借用 nnngu的blog:https://www.cnblogs.com/nnngu/p/9347635.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 201,784评论 5 474
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 84,745评论 2 378
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 148,702评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,229评论 1 272
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,245评论 5 363
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,376评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,798评论 3 393
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,471评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,655评论 1 295
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,485评论 2 318
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,535评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,235评论 3 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,793评论 3 304
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,863评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,096评论 1 258
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,654评论 2 348
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,233评论 2 341