基于TCP的聊天室

本文共--字 阅读约--分钟 | 浏览: -- Last Updated: 2019-11-21

TCP是一种传输层协议,它可以让你讲数据从一台计算机完整有序地传输到另一台计算机。

Node HTTP服务器是构建于Node TCP服务器之上的,从编程角度来说,也就是Node中的http.Server继承自net.Server (net是TCP模块)。还有邮件客户端(SMTP/IMAP/POP)、聊天程序(IRC/XMPP)以及远程shell(SSH)等都基于TCP协议。

TCP特性

面向连接

TCP的首要特性就是它是面向连接的,而TCP协议所基于的IP协议却是面向无连接的,IP是基于数据包的传输,这些数据包都是独立进行传输的,送达的顺序也是无序的。

使用IP协议意味着数据包送达时是无序的,这些数据包不属于任何的数据流或者连接,那么当使用TCP/IP和服务器建立连接后,是怎样做到让数据包送达时是有序的呢?

因为当在TCP连接内进行数据传递时,发送的IP数据包包含了标识该连接以及数据流顺序的信息。

面向字节

TCP对字符和字符编码是完全无知的,TCP允许数据以ASCII字符(每个字符一个字节)或者Unicode(每个字符四个字节)进行传输。

可靠性

由于TCP是基于底层不可靠的服务,因此,它必须要基于确认和超时实现一系列的机制来达到可靠性的要求,当数据发送出去后,发送方就会等待一个确认消息(表示数据包已经收到的简短的确认消息),如果过了指定的窗口时间,还未收到确认消息,发送方就会对数据进行重发。

流控制

要是两台互相通信的计算机中,有一台速度远快于另一台的话,TCP会通过一种叫流控制的方式来确保两点之间传输数据的平衡。

拥堵控制

TCP有一种内置的机制能够控制数据包的延迟率以及丢包率不会太高,以此来确保服务的质量(QoS)。比如,和流控制机制能够避免发送方压垮接受方一样,TCP会通过控制数据包的传输速率来避免拥堵的情况。

实现

需求:

1、成功连接到服务器后,服务器会显示欢迎信息,并要求输入用户名,同时还会告诉你当前还有多少其他客户端也连接到了该服务器。

2、输入用户名,按下回车键,就认为成功连接上了

3、连接后,就可以通过输入信息再按下回车键,来向其他客户端进行消息的收发。

// telnet 命令退出 ctrl + ] 然后输入 quit退出  不会触发close事件
// ctrl + ] 然后 ctrl + c关闭连接 会触发close事件。
// 使用 telnet 127.0.0.1 3000 加入聊天室
var net = require('net');

var count = 0; // 用来追踪连接数
var users = {}; // 记录设置了昵称的用户

var server = net.createServer(function(conn) {
  conn.write(
    '\n > welcome to \033[92mnode-chat\033[39m!'
    + '\n > ' + count + ' other people are connected at this time.'
    + '\n > please write your name and press enter:'
  )

  count++;
  
  var nickname;
  // 接受用户输入
  conn.setEncoding('utf8');
  conn.on('data', function(data) {
    // 需要注意的是接受到的是一个Buffer 所以需要设置编码
    data = data.replace('\r\n', ''); // 删除回车符
    if(!nickname) { // 如果还没有设置昵称的情况下
      if(users[data]) {
        // 用户输入的昵称已经被注册
        conn.write('\033[90m> nickname already in use. try again:\033[39m ');
        return;
      } else {
        nickname = data;
        // 记录用户的连接
        users[nickname] = conn;
        // 广播给所有已经连接的用户,不排除自己
        broadcast('\033[90m > ' + nickname + ' joined the room\033[39m \n')
      }
    } else {
      // 如果设置了昵称 则之后的输入视为聊天消息 广播给除自己外的其他人
      broadcast('\033[96m > ' + nickname + ':\033[39m' + data + '\n', true)
    }
  })

  // 当客户端关闭连接时 计数器要递减
  conn.on('close', function() {
    count--;
    // 当有人断开连接时,需要清除users中对应的元素
    delete users[nickname];
    // 广播给其他人知道
    broadcast('\033[90m > ' + nickname + ' left the room\033[39m \n')
  })
  
  // 抽象广播方法
  // @params msg [String] 消息
  // @params exceptMyself [Boolean] 是否排除自己 
  function broadcast(msg, exceptMyself) {
    for(var n in users) {
      if(!exceptMyself || n != nickname) {
        // 为false时 不排除自己 在不排除自己时才向自己发送消息
        // 在排除自己时,向其他人发送消息
        users[n].write(msg);
      }
    }
  }
})

server.listen(3000, function() {
  console.log('\033[90m   server listening on: 3000\033[39m');
})