socket.io简单使用

2022-06-24  本文已影响0人  绝尘kinoko

websocket都知道,是用于CS全双工通信的。socket.io是在此基础上进行了封装,提供了namespace、Manager等概念。
demo采用服务端渲染,所以还是用静态资源引入。服务端也是用commonJs,不太明白为什么官网都是用ES6模式引入的例子。在API用法时,会说一下差别。文档全英文,可能有理解不到位的地方,望指正。
版本:4.4.0

基本使用

1. 建立连接
// server
var app = require('../app');
var http = require('http');

var server = http.createServer(app);
let io = require('socket.io')(server);
// client
// html
// script(src='/javascripts/socket.io.js')
// js
let socket = io('');

服务端可以用npm下载,客户端就从GitHub上下了配套的client静态js,跟socket.io-client不太一样,且官网也没有静态js的文档。
首先引入一下namespace和Manager,借用官网的图看下关系。


server
client

还有个room,关系上算是socket 1 --- * room。另外manager和socket 1-n的关系没太搞懂,client看起来像单例。
按个人理解,用聊天软件举例,namespace相当于账号,room相当于群组,manager就是个人。
namespace和manager是对应的

// server
io = io.of('/namespace1');
// client 静态js是没有Manager外部接口的
let socket = io('/namespace1');
2. 通信

总体来说还是eventEmitter那一套,不过底层原理应该不一样。
client.emit -- server.on
server.emit -- client.on
emit(eventName, ...params)
on(eventName, callback)
事件名可自定义,也有一些内置事件

  1. server/namespace
  1. client socket
  1. manager
    client socket.io即可拿到当前manager

demo

因为公司不能连外网,简单记录下;client底子是官网直接搬的

// server
let idNameMap = Object.create(null);
let numUsers = 0;

io = io.of('/namespace1');
io.on('connection', (client) => {
    numUsers++;
    client.emit('login', { numUsers });

    client.on('add user', (name) => {
        idNameMap[client.id] = name;
        client.broadcast.emit('user joined', { username: name, numUsers });
    });
    client.on('new message', (data) => {
        client.broadcast.emit('new message', data);
    });
    client.on('typing', () => {
        client.broadcast.emit('typing', {
            username: idNameMap[client.id]
        });
    });
    client.on('stop typing', () => {
        client.broadcast.emit('stop typing', {
            username: idNameMap[client.id]
        });
    });
    client.on('disconnect', () => {
        numUsers--;
        console.log(idNameMap, client.id, numUsers);
        client.broadcast.emit('user left', {
            username: idNameMap[client.id],
            numUsers
        });
        delete idNameMap[client.id];
    });
    client.on('request_reconnect', () => {
        client.emit('reconnect');
    });
});
// client
$(() => {
    const FADE_TIME = 150; // ms
    const TYPING_TIMER_LENGTH = 400; // ms
    const COLORS = ['#e21400', '#91580f', '#f8a700', '#f78b00', '#58dc00', '#287b00', '#a8f07a', '#4ae8c4', '#3b88eb', '#3824aa', '#a700ff', '#d300e7'];

    var $window = $(window);
    var $usernameInput = $('.user-name'); // Input for username
    var $messages = $('.messages'); // Messages area
    var $inputMessage = $('.input-message'); // Input message input box

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

    // Prompt for setting a username
    var username;
    var connected = false;
    var typing = false;
    var lastTypingTime;
    let loginFlag = false;
    var $currentInput = $usernameInput.focus();
    var timer = null;

    // let socket = io();
    let socket = io('/namespace1');

    const addParticipantsMessage = (data) => {
        var message = '';
        if (data.numUsers === 1) {
            message += '现在只有你一个(`・ω・´)';
        } else {
            message += '现在有 ' + data.numUsers + ' 只小伙伴啦( ̄▽ ̄)/';
        }
        log(message);
    };

    // Sets the client's username
    const setUsername = () => {
        username = cleanInput($usernameInput.val().trim());

        // If the username is valid
        if (username) {
            $loginPage.fadeOut();
            $chatPage.show();
            $loginPage.off('click');
            $currentInput = $inputMessage.focus();

            // Tell the server your username
            socket.emit('add user', username);
        }
    };

    // Sends a chat message
    const sendMessage = () => {
        var message = $inputMessage.val().trim();
        // 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('');
            let data = {
                username,
                message
            };
            addChatMessage(data);
            // tell server to execute 'new message' and send along one parameter
            socket.emit('new message', data);
        }
    };

    // Log a message
    const log = (message, options) => {
        var $el = $('<li>').addClass('log').text(message);
        addMessageElement($el, options);
    };

    // Adds the visual chat message to the message list
    const addChatMessage = (data, options) => {
        // Don't fade the message in if there is an 'X was typing'
        var $typingMessages = getTypingMessages(data);
        options = options || {};
        if ($typingMessages.length !== 0) {
            options.fade = false;
            $typingMessages.remove();
        }

        var $usernameDiv = $('<span class="username"/>').text(data.username).css('color', getUsernameColor(data.username));
        var $messageBodyDiv = $('<span class="messageBody">').text(data.message);

        var typingClass = data.typing ? 'typing' : '';
        var $messageDiv = $('<li class="message"/>').data('username', data.username).addClass(typingClass).append($usernameDiv, $messageBodyDiv);

        addMessageElement($messageDiv, options);
    };

    // Adds the visual chat typing message
    const addChatTyping = (data) => {
        data.typing = true;
        data.message = '正在编辑...';
        addChatMessage(data);
    };

    // Removes the visual chat typing message
    const removeChatTyping = (data) => {
        getTypingMessages(data).fadeOut(function () {
            $(this).remove();
        });
    };

    // Adds a message element to the messages and scrolls to the bottom
    // el - The element to add as a message
    // options.fade - If the element should fade-in (default = true)
    // options.prepend - If the element should prepend
    //   all other messages (default = false)
    const addMessageElement = (el, options) => {
        var $el = $(el);

        // Setup default 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;
    };

    // Prevents input from having injected markup
    const cleanInput = (input) => {
        return $('<div/>').text(input).html();
    };

    // Updates the typing event
    const updateTyping = () => {
        if (connected) {
            if (!typing) {
                typing = true;
                socket.emit('typing');
            }
            lastTypingTime = new Date().getTime();

            setTimeout(() => {
                var typingTimer = new Date().getTime();
                var timeDiff = typingTimer - lastTypingTime;
                if (timeDiff >= TYPING_TIMER_LENGTH && typing) {
                    socket.emit('stop typing');
                    typing = false;
                }
            }, TYPING_TIMER_LENGTH);
        }
    };

    // Gets the 'X is typing' messages of a user
    const getTypingMessages = (data) => {
        return $('.typing.message').filter(function (i) {
            return $(this).data('username') === data.username;
        });
    };

    // Gets the color of a username through our hash function
    const getUsernameColor = (username) => {
        // Compute hash code
        var hash = 7;
        for (var i = 0; i < username.length; i++) {
            hash = username.charCodeAt(i) + (hash << 5) - hash;
        }
        // Calculate color
        var index = Math.abs(hash % COLORS.length);
        return COLORS[index];
    };

    // Keyboard events

    $window.keydown((event) => {
        // Auto-focus the current input when a key is typed
        if (!(event.ctrlKey || event.metaKey || event.altKey)) {
            $currentInput.focus();
        }
        // When the client hits ENTER on their keyboard
        if (event.which === 13) {
            if (username) {
                sendMessage();
                socket.emit('stop typing');
                typing = false;
            } else {
                setUsername();
            }
        }
    });

    $inputMessage.on('input', () => {
        updateTyping();
    });

    // Click events

    // Focus input when clicking anywhere on login page
    $loginPage.click(() => {
        $currentInput.focus();
    });

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

    // Socket events

    // Whenever the server emits 'login', log the login message
    socket.on('login', (data) => {
        connected = true;
        // Display the welcome message
        if (!loginFlag) {
            loginFlag = true;
            var message = '欢迎来到技术交流群(ಥ_ಥ) ';
            log(message, {
                prepend: true
            });
            addParticipantsMessage(data);
        }
    });

    // Whenever the server emits 'new message', update the chat body
    socket.on('new message', (data) => {
        addChatMessage(data);
    });

    // Whenever the server emits 'user joined', log it in the chat body
    socket.on('user joined', (data) => {
        log(data.username + ' 进来了(*^▽^*)');
        addParticipantsMessage(data);
    });

    // Whenever the server emits 'user left', log it in the chat body
    socket.on('user left', (data) => {
        log(data.username + ' 离开了!!!∑(゚Д゚ノ)ノ');
        addParticipantsMessage(data);
        removeChatTyping(data);
    });

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

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

    socket.on('connect', () => {
        console.log('connect..');
    });

    socket.on('disconnect', () => {
        log('服务器已断开...');
    });

    socket.io.on('reconnect', () => {
        console.log('...xxx');
        log('服务器已重连...');
    });

    socket.io.on('reconnect_attempt', () => {
        // 发起轮询时
        console.log('reconnect_attempt');
    });

    socket.io.on('reconnect_error', () => {
        // 发起轮询失败时
        console.log('reconnect_error');
    });

    socket.io.on('reconnect_failed', () => {
        console.log('reconnect_failed');
    });

    socket.on('connect_error', () => {
        log('服务器重连失败...');
    });

    // todo refresh
});

问题

上一篇 下一篇

猜你喜欢

热点阅读