参照网上的各种教程和代码实现一个简易在线聊天器,支持定向发消息
服务器端
app.js(使用express搭建静态服务,socket.io建立服务器端监听)
var app = require('express')() , express = require('express') , server = require('http').createServer(app) , io = require('socket.io').listen(server); var zTool = require("./zTool"); var onlineUserMap = new zTool.SimpleMap(); var historyContent = new zTool.CircleList(100); EVENT_TYPE = { "LOGIN":"LOGIN", "LOGOUT":"LOGOUT", "CHAT":"CHAT", "ERROR":"ERROR" }; server.listen(80); app.use('/', express.static(__dirname + '/')); io.sockets.on('connection', function (socket){ // // exception from clients // socket.on("disconnect", function (){ var user = onlineUserMap.get(socket.id); var data = JSON.stringify({ "event":EVENT_TYPE.LOGOUT, "values":[user] }); onlineUserMap.remove(socket.id); io.sockets.emit("message",data);//broadcast }); // // normal messages(login | logout | chat) // socket.on('message', function (message){ //json decode var msgData = JSON.parse(message); if(msgData && msgData.event){ switch(msgData.event) { // //user login // case EVENT_TYPE.LOGIN: var newUser = {"uid":socket.id, "nick":msgData.values[0]}; onlineUserMap.put(socket.id, newUser); var data = JSON.stringify({ "user":onlineUserMap.get(socket.id), "event":EVENT_TYPE.LOGIN, "values":[newUser], "users":onlineUserMap.values(), "historyContent":historyContent.values() }); io.sockets.emit("message",data);//broadcast break; // //user chat // case EVENT_TYPE.CHAT: var content = msgData.values[0]; var data = JSON.stringify({ "user":onlineUserMap.get(socket.id), "event":EVENT_TYPE.CHAT, "values":[content] }); var to = msgData.to; if(to != 0){//private chat io.sockets.emit(socket.id,data); //to sender io.sockets.emit(to,data);//to receiver } else{//public chat io.sockets.emit("message",data);//broadcast historyContent.add({ "user":onlineUserMap.get(socket.id), "content":content, "time":new Date().getTime() }); } break; // //user logout // case EVENT_TYPE.LOGOUT: var user = msgData.values[0]; onlineUserMap.remove(user.uid); var data = JSON.stringify({ "event":EVENT_TYPE.LOGOUT, "values":[user] }); io.sockets.emit("message",data);//broadcast break; } } else { console.log("invalid message , userId:"+socket.id+" , message:"+message); var data = JSON.stringify({ "uid":socket.id, "event":EVENT_TYPE.ERROR }); socket.emit(socket.id, data); } }); });
网页端index.html
<html>
<head>
<title>chatroom</title>
<meta charset="utf-8" />
<script src="jquery.js"></script>
<script src="zTool.js"></script>
<script src="DateUtil.js"></script>
<script src="chat.js"></script>
<script src="http://127.0.0.1:80/socket.io/socket.io.js"></script>
<link href="style.css" rel="stylesheet" media="all">
</head>
<body>
<div id="errorPage" class="page" style="display:none">
<h1>当前浏览器不支持WebSocket,请使用其他浏览器,例如chrome 4.0.249.0 +</h1>
</div>
<div id="prePage" class="page" style="text-align:center;">
<input id = "nickInput" type="text" class="itext" placeholder="输入昵称" style="margin-top:150px;" />
<input id = "enter" type="button" class="ibutton" value="开始聊天" />
</div>
<div id="mainPage" class="page" style="display:none">
<div>
<div class="talkLeft">
<div id="talkFrame" class="talkHistory">
</div>
</div>
<div class="talkRight">
<div id="onlineUsers"></div>
</div>
</div>
<div id="inputDiv" style="margin-top:50px">
<input placeholder="说点什么" id="message" class="itext" />
<input type="button" class="ibutton" value="发送" id="send" />
<input type="button" class="ibutton" value="退出" id="logout" />
<select style="float:right" id="chatWith" class="iselect">
<option>所有人</option>
</select>
</div>
</div>
</body>
</html>
网页端chat.js(监听各种消息,监听服务器异常断开等)
EVENT_TYPE = { "LOGIN":"LOGIN", "LOGOUT":"LOGOUT", "CHAT":"CHAT", "ERROR":"ERROR" }; $(document).ready(function() { var socket = null; var onlineUserMap = new zTool.SimpleMap(); var currentUser = null; var currentUserNick = null; if (typeof WebSocket === 'undefined') { $("#prePage").hide(); $("#errorPage").show(); } function appendMessage(msg) { $("#talkFrame").append("<div>" + msg + "</div>"); document.getElementById("talkFrame").scrollTop = document.getElementById("talkFrame").scrollHeight; } function formatUserTalkString(user) { return user.nick + " " + new Date().format("hh:mm:ss") + " "; } function formatUserTalkHisString(user, time) { return user.nick + " " + new Date(time).format("yyyy-MM-dd hh:mm:ss") + " "; } function reset() { if (socket) { socket.close(); } socket = null; onlineUserMap = null; currentUser = null; $("#onlineUsers").html(""); $("#talkFrame").html(""); $("#nickInput").val(""); $("#chatWith").val("<option>所有人</option>"); } function setChatWith(uid){ $("#chatWith").find(uid).attr("SELECTED","SELECTED"); } function updateOnlineUser() { var html = ["<div>在线用户(" + onlineUserMap.size() + ")</div>"]; if (onlineUserMap.size() > 0) { var users = onlineUserMap.values(); var number = users.length; for ( var i=0;i<number;i++) { html.push("<div>"); if (users[i].uid == currentUser.uid) { html.push("<b>" + users[i].nick + "(我)</b>"); } else { html.push(users[i].nick); } html.push("</div>"); } } $("#onlineUsers").html(html.join('')); } function updateChatWith() { var html = ["<option value=\"0\">所有人</option>"]; if (onlineUserMap.size() > 0) { var users = onlineUserMap.values(); var number = users.length; for ( var i=0;i<number;i++) { if (users[i].uid == currentUser.uid) { } else { html.push("<option value=\""+users[i].uid+"\">"); html.push(users[i].nick); html.push("</option>"); } } } $("#chatWith").html(html.join('')); } //enter chatroom $("#enter").click(function(event) { currentUserNick = $.trim($("#nickInput").val()); if ('' == currentUserNick) { alert('请先输入昵称'); return; } $("#prePage").hide(); $("#mainPage").show(); reset(); socket = io.connect('http://127.0.0.1'); onlineUserMap = new zTool.SimpleMap(); socket.on('connect', function () { socket.emit('message', JSON.stringify({ 'event' : EVENT_TYPE.LOGIN, 'values' : [currentUserNick] })); }); socket.on("disconnect",function(message){ $("#prePage").show(); $("#mainPage").hide(); close(); }); socket.on("message",function(message){ var mData = JSON.parse(message); if (mData && mData.event) { switch (mData.event) { // // user login // case EVENT_TYPE.LOGIN: var user = mData.values[0]; var users = mData.users; if (users && users.length) { var number = users.length; for (var i=0;i<number;i++) { onlineUserMap.put(users[i].uid, users[i]); if (mData.user.uid == users[i].uid && currentUser == null) { currentUser = users[i]; // // listen on private chat // socket.on(currentUser.uid,function(pmessage){ var pmData = JSON.parse(pmessage); if (pmData && pmData.event) { switch (pmData.event) { case EVENT_TYPE.CHAT: var content = pmData.values[0]; appendMessage(formatUserTalkString(pmData.user)); appendMessage("<span> </span>" + content); break; } } }); } } } // // get history message // var data = mData.historyContent; if (data && data.length) { var number = data.length; for ( var i=0;i<number;i++) { appendMessage(formatUserTalkHisString(data[i].user, data[i].time)); appendMessage("<span> </span>" + data[i].content); } appendMessage("<span class='gray'>==================以上为最近的历史消息==================</span>"); } updateOnlineUser(); updateChatWith(); appendMessage(formatUserTalkString(user) + "[进入房间]"); break; // // user logout // case EVENT_TYPE.LOGOUT: var user = mData.values[0]; onlineUserMap.remove(user.uid); updateOnlineUser(); updateChatWith(); appendMessage(formatUserTalkString(user) + "[离开房间]"); break; // // user public chat // case EVENT_TYPE.CHAT: var content = mData.values[0]; appendMessage(formatUserTalkString(mData.user)); appendMessage("<span> </span>" + content); break; case EVENT_TYPE.ERROR: appendMessage("[error state...]"); break; default: break; } } }); socket.on("error",function(){ appendMessage("[server encounts an error...]"); }); socket.on("close",function(){ appendMessage("[server is closed...]"); close(); }); }); //send a message $("#send").click(function(event) { var value = $.trim($("#message").val()); var to = $.trim($("#chatWith").val()); if (value) { $("#message").val(''); var data = JSON.stringify({ 'event' : EVENT_TYPE.CHAT, 'to' : to, 'values' : [value] }); socket.emit('message',data); } }); //logout $("#logout").click(function(event){ var data = JSON.stringify({ 'event' : EVENT_TYPE.LOGOUT, 'values' : [currentUser] }); socket.emit('message',data); $("#prePage").show(); $("#mainPage").hide(); }); });
运行方式sudo node app.js
附:zTool.js如下
(function (exports) { var SimpleMap = exports.SimpleMap = function() { this.map = {}; this.mapSize = 0; }; SimpleMap.prototype.put = function(key, value) { var oldValue = this.map[key]; this.map[key] = value; if (!oldValue) { this.mapSize++; } return (oldValue || value); }; SimpleMap.prototype.get = function(key) { return this.map[key]; }; SimpleMap.prototype.remove = function(key) { var v = this.map[key]; if (v) { delete this.map[key]; this.mapSize--; }; return v; }; SimpleMap.prototype.size = function() { return this.mapSize; }; SimpleMap.prototype.clear = function() { this.map = {}; this.mapSize = 0; }; SimpleMap.prototype.keySet = function() { var theKeySet = []; for (var i in this.map) { theKeySet.push(i); } return theKeySet; }; SimpleMap.prototype.values = function() { var theValue = []; for (var i in this.map) { theValue.push(this.map[i]); } return theValue; }; var CircleList = exports.CircleList = function(maxSize) { this.maxSize = (maxSize || 10); this.list = []; this.index = null; }; CircleList.prototype.clear = function() { this.list = []; this.index = null; }; CircleList.prototype.add = function(value) { if (null == this.index) { this.index = 0; } this.list[this.index++] = value; if (this.index == this.maxSize) { this.index = 0; } }; CircleList.prototype.values = function() { var theValue = []; if (null != this.index) { if (this.list.length == this.maxSize) { for (var i = this.index; i < this.maxSize; i++) { theValue.push(this.list[i]); } } for (var i = 0; i < this.index; i++) { theValue.push(this.list[i]); } } return theValue; }; })( (function(){ if(typeof exports === 'undefined') { window.zTool = {}; return window.zTool; } else { return exports; } })() );