從0開始用Nodejs做一個聊天室

Cover

圖片來自Unsplash

效果圖

老樣子,還是先放個效果圖谎倔,動態(tài)圖鸟辅,有點大(4M)請耐心等加載氛什。


效果

隨便說說

最近在做東西的時候有一個對戰(zhàn)功能,需要用到Socket技術(shù)匪凉,于是了解了一番相關(guān)的實現(xiàn)方案枪眉,最后選擇了Nodejs以及基于Node的socket.io 最主要的原因還是Node比較好寫接癌,相比于PHP好多個函數(shù)來說簡潔太多了政模。

本文雖說是從0開始伦泥,但不是說無編程基礎(chǔ)的也可以望忆,至少要懂以下東西琐簇。

  1. 基礎(chǔ)的HTML
  2. Javascript語法扮饶,很基礎(chǔ)的jQuery語法

因為本文是說從0開始辩昆,所以必須要說一下Socket是什么東西盲再,有較好基礎(chǔ)的可以跳過這段直接到 如何開始 處開始蛋济。

為什么要使用Socket

不引官話棍鳖,我直接按照我的理解通俗來說。通常的網(wǎng)站或是其他的聯(lián)網(wǎng)的應(yīng)用瘫俊,因為要獲取數(shù)據(jù)鹊杖,都需要發(fā)送一個請求,這個請求你可以將它看作一個網(wǎng)址扛芽,比如你在瀏覽器鍵入網(wǎng)址"www.baidu.com"回車骂蓖,就是請求了這個地址,然后它會返回給你一些東西川尖,前面的就會給你返回百度首頁的頁面登下,瀏覽器自己會解析它(這又涉及到了其他的方面,我們暫且不談)叮喳。

所以通常的用戶和服務(wù)器的通信就只能是:用戶發(fā)送請求 -> 服務(wù)器接受請求 -> 服務(wù)器返回結(jié)果 -> 用戶收到結(jié)果被芳。在這種情況下,想要做即時應(yīng)用就無法實現(xiàn)馍悟。

想想一下畔濒,現(xiàn)在你和別人在對戰(zhàn),你打掉了他8點血锣咒,你可以告訴服務(wù)器這個信息侵状,但是服務(wù)器沒有辦法在你告訴它之后赞弥,也迅速告訴你的對手,他掉了8點血趣兄。除非對手當時發(fā)送一個請求绽左,來查看自己當前血量。但是游戲是實時的艇潭,不可能你每隔幾秒鐘就詢問服務(wù)器更新一下自己的血量拼窥。

這里的需求就是讓己方的數(shù)據(jù)發(fā)送之后即時發(fā)送給對方,那么只需要服務(wù)器也能夠以主動發(fā)送數(shù)據(jù)給用戶即可蹋凝,所以需要一種客戶端<=>服務(wù)器雙向通信的技術(shù)鲁纠,就是Socket。

BTW

本文也相當于是Socket.io官方Demo的翻譯鳍寂,想看原教程的可以去看看原文房交。


如何開始

那我們知道了為什么要用Socket之后,我們來盤算一下該如何完成這個項目伐割。

首先我們要用Nodejs,那就要去安裝Nodejs刃唤,網(wǎng)上教程很多隔心,我不想贅述,給你們一篇我覺得適合的教程:Nodejs環(huán)境搭建

裝好之后在cmd中鍵入 node -vnpm -v尚胞,顯示版本號就說明安裝沒有問題了硬霍。

然后我們先新建一個工程文件夾,名字就叫node-chat笼裳,在里面我們新建兩個文件唯卖,一個是server.js,一個是index.html躬柬,注意后綴名拜轨。

文件

然后先安裝兩個東西。

打開CMD

因為npm是國外的庫可能有點慢允青,可以安裝cnpm橄碾,執(zhí)行下面命令

npm install -g cnpm --registry=https://registry.npm.taobao.org

然后輸入

cnpm install --save express

等待安裝成功,再安裝

cnpm install --save socket.io

都安裝成功之后我們就可以開始著手Coding了颠锉。

功能分析

我們要做一個聊天室法牲,簡單起見,就不做私聊的功能了琼掠,那么我們想要的功能可以是這些:

  1. 每個人有自己的昵稱拒垃,在進入聊天室的時候自己輸入。
  2. 每個人都可以發(fā)言
  3. 有一個區(qū)域用來展示所有的發(fā)言瓷蛙,并且要實時更新
  4. 有人加入的時候提示xxx加入了群聊

開始Coding

Nodejs之 Hello World

Nodejs是用來當服務(wù)器的悼瓮,所以我們先來用Nodejs搭建一個服務(wù)器戈毒。其中用到了express這個框架,不知道的想知道可以搜搜相關(guān)知識谤牡,不想知道直接不要管也沒關(guān)系副硅。

讓我們在server.js中寫下以下代碼:

var app = require('express')(); //引入express庫
var http = require('http').Server(app); //將express注冊到http中

//當訪問根目錄時,返回Hello World
app.get('/', function(req, res){
  res.send('<h1>Hello world</h1>');
});

//啟動監(jiān)聽翅萤,監(jiān)聽3000端口
http.listen(3000, function(){
  console.log('listening on *:3000');
});

保存之后恐疲,我們在命令行cd到server.js所在的文件夾下,執(zhí)行

node server.js

跟下面圖片相同就說明成功了

node server.js

然后打開瀏覽器套么,輸入localhost:3000回車培己。

hello world

OK,Node搭建的服務(wù)器就OK了胚泌。接下來省咨,我們讓瀏覽器進入的時候,跳轉(zhuǎn)到我們的index.html頁面玷室,我們在那寫聊天室的界面零蓉。

跳轉(zhuǎn)到聊天室頁面

聊天室頁面的實現(xiàn)

index.html中輸入以下代碼:

<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>發(fā)送</button>
    </form>
  </body>
</html>

保存。

根目錄跳轉(zhuǎn)

前面我們在server.js中寫了根目錄返回hello world穷缤,現(xiàn)在我們讓它跳轉(zhuǎn)到聊天室頁面敌蜂。將server.js中的

app.get('/', function(req, res){
  res.send('<h1>Hello world</h1>');
});

替換為

app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});

然后在CMD中ctrl+c結(jié)束,重新運行node server.js津肛。
現(xiàn)在在瀏覽器中訪問localhost:3000章喉,已經(jīng)跳轉(zhuǎn)到聊天頁面了。

跳轉(zhuǎn)到聊天界面

引入Socket

繼續(xù)修改server.js身坐,增加下方標注內(nèi)容:

var app = require('express')();
var http = require('http').Server(app);
//new addition
var io = require('socket.io')(http);
//end

app.get('/', function(req, res){
  res.sendFile(__dirname + '/index.html');
});

//new addition
io.on('connection', function(socket){
  console.log('a user connected');
});
//end

http.listen(3000, function(){
  console.log('listening on *:3000');
});

新加入的內(nèi)容的意思就是秸脱,當有新的socket連接成功之后,就打印一下信息部蛇。

然后編輯inde.js摊唇,添加下方標注內(nèi)容:

<!doctype html>
<html>
  <head>
    <title>Socket.IO chat</title>
    <style>
      * { margin: 0; padding: 0; box-sizing: border-box; }
      body { font: 13px Helvetica, Arial; }
      form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
      form input { border: 0; padding: 10px; width: 90%; margin-right: .5%; }
      form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
      #messages { list-style-type: none; margin: 0; padding: 0; }
      #messages li { padding: 5px 10px; }
      #messages li:nth-child(odd) { background: #eee; }
    </style>
    //new addition
    <script src="http://apps.bdimg.com/libs/jquery/2.1.4/jquery.min.js"></script>
    //end
  </head>
  <body>
    <ul id="messages"></ul>
    <form action="">
      <input id="m" autocomplete="off" /><button>發(fā)送</button>
    </form>
  </body>

  //new addition
  <script src="/socket.io/socket.io.js"></script>
  <script>
    var socket = io()
  </script>
  //end
</html>

在上面的內(nèi)容中,引入了jQuery與socket.io涯鲁,并且在頁面加載完成之后遏片,新建了一個io對象。

然后我們重新啟動node服務(wù)撮竿,訪問localhost:3000吮便。就會有以下結(jié)果。

新用戶進入

添加交互

當用戶有動作的時候幢踏,我們要顯示在聊天面板中髓需,就插入一個li標簽好了。在index.html的script中寫一個方法房蝉,后面顯示什么內(nèi)容僚匆,就調(diào)用這個方法在面模板中插入一條li就好了微渠。

將以下代碼寫到index.html中。

function addLine(msg) {
  $('#messages').append($('<li>').text(msg));
}

等等咧擂,我們好像忘了什么事情

在前面功能分析的時候逞盆,我們就說用戶剛進來的時候,需要輸入自己的昵稱松申。
我們現(xiàn)在就先把這個功能實現(xiàn)一下云芦。

index.html的script最前面,我們先彈出一個prompt()來詢問用戶的昵稱贸桶,然后將用戶名發(fā)送給后端舅逸,讓后端告訴大家這個用戶進來了接。

發(fā)送昵稱給后端

新用戶發(fā)送自己的昵稱給服務(wù)器后皇筛,要讓所有處在聊天室的人知道琉历,所以服務(wù)器要發(fā)一個廣播。發(fā)送數(shù)據(jù)給后端水醋,調(diào)用socket的emit方法旗笔,這里指定一個事件名字叫join,然后后端會處理這個事件拄踪。

然后現(xiàn)在我們的script標簽中是這樣的换团。

<script>
    var name   = prompt("請輸入你的昵稱:");
    var socket = io()
    
    //發(fā)送昵稱給后端
    socket.emit("join", name)
    function addLine(msg) {
        $('#messages').append($('<li>').text(msg));
    }
</script>

后端發(fā)送廣播

為了在后端區(qū)分用戶,我們用一個usocket數(shù)組來保存每一個用戶的socket實例宫蛆。

首先監(jiān)聽join事件,在接收到昵稱之后的猛,將該用戶的加入聊廣播給所有用戶耀盗。
server.js代碼如下:

為了限制篇幅長度,從此處開始貼上來的代碼有省略卦尊,請參考上下文確定位置叛拷。

var usocket = []; //全局變量
...
io.on('connection', function(socket){
  console.log('a user connected')

  //監(jiān)聽join事件
  socket.on("join", function (name) {
    usocket[name] = socket
    io.emit("join", name) //服務(wù)器通過廣播將新用戶發(fā)送給全體群聊成員
  })
});
...

然后在index.html的script中添加以下代碼,接受到新用戶加入事件的處理:

...
//發(fā)送昵稱給后端
socket.emit("join", name)

//收到服務(wù)器發(fā)來的join事件時
socket.on("join", function (user) {
  addLine(user + " 加入了群聊")
})
...

現(xiàn)在我們再來測試一下岂却,node server.js開啟后端服務(wù)忿薇,然后訪問localhost:3000,輸入用戶名iimT

輸入iimT

然后可以看到

加入群聊

然后新開一個標簽頁躏哩,輸入用戶名iimY

iimY

在iimY的面板中署浩,可以看到

iimY加入群聊

再回到iimT的面板中,看到新提示的iimY加入了群聊

iimT面板提示iimY加入

臨時加個小功能

為了更好的辨識每個面板是誰扫尺,我們在用戶輸入自己的昵稱之后筋栋,將網(wǎng)頁標題改成XXX的聊天

只需要新加一行代碼:

...
//發(fā)送昵稱給后端正驻,并更改網(wǎng)頁title
socket.emit("join", name)
document.title = name + "的群聊" //new addition
...

處理發(fā)送和接受消息

【一】index.html 將用戶消息發(fā)送給服務(wù)器

用戶輸入消息弊攘,然后點擊發(fā)送我們將用戶的消息發(fā)送給服務(wù)器抢腐,然后服務(wù)器將消息廣播給所有的用戶。

我們需要給form的提交綁定一個事件襟交,讓它來處理新消息的發(fā)送迈倍。

$('form').submit(function () {
    //solve code
})

在綁定的事件中,我們需要逐步做以下事情:

  1. 獲取用戶輸入的消息捣域。
var msg = $("#m").val()
  1. 發(fā)送事件給服務(wù)器啼染,事件名就叫message吧,發(fā)送的數(shù)據(jù)就是msg竟宋。
socket.emit("message", msg) //將消息發(fā)送給服務(wù)器
  1. 將消息輸入框置空提完。
$("#m").val("") //置空消息框
  1. 阻止form的提交事件。
return false //阻止form提交

最后丘侠,我們給form綁定的事件如下:

...
//當發(fā)送按鈕被點擊時
$('form').submit(function () {
  var msg = $("#m").val() //獲取用戶恮的信息
  socket.emit("message", msg) //將消息發(fā)送給服務(wù)器
  $("#m").val("") //置空消息框
  return false //阻止form提交
})
...

【二】server.js 服務(wù)器接受消息

只需要兩步:

  1. 監(jiān)聽前端的message事件
  2. 在監(jiān)聽到事件之后徒欣,將新消息廣播給全體用戶,這里也將廣播事件的名字叫message吧蜗字。

代碼如下:

io.on('connection', function(socket){
  console.log('a user connected')

  socket.on("join", function (name) {
    usocket[name] = socket
    io.emit("join", name)
  })
  
  //new addition
  socket.on("message", function (msg) {
    io.emit("message", msg) //將新消息廣播出去
  })
});

【三】index.html 客戶接受新消息

客戶接收到服務(wù)器發(fā)來的message事件打肝,將新消息呈現(xiàn)在面板中。

...
socket.on("join", function (user) {
  addLine(user + " 加入了群聊")
})

//接收到服務(wù)器發(fā)來的message事件
socket.on("message", function(msg) {
  addLine(msg)
})

//當發(fā)送按鈕被點擊時
$('form').submit(function () {
  var msg = $("#m").val() //獲取用戶輸入的信息
...

整個過程相當于挪捕,用戶a將自己的消息用message事件發(fā)送給服務(wù)器粗梭,服務(wù)器監(jiān)聽message事件接收其中的消息,將消息用事件message廣播給全體用戶级零,全體用戶監(jiān)聽message事件断医,將事件接受到的消息呈現(xiàn)在聊天框中。PS:這一段是整個socket編程的核心奏纪,可以多讀幾遍細細理解鉴嗤。

最終測試

那么現(xiàn)在我們就完成了整個聊天室的功能,來測試一下吧序调。

1. 開啟服務(wù)器node server.js醉锅。

開啟服務(wù)

2. 開一個標簽,輸入地址localhost:3000发绢,回車硬耍,填寫昵稱為iimT

iimT

3. 再開一個標簽边酒,輸入地址localhost:3000经柴,回車,填寫昵稱為iimY墩朦。

iimY

然后開始讓兩個人互相發(fā)送消息吧口锭。我這里用一張動態(tài)圖來顯示最終效果,也就是本文最開頭的那張效果圖。

PS: 圖大概有4M鹃操,所以需要點時間加載


效果

至此韭寸,聊天室以就做完了。

最后

源代碼地址:tfh93121/node-chat

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末荆隘,一起剝皮案震驚了整個濱河市恩伺,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌椰拒,老刑警劉巖晶渠,帶你破解...
    沈念sama閱讀 218,941評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異燃观,居然都是意外死亡褒脯,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,397評論 3 395
  • 文/潘曉璐 我一進店門缆毁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來番川,“玉大人,你說我怎么就攤上這事脊框“涠剑” “怎么了?”我有些...
    開封第一講書人閱讀 165,345評論 0 356
  • 文/不壞的土叔 我叫張陵浇雹,是天一觀的道長沉御。 經(jīng)常有香客問我,道長昭灵,這世上最難降的妖魔是什么吠裆? 我笑而不...
    開封第一講書人閱讀 58,851評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮烂完,結(jié)果婚禮上试疙,老公的妹妹穿的比我還像新娘。我一直安慰自己窜护,他們只是感情好,可當我...
    茶點故事閱讀 67,868評論 6 392
  • 文/花漫 我一把揭開白布非春。 她就那樣靜靜地躺著柱徙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪奇昙。 梳的紋絲不亂的頭發(fā)上护侮,一...
    開封第一講書人閱讀 51,688評論 1 305
  • 那天,我揣著相機與錄音储耐,去河邊找鬼羊初。 笑死,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的长赞。 我是一名探鬼主播晦攒,決...
    沈念sama閱讀 40,414評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼得哆!你這毒婦竟也來了脯颜?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,319評論 0 276
  • 序言:老撾萬榮一對情侶失蹤贩据,失蹤者是張志新(化名)和其女友劉穎栋操,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體饱亮,經(jīng)...
    沈念sama閱讀 45,775評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡矾芙,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了近上。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片剔宪。...
    茶點故事閱讀 40,096評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖戈锻,靈堂內(nèi)的尸體忽然破棺而出歼跟,到底是詐尸還是另有隱情,我是刑警寧澤格遭,帶...
    沈念sama閱讀 35,789評論 5 346
  • 正文 年R本政府宣布哈街,位于F島的核電站,受9級特大地震影響拒迅,放射性物質(zhì)發(fā)生泄漏骚秦。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,437評論 3 331
  • 文/蒙蒙 一璧微、第九天 我趴在偏房一處隱蔽的房頂上張望作箍。 院中可真熱鬧,春花似錦前硫、人聲如沸胞得。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,993評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽阶剑。三九已至,卻和暖如春危号,著一層夾襖步出監(jiān)牢的瞬間牧愁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,107評論 1 271
  • 我被黑心中介騙來泰國打工外莲, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留猪半,地道東北人。 一個月前我還...
    沈念sama閱讀 48,308評論 3 372
  • 正文 我出身青樓,卻偏偏與公主長得像磨确,于是被迫代替她去往敵國和親沽甥。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,037評論 2 355

推薦閱讀更多精彩內(nèi)容