实现一个简单的tcp聊天室

65 篇文章 0 订阅
45 篇文章 1 订阅

这里写一个简单的聊天室,用来理解和使用net的api,方便自己理解,

聊天室设计

可以展示当前在线人数,在线用户,使用指定命令进行操作

  • 默认情况下用户应该是匿名状态
  • 通过关机命令改名:r: newName
  • 支持显示在线用户列表 l
  • 广播功能: b: xxx
  • 私聊的功能: p:nickName: msg

创建服务器

const net = require('net')
const clients = {} // 保存用户
// 保存所有的用户名
let nickNames = []

const server = net.createServer(function (scoket) {
  server.maxConnections = 20
  // 创建用户ID


  // 用户名
  scoket.setEncoding('utf8')
  scoket.on('data', chunk => {
  })
})
let port = 3000
server.listen(port, 'localhost', () => {
  console.log(`server start ${port}`)
})

// 当服务端发生错误时,会调用监听函数
server.on('error', err => {
  if (err.code == 'EADDRINUSE') server.listen(++port)
  if (err) console.log(err)
})

// 当服务器错误时
server.on('close', () => {
  console.log('服务端关闭')
})

设置默认参数

// 默认情况下用户应该是匿名状态
// 通过关机命令改名:r: newName
// 支持显示在线用户列表 l
// 广播功能: b: xxx
// 私聊的功能: p:nickName: msg

const net = require('net')
const clients = {} // 保存用户
// 保存所有的用户名
const server = net.createServer(function (scoket) {
  server.maxConnections = 20
  // 创建用户ID
  let key = scoket.remoteAddress + scoket.remotePort
  clients[key] = {nickName: '匿名', scoket}
  nickNames.push(key)

  server.getConnections((err, count) => {
    scoket.write(`欢迎来到聊天室 当前用户数量:${count}个, 请输入用户名\r\n`);
  })

  // 用户名
  scoket.setEncoding('utf8')
  scoket.on('data', chunk => {
    // 处理换行回车的问题
    chunk = chunk.replace(/(\r\n)|(\r)/, '')
    let chars = chunk.split(':')

    switch (chars[0]) {
      case 'r':
        //改名
        rename(key, chars[1])
        break;
      case 'l':
        //展示用户列表
        showUsers(key, scoket)
        break;
      case 'b':
        //广播
        broadcast(key, chars[1])
        break;
      case 'p':
        //私聊
        private(chars[1], chars[2], key)
        break;
      default:
        scoket.write('您输入的问题无法解毒,请重新输入\r\n')
        break;
    }
  })
})
let port = 3000
server.listen(port, 'localhost', () => {
  console.log(`server start ${port}`)
})

// 当服务端发生错误时,会调用监听函数
server.on('error', err => {
  if (err.code == 'EADDRINUSE') server.listen(++port)
  if (err) console.log(err)
})

// 当服务器错误时
server.on('close', () => {
  console.log('服务端关闭')
})

完成设置(修改)昵称

/**
 * @description 重命名
 * @param {string} id
 * @param {string} name
 */
function rename (id, name) {
  // 如果存在这个用户,就赋值
  let res
  // 判断是否又当前用户名
  nickNames.forEach(key => {
    if (clients[key].nickName === name) res = true
  })
  // 如果当前用户名已经存在,提示用户重新填写用户名
  // 如果目前没有存在该用户名,则让用户修改
  if (res) return clients[id].scoket.write('当前用户名已经存在,请重新输入\r\n')
  else clients[id].nickName = name
  clients[id].scoket.write(`您修改昵称成功,当前昵称${name}\r\n`)
}
// 链接服务器:输入 r:new name 
// 服务器会返回给你结果

实现广播逻辑

/**
 * @description 广播消息
 * @param {*} id 用户唯一标识
 * @param {*} msg 发送的消息
 */
function broadcast(id, msg) {
  // 广播消息的用户昵称
  let name = clients[id].nickName
  nickNames.forEach(key => {
    if (key !== id) {
      clients[key].scoket.write(`${name}: ${msg} \r\n`)
    }
  })
}

// 链接服务器以后输入: b:testMsg 
// 其他用户就能收到消息

实现展示用户列表

/**
 * 展示用户列表
 * @param {string} id 用户位移表示
 */
function  showUsers(id, scoket) {
  let str = '当前在线用户:\r\n'
  let nameList = ''
  // 遍历 拿到所有的
  nickNames.forEach(key => {
    if (key !== id) {
      nameList += `${clients[key].nickName}\r\n`
    }
  })
  // 返回用户列表
  scoket.write(str + nameList)
}
// 链接服务器,输入: l 回车
// 展示当前用户列表

实现私聊

/**
 * @description 私聊
 * @param {string} nickname 私聊的目标用户
 * @param {string} msg 发送的消息
 */
function private(nickname, msg, id) {
  let users
  // 从连接列表拿到当前用的昵称
  let form = clients[id].nickName
  // 找到当前用户要@的人,这里默认用户名不能重复
  nickNames.forEach(name => {
    if(clients[name].nickName === nickname) {
      users = clients[name]
    }
  })
  // 发送消息给用户
  users.scoket.write(`${form}: ${msg}\r\n`)
}
// 测试
// 链接当前服务,输入: p:xxx:我们走私吧

这里是完整的代码

// 默认情况下用户应该是匿名状态
// 通过关机命令改名:r: newName
// 支持显示在线用户列表 l
// 广播功能: b: xxx
// 私聊的功能: p:nickName: msg

const net = require('net')
const clients = {} // 保存用户
// 保存所有的用户名
let nickNames = []
// 当客户端连接服务室 辉触发回调函数,默认提示输入用户名,就可以通信了
// 自己的说的话,不应该通知自己,应该通知别人呢
/**
 * @description 重命名
 * @param {string} id
 * @param {string} name
 */
function rename (id, name) {
  // 如果存在这个用户,就赋值
  let res
  // 判断是否又当前用户名
  nickNames.forEach(key => {
    if (clients[key].nickName === name) res = true
  })
  // 如果当前用户名已经存在,提示用户重新填写用户名
  // 如果目前没有存在该用户名,则让用户修改
  if (res) return clients[id].scoket.write('当前用户名已经存在,请重新输入\r\n')
  else clients[id].nickName = name
  clients[id].scoket.write(`您修改昵称成功,当前昵称${name}\r\n`)
}
/**
 * @description 私聊
 * @param {string} nickname 私聊的目标用户
 * @param {string} msg 发送的消息
 */
function private(nickname, msg, id) {
  let users
  // 从连接列表拿到当前用的昵称
  let form = clients[id].nickName
  // 找到当前用户要@的人,这里默认用户名不能重复
  nickNames.forEach(name => {
    if(clients[name].nickName === nickname) {
      users = clients[name]
    }
  })
  // 发送消息给用户
  users.scoket.write(`${form}: ${msg}\r\n`)
}

/**
 * 展示用户列表
 * @param {string} id 用户位移表示
 */
function  showUsers(id, scoket) {
  let str = '当前在线用户:\r\n'
  let nameList = ''
  // 遍历 拿到所有的
  nickNames.forEach(key => {
    if (key !== id) {
      nameList += `${clients[key].nickName}\r\n`
    }
  })
  // 返回用户列表
  scoket.write(str + nameList)
}

/**
 * @description 广播消息
 * @param {*} id 用户唯一标识
 * @param {*} msg 发送的消息
 */
function broadcast(id, msg) {
  // 广播消息的用户昵称
  let name = clients[id].nickName
  nickNames.forEach(key => {
    if (key !== id) {
      clients[key].scoket.write(`${name}: ${msg} \r\n`)
    }
  })
}

const server = net.createServer(function (scoket) {
  server.maxConnections = 20
  // 创建用户ID
  let key = scoket.remoteAddress + scoket.remotePort
  clients[key] = {nickName: '匿名', scoket}
  nickNames.push(key)

  server.getConnections((err, count) => {
    scoket.write(`欢迎来到聊天室 当前用户数量:${count}个, 请输入用户名\r\n`);
  })

  // 用户名
  scoket.setEncoding('utf8')
  scoket.on('data', chunk => {
    // 处理换行回车的问题
    chunk = chunk.replace(/(\r\n)|(\r)/, '')
    let chars = chunk.split(':')

    switch (chars[0]) {
      case 'r':
        //改名
        rename(key, chars[1])
        break;
      case 'l':
        //展示用户列表
        showUsers(key, scoket)
        break;
      case 'b':
        //广播
        broadcast(key, chars[1])
        break;
      case 'p':
        //私聊
        private(chars[1], chars[2], key)
        break;
      default:
        scoket.write('您输入的问题无法解毒,请重新输入\r\n')
        break;
    }
  })
})
let port = 3000
server.listen(port, 'localhost', () => {
  console.log(`server start ${port}`)
})

// 当服务端发生错误时,会调用监听函数
server.on('error', err => {
  if (err.code == 'EADDRINUSE') server.listen(++port)
  if (err) console.log(err)
})

// 当服务器错误时
server.on('close', () => {
  console.log('服务端关闭')
})

结束

通过上边几十行代码,实现了一个简单的tcp聊天室,聊天室支持私聊,修改昵称,群聊,查看在线用户,

通过上述的练习,对net的基本功能,流有了一定的认识

tcp是没有状态的,所以需要我们自己定义规则,然后按照自己定义的规则做一定的处理,http作为应用层,就会有很多可以操作的地方,如cookie,缓存,分片等等

  • 1
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值