Introduction

In this guide we’ll create a basic chat application. It requires almost no basic prior knowledge of Node.JS or Socket.IO, so it’s ideal for users of all knowledge levels.

在本指南中,我们将创建一个基本的聊天应用程序。 它几乎不需要Node.JS或Socket.IO的基础知识,因此非常适合所有知识水平的用户。

Introduction

Writing a chat application with popular web applications stacks like LAMP (PHP) has normally been very hard. It involves polling the server for changes, keeping track of timestamps, and it’s a lot slower than it should be.

Sockets have traditionally been the solution around which most real-time chat systems are architected, providing a bi-directional communication channel between a client and a server.

This means that the server can push messages to clients. Whenever you write a chat message, the idea is that the server will get it and push it to all other connected clients.

使用LAMP(PHP)等流行的Web应用程序堆栈编写聊天应用程序通常非常困难。 它涉及轮询服务器的更改,跟踪时间戳,并且比预期的要慢得多。

传统上,套接字是构建大多数实时聊天系统的解决方案,可在客户端和服务器之间提供双向通信通道。

这意味着服务器可以将消息推送到客户端。 每当您编写聊天消息时,其想法都是服务器将获得该消息并将其推送到所有其他已连接的客户端。

The web framework

The first goal is to set up a simple HTML webpage that serves out a form and a list of messages. We’re going to use the Node.JS web framework express to this end. Make sure Node.JS is installed.

First let’s create a package.json manifest file that describes our project. I recommend you place it in a dedicated empty directory (I’ll call mine chat-example).

第一个目标是建立一个简单的HTML网页,以提供表单和消息列表。 为此,我们将使用Node.JS网络框架express。 确保已安装[Node.JS](https://nodejs.org/)。

首先,我们创建一个描述我们项目的package.json清单文件。 我建议您将其放在专用的空白目录中(我将其称为“聊天示例”)。

1
2
3
4
5
6
{
"name": "socket-chat-example",
"version": "0.0.1",
"description": "my first socket.io app",
"dependencies": {}
}

Now, in order to easily populate the dependencies property with the things we need, we’ll use npm install:

1
npm install express@4.15.2

Once it’s installed we can create an index.js file that will set up our application.

1
2
3
4
5
6
7
8
9
10
var app = require('express')();
var http = require('http').createServer(app);

app.get('/', (req, res) => {
res.send('<h1>Hello world</h1>');
});

http.listen(3000, () => {
console.log('listening on *:3000');
});

This means that it:

  • Express initializes app to be a function handler that you can supply to an HTTP server (as seen in line 2).
  • We define a route handler / that gets called when we hit our website home.
  • We make the http server listen on port 3000.

If you run node index.js you should see the following:

这意味着:

  • Express将app初始化为可以提供给HTTP服务器的函数处理程序(如第2行所示)。
  • 我们定义了一个路由处理程序/,当我们访问我们的网站首页时会调用它。
  • 我们使http服务器在端口3000上侦听。

如果运行node index.js,您将看到以下内容:

![image-20200828144327055](/Users/liyuanmeng/Library/Application Support/typora-user-images/image-20200828144327055.png)

And if you point your browser to http://localhost:3000:

![image-20200828144339554](/Users/liyuanmeng/Library/Application Support/typora-user-images/image-20200828144339554.png)

Serving HTML

So far in index.js we’re calling res.send and passing it a string of HTML. Our code would look very confusing if we just placed our entire application’s HTML there, so instead we’re going to create a index.html file and serve that instead.

Let’s refactor our route handler to use sendFile instead.

到目前为止,在index.js中,我们正在调用res.send并将其传递为HTML字符串。 如果仅将整个应用程序的HTML放在此处,我们的代码就会看起来很混乱,因此,我们将创建一个index.html文件并将其提供。

让我们重构路由处理程序,改为使用sendFile。

1
2
3
app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});

Put the following in your index.html file:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
<!doctype html>
<html>
<head>
<title>Socket.IO chat</title>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
body { font: 13px Helvetica, Arial; }
form { background: #000; padding: 3px; position: fixed; bottom: 0; width: 100%; }
form input { border: 0; padding: 10px; width: 90%; margin-right: 0.5%; }
form button { width: 9%; background: rgb(130, 224, 255); border: none; padding: 10px; }
#messages { list-style-type: none; margin: 0; padding: 0; }
#messages li { padding: 5px 10px; }
#messages li:nth-child(odd) { background: #eee; }
</style>
</head>
<body>
<ul id="messages"></ul>
<form action="">
<input id="m" autocomplete="off" /><button>Send</button>
</form>
</body>
</html>

If you restart the process (by hitting Control+C and running node index.js again) and refresh the page it should look like this:

![image-20200828144033697](/Users/liyuanmeng/Library/Application Support/typora-user-images/image-20200828144033697.png)

Integrating Socket.IO

Socket.IO is composed of two parts:

  • A server that integrates with (or mounts on) the Node.JS HTTP Server socket.io
  • A client library that loads on the browser side socket.io-client

During development, socket.io serves the client automatically for us, as we’ll see, so for now we only have to install one module:

Socket.IO由两部分组成:

  • 与Node.JS HTTP Server socket.io集成(或挂载)的服务器

  • 在浏览器端加载的客户端库socket.io-client

如我们所见,在开发过程中,socket.io为我们自动为客户端提供服务,因此,现在我们只需要安装一个模块:

1
npm install socket.io

That will install the module and add the dependency to package.json. Now let’s edit index.js to add it:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
var app = require('express')();
var http = require('http').createServer(app);
var io = require('socket.io')(http);

app.get('/', (req, res) => {
res.sendFile(__dirname + '/index.html');
});

io.on('connection', (socket) => {
console.log('a user connected');
});

http.listen(3000, () => {
console.log('listening on *:3000');
});

Notice that I initialize a new instance of socket.io by passing the http (the HTTP server) object. Then I listen on the connection event for incoming sockets and log it to the console.

Now in index.html add the following snippet before the `` (end body tag):

请注意,我通过传递http(HTTP服务器)对象来初始化socket.io的新实例。 然后,我侦听传入套接字的连接事件,并将其记录到控制台。

现在在index.html中,在</ body>(结束body标签)之前添加以下代码段:

1
2
3
4
<script src="/socket.io/socket.io.js"></script>
<script>
var socket = io();
</script>

That’s all it takes to load the socket.io-client, which exposes an io global (and the endpoint GET /socket.io/socket.io.js), and then connect.

If you would like to use the local version of the client-side JS file, you can find it at node_modules/socket.io-client/dist/socket.io.js.

Notice that I’m not specifying any URL when I call io(), since it defaults to trying to connect to the host that serves the page.

If you now restart the process (by hitting Control+C and running node index.js again) and then refresh the webpage you should see the console print “a user connected”.

Try opening several tabs, and you’ll see several messages.

这就是加载socket.io-client的全部工作,它公开了一个io全局(和端点GET /socket.io/socket.io.js),然后进行连接。

如果要使用客户端JS文件的本地版本,可以在node_modules / socket.io-client / dist / socket.io.js中找到它。

请注意,我在调用io()时未指定任何URL,因为它默认为尝试连接到为该页面提供服务的主机。

如果现在重新启动该过程(通过单击Control + C并再次运行节点index.js),然后刷新网页,则应该看到控制台打印“用户已连接”。

尝试打开多个标签,您会看到多则消息。

![image-20200828151113781](/Users/liyuanmeng/Library/Application Support/typora-user-images/image-20200828151113781.png)

Each socket also fires a special disconnect event:

1
2
3
4
5
6
io.on('connection', (socket) => {
console.log('a user connected');
socket.on('disconnect', () => {
console.log('user disconnected');
});
});

Then if you refresh a tab several times you can see it in action.![image-20200828151141285](/Users/liyuanmeng/Library/Application Support/typora-user-images/image-20200828151141285.png)

Emitting events

The main idea behind Socket.IO is that you can send and receive any events you want, with any data you want. Any objects that can be encoded as JSON will do, and binary data is supported too.

Let’s make it so that when the user types in a message, the server gets it as a chat message event. The script section in index.html should now look as follows:

Socket.IO的主要思想是,您可以使用所需的任何数据发送和接收所需的任何事件。 任何可以被编码为JSON的对象都可以,并且也支持二进制数据。

让我们做到这一点,以便用户输入消息时,服务器将其作为聊天消息事件来获取。 现在,index.html中的脚本部分应如下所示:

1
2
3
4
5
6
7
8
9
10
11
12
13
<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-3.4.1.min.js"></script>
<script>
$(function () {
var socket = io();
$('form').submit(function(e) {
e.preventDefault(); // prevents page reloading
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
});
</script>

And in index.js we print out the chat message event:

1
2
3
4
5
io.on('connection', (socket) => {
socket.on('chat message', (msg) => {
console.log('message: ' + msg);
});
});

The result should be like the following video:

Broadcasting

The next goal is for us to emit the event from the server to the rest of the users.

In order to send an event to everyone, Socket.IO gives us the io.emit() method.

我们的下一个目标是将事件从服务器发送给其他用户。

为了向所有人发送事件,Socket.IO提供了io.emit()方法。

1
io.emit('some event', { someProperty: 'some value', otherProperty: 'other value' }); // This will emit the event to all connected socket

If you want to send a message to everyone except for a certain emitting socket, we have the broadcast flag for emitting from that socket:

如果您想向除某个发射套接字之外的所有人发送消息,我们具有从该套接字发射的广播标志:

1
2
3
io.on('connection', (socket) => {
socket.broadcast.emit('hi');
});

In this case, for the sake of simplicity we’ll send the message to everyone, including the sender.

在这种情况下,为简单起见,我们会将邮件发送给所有人,包括发件人。

1
2
3
4
5
io.on('connection', (socket) => {
socket.on('chat message', (msg) => {
io.emit('chat message', msg);
});
});

And on the client side when we capture a chat message event we’ll include it in the page. The total client-side JavaScript code now amounts to:

在客户端,当我们捕获聊天消息事件时,会将其包括在页面中。 客户端JavaScript代码总数现在达到:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
<script>
$(function () {
var socket = io();
$('form').submit(function(e){
e.preventDefault(); // prevents page reloading
socket.emit('chat message', $('#m').val());
$('#m').val('');
return false;
});
socket.on('chat message', function(msg){
$('#messages').append($('<li>').text(msg));
});
});
</script>

And that completes our chat application, in about 20 lines of code! This is what it looks like:

Homework

Here are some ideas to improve the application:

  • Broadcast a message to connected users when someone connects or disconnects.
  • Add support for nicknames.
  • Don’t send the same message to the user that sent it. Instead, append the message directly as soon as he/she presses enter.
  • Add “{user} is typing” functionality.
  • Show who’s online.
  • Add private messaging.
  • Share your improvements!

以下是一些改进应用程序的想法:

  • 当有人连接或断开连接时,向连接的用户广播消息。
  • 添加对昵称的支持。
  • 不要将相同的消息发送给发送该消息的用户。 而是,只要他/她按下Enter键,就直接添加消息。
  • 显示谁在线。
  • 添加私人消息。
  • 分享您的改进!

Getting this example

You can find it on GitHub here.

1
git clone https://github.com/socketio/chat-example.git

Caught a mistake? Edit this page on GitHub