项目结构
前端
- html
<!DOCTYPE html>
<html>
<head>
<title>chat</title>
<link rel="stylesheet" href="/stylesheets/style.css">
</head>
<body>
<div id="content">
<div id="room"></div>
<div id="room-list"></div>
<div id="messages"></div>
<form id="send-form">
<input id="send-message">
<input type="submit" value="Send" id="send-button">
<div id="help">
Chat commands:
<ul>
<li>Change nickname:<code>/nick{username}</code></li>
<li>Join/create room:<code>/join{room name}</code></li>
</ul>
</div>
</form>
</div>
<script type="text/javascript" src='/socket.io/socket.io.js'></script>
<script type="text/javascript" src='http://code.jquery.com/jquery-1.8.0.min.js'></script>
<script type="text/javascript" src='/javascripts/chat.js'></script>
<script type="text/javascript" src='/javascripts/chat_ui.js'></script>
</body>
</html>
- CSS
body {
padding: 50px;
font:14px 'Lucida Grande'. Helvetica, Arial. sans-serif;
}
a {
color: #00B7FF;
}
#content {
width: 800px;
margin-left: auto;
margin-right: auto;
}
#room {
background-color: #ddd;
margin-bottom: 1em;
}
#messages {
width: 690px;
height: 300px;
overflow: auto;
background-color: #eee;
margin-bottom: 1em;
margin-right: 10px;
}
#room-list{
float: right;
width: 100px;
height: 300px;
overflow: auto;
}
#room-list div {
border-bottom: 1px solid #eee;
}
#room-list div:hover {
background-color: #ddd;
}
#send-message {
width: 700px;
margin-bottom: 1em;
margin-right: 1em;
}
#help {
font:10px 'Lucida Grande'. Helvetica, Arial. sans-serif;
}
- JS
chat.js
var Chat = function(socket) {
this.socket = socket;
};
Chat.prototype.sendMessage = function(room, text){
var message = {
room:room,
text:text
};
this.socket.emit('message', message);
};
Chat.prototype.changeRoom = function(room){
this.socket.emit('join', {
newRoom:room
});
};
Chat.prototype.processCommand = function(command){
var words = command.split(' ');
var command = words[0].substring(1, words[0].length).toLowerCase();
var message = false
switch (command) {
case 'join':
words.shift();
var room = words.join(" ");
this.changeRoom(room);
break;
case 'nick':
words.shift();
var name = words.join(' ');
this.socket.emit('nameAttempt', name);
break;
default:
message = 'Unrecognized command.';
break;
}
return message;
};
chat_ui.js
function divEscapedContentElement(message) {
return $('<div></div>').text(message);
}
function divSystemContentElement(message) {
return $('<div></div>').html('<i>' + message + '</i>');
}
function processUserInput(chatApp, socket) {
var message = $('#send-message').val();
var systemMessage;
if (message.charAt(0) == '/') {
systemMessage = chatApp.processCommand(message);
if (systemMessage) {
$('#messages').append(divSystemContentElement(systemMessage));
}
} else {
chatApp.sendMessage($('#room').text(), message);
$('#messages').append(divEscapedContentElement(message));
$('#messages').scrollTop($('#messages').prop('scrollHeight'));
}
$('#send-message').val('');
}
var socket = io.connect();
$(document).ready(function() {
var chatApp = new Chat(socket);
console.log(socket);
socket.on('nameResult', function(result) {
var message;
if (result.success) {
message = 'you are now knowd as ' + result.name + '.';
} else {
message = result.message;
}
$('#messages').append(divSystemContentElement(message));
});
socket.on('joinResult', function(result) {
$('#room').text(result.room);
$('#messages').append(divSystemContentElement('Room Changed'));
});
socket.on('message', function(message) {
var newElement = $('<div></div>').text(message.text);
$('#messages').append(newElement);
});
socket.on('rooms', function(rooms) {
$('#room-list').empty();
for(var room in rooms) {
room = room.substring(1, room.length);
if (room != '') {
$('#room-list').append(divEscapedContentElement(room));
}
}
$('#room-list div').click(function() {
chatApp.processCommand('/join' + $(this).text());
$('#send-message').focus();
});
});
setInterval(function() {
socket.emit('rooms');
}, 1000);
$('#send-message').focus();
$('#send-form').submit(function() {
processUserInput(chatApp, socket);
return false;
});
});
服务端
app.js
var http = require('http');
var fs = require('fs');
var path = require('path');
var mime = require('mime');
var cache = {};
var chatServer = require('./lib/chat_server')
var server = http.createServer(function(req,res) {
var filePath = '';
if(req.url == '/') {
filePath = 'public/index.html';
} else {
console.log(req.url);
filePath = 'public' + req.url;
}
var absPath = './' + filePath;
console.log(absPath);
serverStatic(res, cache, absPath);
});
server.listen(3000, function () {
console.log('localhost:3000...');
});
chatServer.listen(server);
function send404(res) {
res.writeHead(404,{'content-type':'text/plain'});
res.write('Error:404:resource not found');
res.end();
}
function sendFile(res, filepath, fileContents) {
res.writeHead(200,{
'content-type':mime.lookup(path.basename(filepath))
});
res.end(fileContents);
}
function serverStatic(res, cache, absPath) {
if (cache[absPath]) {
sendFile(res, absPath, cache[absPath]);
} else {
fs.exists(absPath, function(exits) {
if(exits) {
fs.readFile(absPath, function(err, data) {
if (err) {
send404(res);
} else {
cache[absPath] = data;
sendFile(res, absPath, data);
}
});
} else {
send404(res);
}
});
}
}
chat_server.js
var socketio = require('socket.io');
var io;
var guestNumber = 1;
var nickNames = {};
var namesUsed = [];
var currentRoom = {};
exports.listen = function (server) {
io = socketio.listen(server);
io.set('log level', 1);
io.sockets.on('connection', function (socket) {
guestNumber = assignGuestName(socket, guestNumber, nickNames, namesUsed);
joinRoom(socket, 'Lobby');
handleMessageBroadcasting(socket, nickNames);
handleNameChangeAttempts(socket, nickNames, namesUsed);
handleRoomJoining(socket);
socket.on('rooms', function () {
socket.emit('rooms', io.sockets.manager.rooms);
});
handleClientDisconnection(socket, nickNames, namesUsed);
});
};
function assignGuestName(socket, guestNumber, nickNames, namesUsed) {
var name = 'Guest' + guestNumber;
nickNames[socket.id] = name;
socket.emit('nameResult',{
success:true,
name:name
});
namesUsed.push(name);
return guestNumber + 1;
}
function joinRoom(socket, room) {
socket.join(room);
currentRoom[socket.id] = room;
socket.emit('joinResult',{
room:room
});
socket.broadcast.to(room).emit('message', {
text:nickNames[socket.id] + ' has joined ' + room + '.'
});
var usersInRoom = io.sockets.clients(room);
if (usersInRoom.length > 1 ) {
var usersInRoomSummary = 'Users currently in ' + room +":";
for(var index in usersInRoom) {
var userSocketId = usersInRoom[index].id;
if (userSocketId != socket.id) {
if (index > 0) {
usersInRoomSummary += ',';
}
usersInRoomSummary += nickNames[userSocketId];
}
}
usersInRoomSummary += '.';
socket.emit('message', {text:usersInRoomSummary});
}
}
function handleNameChangeAttempts(socket, nickNames, namesUsed) {
socket.on('nameAttempt', function(name) {
if (name.indexOf('Guest') == 0 ) {
socket.emit('nameResult', {
success:false,
message:'Names cannot begin with "Guest".'
});
} else {
if (namesUsed.indexOf(name) == -1) {
var prename = nickNames[socket.id];
var prenameindex = namesUsed.indexOf(prename);
namesUsed.push(name);
nickNames[socket.id] = name;
delete namesUsed[prenameindex];
socket.emit('nameResult', {
success:true,
name:name
});
socket.broadcast.to(currentRoom[socket.id]).emit('message', {
text:prename + " is now known as " + name + '.'
});
} else {
socket.emit('nameResult', {
success: false,
message:'That name is already in use.'
});
}
}
});
}
function handleMessageBroadcasting(socket,nickNames) {
socket.on('message', function (message) {
socket.broadcast.to(message.room).emit('message', {
text:nickNames[socket.id] + ':' + message.text
});
});
}
function handleRoomJoining(socket) {
socket.on('join', function(room) {
socket.leave(currentRoom[socket.id]);
joinRoom(socket, room.newRoom);
});
}
function handleClientDisconnection(socket) {
socket.on('disconnect', function() {
var nameIndex = namesUsed.indexOf(nickNames[socket.id]);
delete namesUsed[nameIndex];
delete nickNames[socket.id];
})
}