node.js android 聊天,用Node.js实现多人多房间在线聊天程序
先看下效果:case 1:进入房间初始界面:默认进入房间「Lobby」,用户默认昵称 命名规则为 小可爱 x 号,当有用户进入房间时,系统消息会提醒「『用户昵称』已经进入『房间名』啦!」,当用户更换房间时,系统消息会提醒「房间已更改!」case 2:当前用户发送消息后,系统会发送给其他用户,发送消息的用户的聊天消息。注意:当前用户发的消息,是直接插到在当前用户的聊天界面的,此时,会将 当前用户的聊
先看下效果:
case 1:进入房间初始界面:默认进入房间「Lobby」,用户默认昵称 命名规则为 小可爱 x 号,当有用户进入房间时,系统消息会提醒「『用户昵称』已经进入『房间名』啦!」,当用户更换房间时,系统消息会提醒「房间已更改!」
case 2:当前用户发送消息后,系统会发送给其他用户,发送消息的用户的聊天消息。
注意:当前用户发的消息,是直接插到在当前用户的聊天界面的,此时,会将 当前用户的聊天消息 通过 WebSocket 发送给服务器,服务器再发送给其他用户界面。
case 3:更改昵称,使用命令 「/nick 『用户名』」来更改用户昵称,同时系统广播给其他用户
case 4:更换房间,使用命令「/join 『房间名』」来更换房间,系统只提醒更改房间的用户,同时,进入新房间时,系统会通知当前用户,该房间有哪些用户。
注意:当一个房间没有用户时,该房间会被自动清除掉,除非用户再新建一个。
技术重点:使用 Socket.IO 处理跟聊天相关的消息
项目目录如下:
package.json:
开发步骤:
第一步:创建HTTP服务器——server.js:
//提供HTTP服务端和客户端功能
let http=require("http")
//提供文件系统功能
let fs=require("fs")
//提供文件系统路径功能
let path=require("path")
//根据文件扩展名得出MIME类型
let mime=require("mime")
let cache={}
//创建 HTTP 服务器
let server=http.createServer((request,response)=>{
let filePath=false
if(request.url==='/'){
filePath='public/index.html'
}else{
filePath='public'+request.url
}
let absPath='./'+filePath
//返回静态文件
serveStatic(response,cache,absPath)
})
//启动 http 服务器
//param:端口,callback
server.listen(3000,()=>{
console.log("服务器已启动")
})
//加载一个Socket.IO
let charServer=require("./lib/chat_server")
charServer.listen(server)
//404
function send404(response) {
response.writeHead(404,{"Content-Type":"text/plain"})
response.write("Ooops,找不到页面啦!")
response.end()
}
//发送文件
function sendFile(response,filePath,fileContents) {
// response.writeHead(200,{"Content-Type":mime.lookup(path.basename(filePath))})
response.writeHead(200,{"Content-Type":mime.getType(path.basename(filePath))})
response.end(fileContents)
}
//静态文件处理
function serveStatic(response,cache,absPath) {
//如果文件缓存在内存中,则从内存中返回文件
if(cache[absPath]){
sendFile(response,absPath,cache[absPath])
}else{
fs.exists(absPath,(exists)=>{
if(exists){
fs.readFile(absPath,(err,data)=>{
if(err){
send404(response)
}else{
//存进内存中
cache[absPath]=data
sendFile(response,absPath,data)
}
})
}else{
send404(response)
}
})
}
}
第二步:设置 Socket.IO 服务器
let socketio=require("socket.io")
let io
//默认一人
let guestNumber=1
let nickNames={}
//曾用名
let namesUsed=[]
//当前聊天室
let currentRoom={}
//启动Socket.IO服务器=================
exports.listen=(server)=>{
//启动SocketIO服务器,允许它搭载在已有的HTTP服务器上
io=socketio.listen(server)
// console.log(io,"chat_server15")
//限制socketio向控制台输出的日志详细程度
// io.set("log level",1)
io.sockets.on("connection",(socket)=>{
// //初始化
// guestNumber=1
// namesUsed=[]
// currentRoom={}
//assignGuestName: 在用户连接的时候,给他一个名字
//该 guestNumber 是新的未有昵称联系的 昵称number
// console.log(guestNumber)
guestNumber=assignGuestName(socket,guestNumber,nickNames,namesUsed)
//在给名字后,把他默认分配到Lobby聊天室里
joinRoom(socket,"Lobby")
//处理用户消息
handleMessageBroadcasting(socket,nickNames)
//处理用户更换昵称
handleNameChangeAttempts(socket,nickNames,namesUsed)
//处理用户创建/更换聊天室
handleRoomJoining(socket)
//用户发送请求时,向用户提供已经被创建的聊天室的列表
socket.on("rooms",()=>{
// console.log(nickNames)
let realRooms={}
//将用户id给过滤掉
// console.log(io.sockets.adapter.rooms)
for(let room in (io.sockets.adapter.rooms)){
if(!nickNames[room]){
realRooms[room]=1
}
}
// console.log(io.sockets.adapter.rooms)
// console.log(realRooms)
// socket.emit("rooms",io.sockets.adapter.rooms)
socket.emit("rooms",realRooms)
})
//用户断开连接后,清除
handleClientDisconnection(socket,nickNames,namesUsed)
})
}
//分配用户昵称==================================
function assignGuestName(socket,guestNumber,nickNames,namesUsed) {
let name="小可爱 "+guestNumber+" 号"
// 将用户昵称跟客户端连接ID相关联
nickNames[socket.id]=name
// 发送昵称给客户端,让用户看到他们自己的昵称
socket.emit("nameResult",{
success:true,
name:name
})
// 存放已经被占用的昵称
namesUsed.push(name)
// 增加昵称计数器
return guestNumber+1
}
//进入聊天室====================
function joinRoom(socket,room) {
// console.log(socket,room,"chat_server56")
// 让用户加入房间
// console.log(socket)
// console.log("===============")
socket.join(room)
// console.log(socket)
// 记录当前用户所在的房间
currentRoom[socket.id]=room
// 让用户知道他们进入了新房间
socket.emit("joinResult",{room:room})
// console.log(room)
// (不包括自己)通知该房间的其他用户,有新用户进入了该房间
// socket.broadcast.to(room).emit("message",{
socket.broadcast.in(room).emit("message",{
text:nickNames[socket.id]+" 已经进入「"+room+"」啦!"
})
// (包括自己)通知该房间的其他用户,有新用户进入了该房间
// socket.broadcast.in(room).emit("message",{
// text:nickNames[socket.id]+" 已经进入「"+room+"」啦!"
// })
// 确认有哪些用户在该房间里
// let usersInRoom=io.sockets.clients(room)
// 是一个对象,而不是数组了
let usersInRoom = io.sockets.adapter.rooms[room];
// console.log(io.sockets.adapter.rooms)
// 当用户数量大于1时,收集该房间里所有用户的昵称信息
let roomInfo=""
// console.log(usersInRoom)
if(usersInRoom&&usersInRoom.length>1){
roomInfo="你当前所处的房间「"+room+"」有如下的成员:"
// console.log(usersInRoom,nickNames)
let userArray=Object.keys(usersInRoom.sockets)
// console.log(userArray)
// for(let index in usersInRoom){
for(let index in userArray){
// let userSocketId=usersInRoom[index].id
let userSocketId=userArray[index]
//排除当前用户
if(userSocketId!==socket.id){
//不能在第一个用户如 aa,前面加「,」
if(index>0){
roomInfo+=","
}
roomInfo+=nickNames[userSocketId]
}
}
roomInfo+="。"
// 将房间信息发送给当前用户
socket.emit("message",{text:roomInfo})
}
}
//处理 昵称变更请求
function handleNameChangeAttempts(socket,nickNames,namesUsed){
// 添加 更换昵称的监听器
socket.on("nameChange",(name)=>{
//说明是以Guest开头的
if(name.indexOf("Guest")===0){
socket.emit("nameResult",{
success:false,
message:"名字不能以「Guest」开头,请更换其他昵称重试!"
})
}else{
//说明该昵称未被使用过
if(namesUsed.indexOf(name)===-1){
//将之前的昵称保存
let previousName=nickNames[socket.id]
//当系统自动创建名字的时候,已将该昵称放进namesUsed数组中,现在需要查找该昵称位置
let previousNameIndex=namesUsed.indexOf(previousName)
//将新名字加入namesUsed
namesUsed.push(name)
// 更新名字
nickNames[socket.id]=name
// 删除旧名字,让其他用户使用(除Guest开头)
delete namesUsed[previousNameIndex]
// 通知用户名称更换成功
socket.emit("nameResult",{
success:true,
name:name
})
// 通知当前房间,当前用户更改昵称的消息
// socket.broadcast.to(currentRoom[socket.id]).emit("message",{
socket.broadcast.in(currentRoom[socket.id]).emit("message",{
text:"「"+previousName+"」已更改成「"+name+"」。"
})
}else{
socket.emit("nameResult",{
success:false,
message:"该名称已被他人使用,请更换其他昵称重试!"
})
}
}
})
}
//用户 发送聊天消息
function handleMessageBroadcasting(socket,nickNames) {
//接收前端发过来的message
socket.on("message",(message)=> {
// console.log(message.room,nickNames[socket.id]+":"+message.text)
//然后向该房间转发该用户的信息
//注意:socket.broadcast.to是除了当前用户,向其他用户发送消息
//所以当前用户自己是看不到「xxx:yyy」这样的,只能看到「yyy」这样的
// socket.broadcast.to(message.room).emit("message",{
//如果自己想看到自己昵称「xxx:yyy」的话,把 to 改成 in
socket.broadcast.in(message.room).emit("message",{
text:nickNames[socket.id]+":"+message.text
})
})
}
//让用户加入已有的房间,如果没有房间,则创建一个
function handleRoomJoining(socket) {
socket.on("join",(room)=>{
//离开当前房间
socket.leave(currentRoom[socket.id])
console.log(room)
//加入新房间,没有房间则创建新的
joinRoom(socket,room.newRoom)
})
}
//用户断开连接后,清空用户数据
function handleClientDisconnection(socket) {
socket.on("disconnect",()=>{
let nameIndex=namesUsed.indexOf(nickNames[socket.id])
delete namesUsed[nameIndex]
delete nickNames[socket.id]
})
}
第三步:向服务器发送用户的消息和昵称,以及变更昵称和房间的请求——chat.js
//采用es6 写法
class Chat{
constructor(socket){
this.socket=socket
}
//发送消息
sendMessage(room,text){
let message={
room:room,
text:text
}
//发送给服务端
// console.log(this.socket)
this.socket.emit("message",message)
}
//变更房间
changeRoom(room){
this.socket.emit("join",{
newRoom:room
})
}
// 处理聊天命令
// 注意使用命令一般是 /xxx ttttt,xxx后务必要加上空格!否则无效
processCommand(command){
//按照command中的空格,给分成数组
let wordsArray=command.split(" ")
// 去掉 /
command=wordsArray[0].substring(1,wordsArray[0].length)
let message=false
switch (command){
case "join":
wordsArray.shift()
//连接成字符串
let room=wordsArray.join(" ")
this.changeRoom(room)
break
case "nick":
wordsArray.shift()
//连接成字符串
let name=wordsArray.join(" ")
this.socket.emit("nameChange",name)
break
default:
message="无法识别该命令"
break
}
return message
}
}
第四步:显示其他用户的消息,以及可使用房间的列表——chat_ui.js
//客户端初始化
$(document).ready(()=>{
// let socket=io.connect()
let socket=io()
// console.log(socket)
let chatApp=new Chat(socket)
// console.log(chatApp)
// 显示改变名字的结果
socket.on("nameResult",(result)=>{
let message
if(result.success){
message="你已更名为「"+result.name+"」。"
}else{
message=result.message
}
$("#messages").append(credible(message))
})
// 显示房间改变的结果
socket.on("joinResult",(result)=>{
// console.log(result,"joinResult")
$("#room").text(result.room)
$("#messages").append(credible("房间已更改!"))
})
// 显示接收到的消息
socket.on("message",(message)=>{
// console.log(message)
let newElement=$("
$("#messages").append(newElement)
})
// 显示可用房间列表
socket.on("rooms",(rooms)=>{
$("#room-list").empty()
console.log(rooms)
for(let room in rooms){
// room=room.substring(1,room.length)
if(room!==""){
$("#room-list").append(doubtful(room))
}
}
$("#room-list div").click(()=>{
chatApp.processCommand("/join"+$(this).text())
$("#send-message").focus()
})
})
// 定期请求可用房间列表
setInterval(()=>{
socket.emit("rooms")
// console.log("rooms1111")
},6000)
$("#send-message").focus()
$("#send-form").submit(()=>{
processUserInput(chatApp,socket)
return false
})
})
//显示可疑的文本
function doubtful(message) {
//text返回其文本内容
return $("
}
//显示守信的文本(斜体显示)
function credible(message) {
return $("
"+message+"")}
//处理原始输入
function processUserInput(chatApp,socket) {
let message=$("#send-message").val()
let systemMessage
// /开头视为命令
if(message.charAt(0)==="/"){
systemMessage=chatApp.processCommand(message)
if(systemMessage){
$("#messages").append(credible(systemMessage))
}
}else{
chatApp.sendMessage($("#room").text(),message)
// console.log(message)
$("#messages").append(doubtful(message))
$("#messages").scrollTop($("#messages").prop("scrollHeight"))
}
$("#send-message").val("")
}
第五步:npm install 安装依赖包,然后在终端输入 node server.js ,打开浏览器,输入 url:localhost:3000 即可运行本程序。
欢迎关注本公众号:
image
(完)
更多推荐
所有评论(0)