Nodejs conn.on方法疑问

Nodejs conn.on方法疑问

最近在学习nodejs,是跟着了不起的Node.js这本书走的,在看到书中第六小节“TCP”时有个基于TCP的聊天室的示例,index.js具体代码如下:

var net = require('net');

/**
 * 追踪连接数
 */
var count = 0,
    users = {};

var server = net.createServer(function (conn) {

    conn.setEncoding('utf8');

    //当前连接的昵称
    var nickname;

    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++;

    conn.on('data', function (data) {

        //删除回车符
        data = data.replace('\r\n','');

        //接收到第一份数据应当是用户输入的昵称
        if (!nickname) {
            if (users[data]) {
                conn.write('\033[93m> nickname already in use. try again:\033[39m ');
                return;
            } else {
                nickname = data;
                users[nickname] = conn;

                for (var i in users) {
                    users[i].write('\033[90m > ' + nickname + ' joinen the room\033[39m\n');
                }
            }
        }

        console.log(data);
    });


    conn.on('close', function () {
        count--;
    });
});

/**
 * 监听
 */
server.listen(3000, function () {
    console.log('\033[96m server listening on *:3000\033[39m');
});

书上的示例效果是在cmd窗口执行node index.js之后,另起一个cmd窗口执行telnet 127.0.0.1 3000,之后在telnet窗口输入信息,服务器端的窗口就会出现相同的信息,书中截图如下所示:

但是,实际的显示效果确实这个样子的:

请问,为什么我的telnet端输入一个字符服务端就输出一个字符,而非与书中显示的输入一串字符结束后服务端才显示同样的字符串啊?是我在conn.on方法中的回调对data处理的方法不对吗?


4 回复

你的问题可能出在 telnet 客户端的行为上。通常,telnet 客户端会在每次输入一个字符后立即发送该字符,而不是等到你按下回车键。这导致服务器在每次接收到一个字符时都触发 conn.on('data') 事件,并且会立即输出接收到的字符。

为了验证这一点,你可以尝试使用其他客户端,例如 nc(Netcat),它更符合你的预期行为。

示例代码

下面是修改后的代码,以确保每个完整的消息被处理,而不仅仅是单个字符:

var net = require('net');

var count = 0,
    users = {};

var server = net.createServer(function (conn) {

    conn.setEncoding('utf8');

    // 当前连接的昵称
    var nickname;

    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++;

    let buffer = '';

    conn.on('data', function (data) {
        // 删除回车符
        data = data.replace('\r\n', '');

        // 将新数据添加到缓冲区
        buffer += data;

        // 检查是否已经接收完整的消息
        while (buffer.indexOf('\n') !== -1) {
            const [message, ...rest] = buffer.split('\n');
            buffer = rest.join('\n');

            if (!nickname) {
                if (users[message]) {
                    conn.write('\033[93m> nickname already in use. try again:\033[39m ');
                    return;
                } else {
                    nickname = message;
                    users[nickname] = conn;

                    for (var i in users) {
                        users[i].write('\033[90m > ' + nickname + ' joined the room\033[39m\n');
                    }
                }
            } else {
                console.log(message);
                for (var user in users) {
                    if (user !== nickname) {
                        users[user].write('\033[94m' + nickname + ': ' + message + '\033[39m\n');
                    }
                }
            }
        }
    });

    conn.on('close', function () {
        count--;
    });
});

// 监听
server.listen(3000, function () {
    console.log('\033[96m server listening on *:3000\033[39m');
});

解释

  1. 缓冲区 (buffer):我们引入了一个缓冲区来存储接收到的数据。每当有新的数据到达时,我们将这些数据追加到缓冲区中。

  2. 检查完整消息:我们通过检查缓冲区中是否存在换行符 \n 来判断是否接收到完整的消息。如果找到了换行符,我们就将缓冲区中的数据分割成多个消息,并逐个处理这些消息。

  3. 处理消息:当接收到完整的消息时,我们将其从缓冲区中移除,并进行相应的处理(例如,检查昵称是否重复,或者将消息转发给其他客户端)。

这样,即使 telnet 客户端每次只发送一个字符,服务器也会等待完整的消息(即用户按下回车键)后再进行处理。


telnet是按字符逐个发送的,也就是不需要按回车就直接发出了。。你可以下载一些socket助手工具来测试 ,不要使用telnet

我是按照书上使用的telnet来做的…我去寻个别的工具试试,谢谢了

你的问题在于 conn.on('data', ...) 回调函数中每次接收到客户端的数据时,都会打印出单个字符。这是因为 Telnet 默认每次发送一个字符,而你在服务器端的回调函数中没有进行缓冲或合并这些字符。

你可以通过在 conn.on('data', ...) 中使用一个缓冲区来解决这个问题。下面是一个改进后的代码示例:

var net = require('net');

// 追踪连接数
var count = 0,
    users = {};

var server = net.createServer(function (conn) {

    conn.setEncoding('utf8');

    // 当前连接的昵称
    var nickname;
    let buffer = '';

    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++;

    conn.on('data', function (data) {

        // 删除回车符并添加到缓冲区
        data = data.replace('\r\n','');
        buffer += data;

        // 接收到第一份数据应当是用户输入的昵称
        if (!nickname) {
            if (users[buffer]) {
                conn.write('\033[93m> nickname already in use. try again:\033[39m ');
                return;
            } else {
                nickname = buffer;
                users[nickname] = conn;

                for (var i in users) {
                    users[i].write('\033[90m > ' + nickname + ' joined the room\033[39m\n');
                }
            }
            buffer = '';  // 清空缓冲区
        }

        console.log(buffer);
    });

    conn.on('close', function () {
        count--;
    });
});

// 监听
server.listen(3000, function () {
    console.log('\033[96m server listening on *:3000\033[39m');
});

解释:

  1. Buffer变量:引入了一个名为 buffer 的变量,用于存储从客户端接收到的数据。
  2. Buffer更新:在每次接收到数据时,将新数据追加到 buffer 中。
  3. Buffer清空:当成功处理完昵称输入后,清空 buffer 以便接收新的数据。

这样,你就可以正确地处理完整的输入行,而不是每次接收到单个字符。

回到顶部