socket.io

简述

如何使用

1.安装

npm install socket.io

2.Node服务器

服务器端的app.js

var app = require('http').createServer(handler);
var io = require('socket.io')(app);
var fs = require('fs');

app.listen(80);

function handler(req, res) {
    fs.readFile(__dirname + '/index.html', function(err, data) {
        if (err) {
            res.writeHead(500);
            return res.end('Error loading index.html');
        }

        res.writeHead(200);
        res.end(data)
    })
}

io.on('connection', function(socket) {
    socket.emit('news', {hello: 'world'});
    socket.on('my other event', function(data) {
        console.log(data);
    });
});

客户端(index.html)

<script src="/socket.io/socket.io.js"></script>
<script>
    var socket = io.connect('http://localhost');
    socket.on('news', function(data) {
       console.log(data);
       socket.emit('my other event', { my: 'data'});
    });
</script>

3.Express 3/4 服务器

服务器端的app.js

var app = require('express')();
var server = require('http').Server(app);
var io = require('socket.io')(server);

server.listen(80);

app.get('/', function (req, res) {
  res.sendfile(__dirname + '/index.html');
});

io.on('connection', function (socket) {
  socket.emit('news', { hello: 'world' });
  socket.on('my other event', function (data) {
    console.log(data);
  });
});

客户端(index.html)

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io.connect('http://localhost');
  socket.on('news', function (data) {
    console.log(data);
    socket.emit('my other event', { my: 'data' });
  });
</script>

4.Express 2.X 服务器

服务器端的app.js

var app = require('express').createServer();
var io = require('socket.io')(app);

app.listen(80);

app.get('/', function (req, res) {
  res.sendfile(__dirname + '/index.html');
});

io.on('connection', function (socket) {
  socket.emit('news', { hello: 'world' });
  socket.on('my other event', function (data) {
    console.log(data);
  });
});

客户端(index.html)

<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io.connect('http://localhost');
  socket.on('news', function (data) {
    console.log(data);
    socket.emit('my other event', { my: 'data' });
  });
</script>

5.发送和接收事件

Socket.IO允许你发送和接收常规事件。除了connect/message/disconnect外,可以发送常规事件。

// note, io(<port>) will create a http server for you
var io = require('socket.io')(80);

io.on('connection', function (socket) {
  io.emit('this', { will: 'be received by everyone'});

  socket.on('private message', function (from, msg) {
    console.log('I received a private message by ', from, ' saying ', msg);
  });

  socket.on('disconnect', function () {
    io.emit('user disconnected');
  });
});

6.命名空间限制

如果你可以控制所有发送至一个特殊应用得信息或事件,使用默认命名空间。如果你想插入第三方代码,或者想与他人共同开发,socket.io提供了一个命名socket的方法。

Server (app.js)


var io = require('socket.io')(80);
var chat = io
  .of('/chat')
  .on('connection', function (socket) {
    socket.emit('a message', {
        that: 'only'
      , '/chat': 'will get'
    });
    chat.emit('a message', {
        everyone: 'in'
      , '/chat': 'will get'
    });
  });

var news = io
  .of('/news')
  .on('connection', function (socket) {
    socket.emit('item', { news: 'item' });
  });


Client (index.html)


<script>
  var chat = io.connect('http://localhost/chat')
    , news = io.connect('http://localhost/news');

  chat.on('connect', function () {
    chat.emit('hi!');
  });

  news.on('news', function () {
    news.emit('woot');
  });
</script>

简单实例(聊天应用)Get started

前言

用诸如PHP的技术写一个聊天应用一直以来都是非常困难的。它包括了轮询,跟踪时间戳,而且其速度总是比较慢。
Sockets是解决服务器和客户端之间即时通信的常规方法。
这意味着服务器可以主动将信息推送到客户端。无论何时你发送了一条信息,服务器都将得到它并且发送至其他已连接的客户端。

Web框架

1.初始化一个npm包

package.json
{
  "name": "socket-chat-example",
  "version": "0.0.1",
  "description": "my first socket.io app",
  "dependencies": {}
}

2.载入依赖express框架

npm install --save express@4.15.2

3.创建index.js文件,该文件将建立我们的应用

// express初始化app,使其成为可以提供给HTTP服务器的处理函数
var app = require('express')();
var http = require('http').Server(app);

// 定义路由路径'/'
app.get('/', function(req, res){
  res.send('<h1>Hello world</h1>');
});

// 本地端口3000监听服务器
http.listen(3000, function(){
  console.log('listening on *:3000');
});

此时运行node index,打开浏览器输入相应网址得到

4.网页不可能全靠字符串形式写在index.js中发送,稍作修改并添加index.html文件。

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

index.html

<!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: .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>

5.现在只是创建了一个本地服务器,并发送相应的html文件,接下来需要整合Socket.IO
安装Socket.IO

npm install --save socket.io

稍作修改index.js文件

var app = require('express')();
var http = require('http').Server(app);
// 初始化socket.io实例并传入http服务器
var io = require('socket.io')(http);

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

// 实例监听connection事件,每次事件触发回调函数
io.on('connection', function(socket){
  console.log('a user connected');
});

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

客户端index.html也需要添加一些脚本

// 只需要这些代码就可以载入socket.io-client,并且可以提供全局io,连接
//  io()没有传入路径,即使用默认路径'/'
<script src="/socket.io/socket.io.js"></script>
<script>
  var socket = io();
</script>

每个socket可以发送特殊的断开事件

io.on('connection', function(socket){
  console.log('a user connected');
  socket.on('disconnect', function(){
    console.log('user disconnected');
  });
});

6.发送事件
Socket.IO背后的思想是你可以发送和接收任何事件,伴随任何数据,比如说可以被编译成JSON的数据或者二维码。
下列代码使用了自定义的chat message事件。
index.html文件

<script src="/socket.io/socket.io.js"></script>
<script src="https://code.jquery.com/jquery-1.11.1.js"></script>
<script>
  $(function () {
    var socket = io();
    // 表单提交,socket发送chat message事件,并清空输入框,禁止了提交表单事件
    $('form').submit(function(){
      socket.emit('chat message', $('#m').val());
      $('#m').val('');
      return false;
    });
  });
</script>

index.js

io.on('connection', function(socket){
  // 监听客户端发送过来的chat message事件,提供回调
  socket.on('chat message', function(msg){
    console.log('message: ' + msg);
  });
});

7.广播
为了能把事件发送给所有人,而不是单纯的服务器,Socket.IO提供了一个API,io.emit

io.emit('some event', { for: 'everyone' });

如果想将信息发送给除了某个socket外的所有人,可以使用broadcast:

io.on('connection', function(socket){
  socket.broadcast.emit('hi');
});

这里为了简洁,把信息发送给所有人,包括发送者。

io.on('connection', function(socket){
  socket.on('chat message', function(msg){
    io.emit('chat message', msg);
  });
});

index.html有接受从服务器端传来的相应事件,并且有个回调处理相关信息

<script>
  $(function () {
    var socket = io();
    $('form').submit(function(){
      socket.emit('chat message', $('#m').val());
      $('#m').val('');
      return false;
    });
    socket.on('chat message', function(msg){
      $('#messages').append($('<li>').text(msg));
    });
  });
</script>

src="https://i.cloudup.com/transcoded/J4xwRU9DRn.mp4" width="310" height="198">

简单实例(多人聊天应用)摘自官网demo

关于官网的多人聊天应用,其实目录结构和第一个实例差不多,在这里简单分析下期中的index.js文件(服务器)和main.js文件(客户端)。

index.js文件代码如下:

  // 创建一个express服务器
  // var app = require('express')(),提供给http创建服务器
var express = require('express');
var app = express();
var server = require('http').createServer(app);
  // ../.. 路径是为了搜索sockiet.io模块
var io = require('../..')(server);
  // 没有设置端口,则默认端口为3000
var port = process.env.PORT || 3000;

  //监听端口,回调函数确认成功启动
server.listen(port, function () {
  console.log('Server listening at port %d', port);
});

// 设置路由
app.use(express.static(__dirname + '/public'));

// 聊天室

// 定义使用人数初始变量为0
var numUsers = 0;

// socket实例io监听connection事件,回调函数参数为socket
io.on('connection', function (socket) {
  // 是否已有用户
  var addedUser = false;

  // 如果客户端socket发送 'new message', 下列代码会监听并执行,回调函数为data,也就时客户端socket.emit传递的第二个参数
  socket.on('new message', function (data) {
    // 发送给客户端 'new messag'事件,传递了一个对象,包括用户名和消息等信息
    socket.broadcast.emit('new message', {
      username: socket.username,
      message: data
    });
  });

  // 监听客户端发送过来的'add user'事件,回调传递了username参数
  socket.on('add user', function (username) {
    if (addedUser) return;

    // 如果不存在该用户名,则把客户端传递过来的用户名储存在socket会话里,添加用户人数,修改存在用户状态,发送'login'事件给客户端,传递一个对象,里面包含了聊天室的人数
    socket.username = username;
    ++numUsers;
    addedUser = true;
    socket.emit('login', {
      numUsers: numUsers
    });

    // 通知所有的客户端,新加入一个人 
    // 即广播发送'user joined'事件,发送一个对象,包含用户名信息和聊天室人数
    socket.broadcast.emit('user joined', {
      username: socket.username,
      numUsers: numUsers
    });
  });

  // 如果客户端发送typing事件,我们把它传递给其他人
  socket.on('typing', function () {
    socket.broadcast.emit('typing', {
      username: socket.username
    });
  });

  // 如果客户端发送stop typing事件,我们同样把他传递给其他人
  socket.on('stop typing', function () {
    socket.broadcast.emit('stop typing', {
      username: socket.username
    });
  });

  // 断开连接,则执行下列代码,如果存在用户名,人数减1
  socket.on('disconnect', function () {
    if (addedUser) {
      --numUsers;

      // 通知所有人,这个用户离开了当前聊天框
      socket.broadcast.emit('user left', {
        username: socket.username,
        numUsers: numUsers
      });
    }
  });
});

main.js 客户端JS代码

// 引用了jQuery
$(function() {
  // 定义常量
  var FADE_TIME = 150; // ms
  var TYPING_TIMER_LENGTH = 400; // ms
  var COLORS = [
    '#e21400', '#91580f', '#f8a700', '#f78b00',
    '#58dc00', '#287b00', '#a8f07a', '#4ae8c4',
    '#3b88eb', '#3824aa', '#a700ff', '#d300e7'
  ];

  // 初始化部分变量
  var $window = $(window);
  var $usernameInput = $('.usernameInput'); // Input for username
  var $messages = $('.messages'); // Messages area
  var $inputMessage = $('.inputMessage'); // Input message input box

  var $loginPage = $('.login.page'); // The login page
  var $chatPage = $('.chat.page'); // The chatroom page

  // 设置用户名的提示框
  var username;
  var connected = false;
  var typing = false;
  var lastTypingTime;
  var $currentInput = $usernameInput.focus();

  // 创建一个IO全局实例
  var socket = io();

  // 得到一个信息对象,根据其numUsers属性值得不同,将不同的文本信息传递给log函数制成完整野生li标签
  function addParticipantsMessage (data) {
    var message = '';
    if (data.numUsers === 1) {
      message += "there's 1 participant";
    } else {
      message += "there are " + data.numUsers + " participants";
    }
    log(message);
  }

  // 设置客户端的用户名
  function setUsername () {
    // 得到干净的用户名
    username = cleanInput($usernameInput.val().trim());

    // 如果用户名存在
    if (username) {
      // 登录界面消退
      // 聊天界面出现
      // 移除登录界面的点击事件,避免浪费内存
      // 自动获取聊天输入框的焦点
      $loginPage.fadeOut();
      $chatPage.show();
      $loginPage.off('click');
      $currentInput = $inputMessage.focus();

      // 发送'add user'事件,并带有username信息
      socket.emit('add user', username);
    }
  }

  // 发送一条聊天消息
  function sendMessage () {
    var message = $inputMessage.val();
    // Prevent markup from being injected into the message
    message = cleanInput(message);
    // if there is a non-empty message and a socket connection
    if (message && connected) {
      // 清空输入框
      $inputMessage.val('');
      addChatMessage({
        username: username,
        message: message
      });
      // 客户端发送'new message'事件,并传递了message信息
      socket.emit('new message', message);
    }
  }

  // 记录一条信息,根据信息创建一个野生的li标签,调用aME函数添加到DOM文档
  function log (message, options) {
    var $el = $('<li>').addClass('log').text(message);
    addMessageElement($el, options);
  }

  // 把聊天信息添加到message list
  function addChatMessage (data, options) {
    // Don't fade the message in if there is an 'X was typing'
    var $typingMessages = getTypingMessages(data);

    // 设置options或者使用默认option
    options = options || {};
    if ($typingMessages.length !== 0) {
      options.fade = false;
      $typingMessages.remove();
    }

    // 创建一个class为username的span野生标签
    // 文本元素是data.username
    // 设置css样式
    var $usernameDiv = $('<span class="username"/>')
      .text(data.username)
      .css('color', getUsernameColor(data.username));

    // 设置一个class为messageBody的span野生标签
    // 内容为data.message
    var $messageBodyDiv = $('<span class="messageBody">')
      .text(data.message);

    var typingClass = data.typing ? 'typing' : '';

    // 创建一个class为message的野生li标签
    // 设置该li标签有个username数据,其值为data.username
    // 添加类
    // 野生li标签内添加名字span,消息主题span,形成一个完整的野生li标签
    var $messageDiv = $('<li class="message"/>')
      .data('username', data.username)
      .addClass(typingClass)
      .append($usernameDiv, $messageBodyDiv);

   // 把野生li标签添加DOM文档
    addMessageElement($messageDiv, options);
  }

  // data传递给aCM函数前,添加两个属性
  function addChatTyping (data) {
    data.typing = true;
    data.message = 'is typing';
    addChatMessage(data);
  }

  // 移除‘X正在输入’消息
  function removeChatTyping (data) {
    getTypingMessages(data).fadeOut(function () {
      $(this).remove();
    });
  }

  // 添加一个message元素(li)给类为messages的(ul),并且拉倒底部
  // el为准备加入的message元素
  // options选项,fade属性表示元素是否应该有fade-in动画效果
  // options的prepend属性表示元素时候应该添加到其他所有的messages的内容前边
  function addMessageElement (el, options) {
    var $el = $(el);

    // 设置options的默认参数
    if (!options) {
      options = {};
    }
    if (typeof options.fade === 'undefined') {
      options.fade = true;
    }
    if (typeof options.prepend === 'undefined') {
      options.prepend = false;
    }

    // Apply options
    if (options.fade) {
      $el.hide().fadeIn(FADE_TIME);
    }
    if (options.prepend) {
      $messages.prepend($el);
    } else {
      $messages.append($el);
    }
    $messages[0].scrollTop = $messages[0].scrollHeight;
  }

  // 返回的还是input,消除了输入内插标记的隐患
  function cleanInput (input) {
    return $('<div/>').text(input).text();
  }

  // 更新输入事件
  function updateTyping () {
    if (connected) {
      if (!typing) {
        typing = true;
        socket.emit('typing');
      }
      lastTypingTime = (new Date()).getTime();

      // 每个一段时间,如果正在输入而且时间差大于TTL,则发送'stop typing'事件,同时typing设置为false
      setTimeout(function () {
        var typingTimer = (new Date()).getTime();
        var timeDiff = typingTimer - lastTypingTime;
        if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
          socket.emit('stop typing');
          typing = false;
        }
      }, TYPING_TIMER_LENGTH);
    }
  }

  // 得到用户'X正在输入..'的信息,只选取那些保存数据username与data参数username属性一致的元素,返回的是过滤过的message list
  function getTypingMessages (data) {
    return $('.typing.message').filter(function (i) {
      return $(this).data('username') === data.username;
    });
  }

  // 通过哈希函数得到用户名的颜色
  function getUsernameColor (username) {
    // 计算哈希值
    var hash = 7;
    for (var i = 0; i < username.length; i++) {
       hash = username.charCodeAt(i) + (hash << 5) - hash;
    }
    // 计算颜色
    var index = Math.abs(hash % COLORS.length);
    return COLORS[index];
  }

  // 键盘事件

  $window.keydown(function (event) {
    // 输入时自动聚焦当前输入框
    if (!(event.ctrlKey || event.metaKey || event.altKey)) {
      $currentInput.focus();
    }
    // 键盘提交
    if (event.which === 13) {
      if (username) {
        sendMessage();
        socket.emit('stop typing');
        typing = false;
      } else {
        setUsername();
      }
    }
  });

  输入框的input事件。更新typing状态
  $inputMessage.on('input', function() {
    updateTyping();
  });

  // 点击事件

  // 点击登录界面的任何位置,聚焦名称输入框
  $loginPage.click(function () {
    $currentInput.focus();
  });

  // Focus input when clicking on the message input's border
  $inputMessage.click(function () {
    $inputMessage.focus();
  });

  // Socket事件

  // Whenever the server emits 'login', log the login message
  // 无论何时服务器发送了login事件,都
  socket.on('login', function (data) {
    connected = true;
    // 欢迎信息
    var message = "Welcome to Socket.IO Chat – ";
    log(message, {
      prepend: true
    });
    addParticipantsMessage(data);
  });

  // 服务器发送'new message'事件,添加到聊天框
  socket.on('new message', function (data) {
    addChatMessage(data);
  });

  // 服务器发送'user joined'事件,添加新消息到聊天框
  socket.on('user joined', function (data) {
    log(data.username + ' joined');
    addParticipantsMessage(data);
  });

  // Whenever the server emits 'user left', log it in the chat body
  socket.on('user left', function (data) {
    log(data.username + ' left');
    addParticipantsMessage(data);
    removeChatTyping(data);
  });

  // Whenever the server emits 'typing', show the typing message
  socket.on('typing', function (data) {
    addChatTyping(data);
  });

  // Whenever the server emits 'stop typing', kill the typing message
  socket.on('stop typing', function (data) {
    removeChatTyping(data);
  });

  socket.on('disconnect', function () {
    log('you have been disconnected');
  });

  socket.on('reconnect', function () {
    log('you have been reconnected');
    if (username) {
      socket.emit('add user', username);
    }
  });

  socket.on('reconnect_error', function () {
    log('attempt to reconnect has failed');
  });

});

流程解析

一、打开chat页面

1.这时候登录界面是显示的,而聊天界面display为none;

二、输入nickname,回车

1.监听$window对象的keydown事件,全局变量username此时值为undefined,调用全局函数setUsername();
2.全局函数setUsername(),取得登录界面输入框的值,经过处理后(清洗、去空),如果该值非空,登录界面loginPage消失,chatPage展示,注销loginPage的点击事件,获取当前聊天界面输入框,并设置为当前输入框。向服务器发送’add user’事件,附带username值。
3.打开页面后,服务器已经接受到了socket的’connection’事件。现在服务器又接收到了客户端传来的’add user’事件。判断addedUser变量是否为true(初始设置为false),如果为false,继续执行。把接收的username值储存在socket.username属性里(socket会话),变量numUsers加1,变量addUser设置为true。服务器端向对应客户端发送’login’事件,附带一个对象(包含人数)。广播所有的已连接客户端’user joined’事件,附带一个对象(包含用户名,人数)。
4.对于单个客户端而言:设置全局变量connected为true,调用log函数(传递一个字符串和一个对象),调用addParticipantsMessage函数(传递服务器传来的对象参数)。
5.log函数接收两个参数,创建一个野生的标签<li class="log">Welcome to Socket.IO Chat –</li>,调用addMessageElement函数(传递两个参数,一个是DOM元素,一个是options设置选项)。
6.addMessageElement函数接收两个参数,默认设置选项options。如果存在fade效果,逐渐显示,将野生的标签<li class="log">Welcome to Socket.IO Chat –</li>添加到<ul class="messages">。聊天界面滚动始终在最下方。
7.addParticipantsMessage函数接收一个参数,创建一个野生的标签<li class="log">there are XX participants</li>,添加到<ul class="messages">
8.所有客户端接收一个’user joined’事件,挂载’Helium joined’,并继续调用addParticipantsMessage显示在线XXX个用户。

三、输入聊天信息

1.监听聊天输入框的’input’事件,调用updateTyping事件。
2.updateTyping事件先判断是否连接,如果是,再判断是否正在输入中,发送给服务器’typing’事件。过段时间执行另一个函数,如果设置的延迟时间内,没有触发第二次’input’事件,则发送给服务器’stop typing’事件,同时设置typing值为false。
3、typing事件。服务器端接收后,向所有的连接客户端广播’typing’事件,附带用户名信息。
4.客户端接收’typing’事件后,调用addChatTyping函数。addChatTyping函数给传入参数添加了typing和message属性,调用addChatMessage函数。
5.addChatMessage函数接受一个数据对象(包含用户名,typing以及message属性)和一个设置选项,首先调用getTypingMessage函数,如果还有用户正在输入,则不把消息显示。创建一个组合标签

<li class="message typingClass" username="data.username">
    <span class="username" style="color">data.username</span>
    <span class="messageBody">is typing</span>
</li>

挂载到聊天框内。
6.stop typing事件。服务器端接收后,向所有的连接客户端广播’stop typing’事件,附带用户名信息。
7.所有连接客户端调用removeChatTyping函数。得到正在输入的消息,移除。

四、回车输入聊天消息。

1.调用sendMessage函数,同时向服务器发送’stop typing’事件,移除’X正在输入中的信息’
2.sendMessage得到聊天输入框的值,处理后调用addChatMessage函数,传入一个数据对象(username和message),清空聊天输入框,同时发送’new message’事件及message字符串。addChatMessage挂载了

<li class="message typingClass" username="data.username">
    <span class="username" style="color">data.username</span>
    <span class="messageBody">data.message</span>
</li>

3.服务器端接收到’new message’事件,广播’new message’事件,伴随数据对象(username,message),客户端调用addChatMessage添加聊天信息到聊天界面。

五、退出界面。

1.服务器端检测到’disconnect’,numUsers用户人数减1,广播发送’user left’事件,伴随一个数据对象(username和numUsers)
2.客户端接收事件,log(’someone left’),显示聊天室在线人数。

服务器端和客户端的交互

这里写图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值