1. 程式人生 > >Node.js 切近實戰(十一) 之實時通訊

Node.js 切近實戰(十一) 之實時通訊

今天我們主要看一下Socket.IO實時通訊,先看一下介面。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

.row

.col-md-9

.panel.panel-primary

.panel-heading

h3.panel-title(style='font-size:13px;') Chat Message

.panel-body#div_msgbody(style='min-height:590px;max-height:590px;overflow:auto;max-width:750px;')

#div_msg.panel-content(style='word-wrap:break-word;word-break: break-word;')

.panel-footer

#div_footer(style='height:36px;line-height:36px')    

.row

.col-md-8(style='color:#3f51b5;font-weight:bold') Chat History:

input#chat_history

.col-md-4.right-align-text 

a#link_clear(href='javascript:void(0)') Clear

.row

.col-md-10

input#txt_msg.form-control(type='text' style='height:40px;resize:none' maxlength=200 placeholder='Input message here.')

.col-md-2.right-align-text

button#btn_send.k-button.k-primary(type='button' style='height:40px;width:100%')

span.glyphicon.glyphicon-send

span(style='margin-left:5px') Send

.col-md-3

.panel.panel-primary

.panel-heading

h3.panel-title(style='font-size:13px;') Members

.panel-body.panel-inner-height(style='overflow:auto;')

#div_users.panel-content(style='word-break: break-word;')

.panel-footer

.left-margin-10 

span.text-color#total Member Count:0

#chat_historyWindow(style='display:none')

#chat_historyContent.panel-content(style='word-wrap:break-word;word-break: break-word;')

span#notify

block scripts

script(type='text/javascript' src='/javascripts/local/other/chat.js')

wKioL1eDsL6xz1MqAABsQWR8_7I057.png 

這就是聊天介面,左邊是聊天內容,右邊是參加聊天的使用者。要實現這個聊天,之前我們在部落格中提到了SingalR,可以用於ASP.NET,WinForm以及WPF。今天我們要使用Node.js平臺上的Socket.IO.js。首先要在專案中引用這個擴充套件包。

wKiom1eDscyQdWxlAACfiVxC6a8872.png

安裝好之後,在Package.json中就會自動加入這個包,管理起來。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

"dependencies": {

"array-splice""^0.1.1",

"body-parser""~1.8.4",

"busboy""^0.2.12",

"cassandra-driver""^3.0.0",

"cookie-parser""~1.3.3",

"debug""~2.0.0",

"express""~4.9.8",

"express-session""1.12.1",

"gridfs-stream""^1.1.1",

"jade""^1.11.0",

"log4js""^0.6.29",

"mongoose""~4.2.3",

"morgan""^1.6.1",

"request""^2.67.0",

"serve-favicon""~2.1.3",

"socket.io""^1.3.7",

"string.prototype.endswith""^0.2.0",

"string.prototype.startswith""^0.2.0"

}

我在這裡還使用的是老版本,哈哈,OK,老版本新版本都能用。我們進入主題,在第一篇環境搭建中,我就說了我們的啟動入口是www檔案。

wKioL1eDspryvKkZAAAOS6Gho1U804.png

在www檔案中,我們初始化了SocketIO的一些東西。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

var chatUserCount = 0;

var chatUsers = {};

var server = app.listen(app.get('port'), function() {

debug('Express server listening on port ' + server.address().port);

});

var io = require('socket.io')(server);

io.on('connection'function (socket) {

socket.on('joinchat'function (obj) {

console.log('a user connected:'+obj.UserName);

socket.name = obj.UserName;

if (!chatUsers.hasOwnProperty(obj.UserName)) {

chatUsers[obj.UserName] = obj;

chatUserCount++;

}

io.emit('joinchat', { chatUsers: chatUsers ,chatUserCount: chatUserCount,joinedUser: obj});

});

socket.on('leftchat'function () {

console.log('a user left');

if (chatUsers.hasOwnProperty(socket.name)) {

var obj = chatUsers[socket.name];

delete chatUsers[socket.name];

chatUserCount--;

io.emit('leftchat', { chatUsers: chatUsers, chatUserCount: chatUserCount, leftUser: obj });

}

});

socket.on('disconnect'function () {

if (chatUsers.hasOwnProperty(socket.name)) {

var obj = chatUsers[socket.name];

delete chatUsers[socket.name];

chatUserCount--;

io.emit('leftchat', { chatUsers: chatUsers, chatUserCount: chatUserCount, leftUser: obj });

}

});

socket.on('message'function (obj) {

io.emit('message', obj);

});

socket.on('error'function(exception) {

console.log('SOCKET ERROR');

socket.destroy();

});

});

在這裡當客戶端有使用者進入聊天時,就會發射joinchat事件,後臺就會觸發joinchat事件。當客戶端和服務端建立連線時,服務端就會發射廣播joinchat,所有連線的客戶端都會收到這個廣播,悄無聲息重新整理介面。當客戶端使用者失去連線(關閉瀏覽器)時,就會自動發射disconnect事件,服務端就會觸發disconnect事件,並將結果廣播到各個客戶端,客戶端自動重新整理頁面。當用戶unload該頁面時,會觸發leftchat。當客戶端發信息時,就會觸發message事件,將該使用者的訊息傳送到其他人。這個聊天介面的過程就是這樣,很簡單。

接下來我們來看一下客戶端程式碼。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

var popupNotification = $("#notify").kendoNotification({

autoHideAfter: 2000,

height: 60,

stacking: "down"

}).data("kendoNotification");

var socket = io();

var loginUser = sessionStorage.getItem("LoginUser");

if (loginUser == null) {

window.location.href = "/";

return;

}

var userObj = eval("(" + loginUser + ")");

sessionStorage.removeItem('chatUser');

socket.emit("joinchat", userObj);

socket.on('joinchat'function (data) {

if (!sessionStorage.getItem('chatUser')) {

sessionStorage.setItem('chatUser', JSON.stringify({ "user": [] }));

}

var usersObj = JSON.parse(sessionStorage.getItem('chatUser'));

if (usersObj.user.indexOf(data.joinedUser.UserID) == -1) {

usersObj.user.push(data.joinedUser.UserID);

sessionStorage.setItem('chatUser', JSON.stringify(usersObj));

setchartdetail(data);

if (data.joinedUser.UserID != userObj.UserID) {

popupNotification.show('<span style="color:red">' + data.joinedUser.FullName + ' joined in.</span>''info');

}

}

});

socket.on('leftchat'function (data) {

var usersObj = JSON.parse(sessionStorage.getItem('chatUser'));

var index = usersObj.user.indexOf(data.leftUser.UserID);

usersObj.user.splice(index, 1);

sessionStorage.setItem('chatUser', JSON.stringify(usersObj));

setchartdetail(data);

popupNotification.show('<span style="color:red">' + data.leftUser.FullName + ' left.</span>''warning');

});

當用戶進入這個頁面時,我們發射joinchat,並將當前登入使用者資訊傳送到服務端,服務端再將該使用者資訊以及計算好的使用者總數廣播到各個客戶端。注意這裡我們為了提醒使用者,用到了kendoNotification,效果如下,當有人進入或者離開時,會出現popup提示。

wKiom1eDuAvCnttIAABY1yPLRUA192.png

當用戶離開時,如上,當用戶進入時,如下

wKioL1eDuDbi3mmiAABxPpJdZlM722.png

OK,接下來我們看一下最主要的部分,聊天。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

$("#btn_send").click(function () {

sendmsg();

})

$("#txt_msg").keydown(function (e) {

if (e.keyCode == 13) {

sendmsg();

}

});

function sendmsg() {

var msg = $.trim($("#txt_msg").val());

if (msg) {

var msgObj = { user: userObj, msg: msg };

socket.emit("message", msgObj);

}

$("#txt_msg").val("");

}

上面就是點選SEND按鈕或者文字框回車傳送訊息的程式碼,後臺接到message廣播給客戶端。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

socket.on('message'function (data) {

var msgObj = data;

var userAvatar = '/images/userlogin.png';

if (msgObj.user.UserName == userObj.UserName) {

$("#div_msg").append("<div class='row-margin'>" 

"<img src='" + userAvatar + "' style='height:40px;width:40px;right:-660px;position:relative'/>" 

"<span style='right:-530px;position:relative'>" + msgObj.user.FullName + "</span>" 

"<div class='demo clearfix fr'>" 

"<span class='triangle'></span>" 

"<div class='article' style='word'>" + msgObj.msg 

"</div></div></div>");

db.transaction(function (tx) {

tx.executeSql('INSERT INTO ChatRecords(userId,sendUserId,fullname,content) VALUES("' + userObj.UserID + '","' + msgObj.user.UserID + '","' + msgObj.user.FullName + '","' + msgObj.msg + '")');

});

}

else {

$("#div_msg").append("<div class='row-margin'>" 

"<img src='" + userAvatar + "' style='height:40px;width:40px;position:relative'/>" 

"<span style='left:10px;position:relative'>" + msgObj.user.FullName + "</span>" 

"<div class='demo clearfix'>" 

"<span class='triangle'></span>" 

"<div class='article'>" + msgObj.msg 

"</div></div></div>");

db.transaction(function (tx) {

tx.executeSql('INSERT INTO ChatRecords(userId,sendUserId,fullname,content) VALUES("' + userObj.UserID + '","' + msgObj.user.UserID + '","' + msgObj.user.FullName + '","' + msgObj.msg + '")');

});

}

var objDiv = document.getElementById("div_msgbody");

objDiv.scrollTop = objDiv.scrollHeight;

});

其實這裡不過是一個拼message的過程,如果傳送者是本人,則訊息靠右顯示,否則靠左顯示。看看James和lilei的聊天。

wKioL1eDvFLChW-nAAEI2Pjo2wY690.png

wKioL1eDvISxYZWoAAEF5YOxGPs308.png

看到了吧,聊天聊的很Happy。OK,上面大家是不是看到了一段類似sql的程式碼,不錯,就是將聊天資訊存到本地WebSQL sqlite資料庫

1

2

3

db.transaction(function (tx) {

tx.executeSql('INSERT INTO ChatRecords(userId,sendUserId,fullname,content) VALUES("' + userObj.UserID + '","' + msgObj.user.UserID + '","' + msgObj.user.FullName + '","' + msgObj.msg + '")');

});

在使用之前我們需要首先連線資料庫建立表。

1

2

3

4

var db = openDatabase('ChatHistory''2.0''chat records', 10 * 1024 * 1024);

db.transaction(function (tx) {

tx.executeSql('CREATE TABLE IF NOT EXISTS ChatRecords (id INTEGER PRIMARY KEY AUTOINCREMENT,userId TEXT NOT NULL DEFAULT "",sendUserId TEXT NOT NULL DEFAULT "",fullname TEXT NOT NULL DEFAULT "",content TEXT NOT NULL DEFAULT "",indate DATETIME default CURRENT_TIMESTAMP)');

});

確實和sqlSever的語法有點像,我們看一下儲存到本地webSQL的聊天記錄,google Chrome,按F12

wKiom1eDvhOAWuv8AAFYusu4uNo881.png

看到了吧,聊天記錄已經被儲存下來,由於我是一臺機器,一個瀏覽器開兩個tab頁,所以這裡的聊天記錄就是兩份,一個是傳送人的,一個是接收人的。大家注意這裡還有張表,sqlite_sequence,我們的主鍵id定義為自增列,所以這張表儲存的是我們的自增列(id)的最大值。

wKioL1eDvwDz8K2YAABqyhD4c2k819.png

最大是54,和我們表ChatRecords中的最大值相等。