IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> 网络协议 -> 如何制作一个简单的Web终端 -> 正文阅读

[网络协议]如何制作一个简单的Web终端

概括

web终端可以在浏览器上模拟xshell的功能,本文源代码在Github: How to create a simple web terminal

效果如下所示:
在这里插入图片描述

架构

现在的web终端基本都是基于xterm.js开发的。xterm是一个前端的终端组件。

web终端的实现是客户端-服务器架构:

在这里插入图片描述

通信协议

  • 客户端和服务器之间使用的是websocket协议
  • 服务器通过本地fork一个bash或者powershell进程,充当传话筒角色

数据结构

服务器会维持一个,客户端的<socketId,pty>的Map, 用来记录客户端连接和终端进程的对应关系。

客户端

客户端主要使用xterm和socketIO这两个库,xterm是终端组件,socketIO是和服务器通信的组件。

客户端显示仅仅需要一个DOM元素

  • index.html
<!DOCTYPE html>
<html>
  <head>
    <title>How to create web terminals</title>
    <meta charset="UTF-8" />
    <style>
        /**设置满屏展示**/
        #terminal-container{
          position: absolute;
          margin: 0;
          padding: 0;
          height: 100%;
          width: 100%;
        }
    </style>
  </head>

  <body>
     <!--终端关联的DOM元素-->
    <div id="terminal-container"></div>
    <script src="src/index.js"></script>
  </body>
</html>

  • index.js

import { TerminalUI } from "./TerminalUI";
import io from "socket.io-client";

// 连接服务端,并给终端设置关联的DOM元素。

// IMPORTANT: 服务端的监听地址
const serverAddress = "http://localhost:8080";

// 连接服务端并返回一个socket
function connectToSocket(serverAddress) {
  return new Promise(res => {
    const socket = io(serverAddress);
    res(socket);
  });
}
// 开启一个终端
function startTerminal(container, socket) {
  // Create an xterm.js instance (TerminalUI class is a wrapper with some utils. Check that file for info.)
  const terminal = new TerminalUI(socket);

  // Attach created terminal to a DOM element.
  terminal.attachTo(container);

  // When terminal attached to DOM, start listening for input, output events.
  // Check TerminalUI startListening() function for details.
  terminal.startListening();
}

function start() {
  // get DOM element as an terminal container
  const container = document.getElementById("terminal-container");
  // Connect to socket and when it is available, start terminal.
  connectToSocket(serverAddress).then(socket => {
    startTerminal(container, socket);
  });
}
  • TerminalUI.js

这个文件主要设置了xterm实例如何发送客户端输入和接收服务端输出。

// TerminalUI.js

import { Terminal } from "xterm";
import { FitAddon } from 'xterm-addon-fit';


import "xterm/css/xterm.css";

export class TerminalUI {
  // xterm实例构造器,设置了背景和字体颜色
  constructor(socket) {
    this.terminal = new Terminal({
      theme: {
        background: "black",
        foreground: "#F5F8FA"
      },
    });
    // xterm实例一一对应一个socket连接
    this.socket = socket;
  }

  /**
   * Attach event listeners for terminal UI and socket.io client
   */
  startListening() {

    this.terminal.onData(data =>{
      this.sendInput(data)
    });

    this.socket.on("output", data => {
      // When there is data from PTY on server, print that on Terminal.
      this.write(data);
    });

  }

  /**
   * Print something to terminal UI.
   */
  write(text) {

    this.terminal.write(text);
  }

  /**
   * Utility function to print new line on terminal.
   */
  prompt() {
    this.terminal.write(`\r\n$ `);

  }

  /**
   * Send whatever you type in Terminal UI to PTY process in server.
   * @param {*} input Input to send to server
   */
  sendInput(input) {
    this.socket.emit("input", input);
  }

  /**
   *
   * @param {HTMLElement} container HTMLElement where xterm can attach terminal ui instance.
   */
  attachTo(container) {
    // 关联DOM元素并加载Fit组件(用于设置终端适应DOM元素大小,不然终端有固定尺寸很难看)
    let fitAddon = new FitAddon();
    this.terminal.loadAddon(fitAddon);
    this.terminal.open(container);
    fitAddon.fit()
    // Default text to display on terminal.
    this.terminal.write("Terminal Connected");
    this.terminal.write("");
    this.prompt();

  }

  clear() {
    this.terminal.clear();
  }
}

服务端

服务端会给每个客户端连接socket生成一个终端进程,终端进程一般情况下linux是bash, windows是powershell.

会有一个全局的Map保存socket和终端进程的映射关系。

  • SocketService.js
// SocketService.js

// Manage Socket.IO server
const socketIO = require("socket.io");
const PTYService = require("./PTYService");

class SocketService {
  constructor() {
      // 全局的Map保存socket和终端进程的映射关系
      this.SocketBook=new Map()
  }

  attachServer(server) {
    if (!server) {
      throw new Error("Server not found...");
    }

    const io = socketIO(server);
    console.log("Created socket server. Waiting for client connection.");
    // "connection" event happens when any client connects to this io instance.
    // 每个socket都有一个唯一的socketID
    io.on("connection", socket => {
      console.log("Client connect to socket.", socket.id);

      // Just logging when socket disconnects.
      socket.on("disconnect", () => {
        console.log("Disconnected Socket: ", socket.id);
        this.SocketBook.delete(socket.id)
      });

      // Create a new pty service when client connects.
      let pty = new PTYService(socket);

      // 如何是容器终端,取消这行注释,使用kubectl exec 或者docker exec
      //pty.write("kubectl exec -it cicd-dev-metric-service-b85cc4cdd-zpcrm /bin/bash \r")


      // add <socket.id,pty> to map
      this.SocketBook.set(socket.id,pty)

      // Attach any event listeners which runs if any event is triggered from socket.io client
      // For now, we are only adding "input" event, where client sends the strings you type on terminal UI.
      // 每当客户端有输入事件到来,根据socket id 查找对应shell进程,并向shell进程写入客户端的输入字符
      socket.on("input", input => {
        //Runs this event function socket receives "input" events from socket.io client
        let pty= this.SocketBook.get(socket.id)
        pty.write(input);
      });


    });
  }
}

module.exports = SocketService;

  • PTYService.js

封装本地终端进程的相关操作,使用了node-pty库来fork终端进程。

// PTYService.js

const os = require("os");
const pty = require("node-pty");

class PTY {
  constructor(socket) {
    // Setting default terminals based on user os
    this.shell = os.platform() === "win32" ? "powershell.exe" : "bash";
    this.ptyProcess = null;
    this.socket = socket;

    // Initialize PTY process.
    this.startPtyProcess();
  }

  /**
   * Spawn an instance of pty with a selected shell.
   */
  startPtyProcess() {
    this.ptyProcess = pty.spawn(this.shell, [], {
      name: "xterm-color",
      cwd: process.env.HOME, // Which path should terminal start
      env: process.env // Pass environment variables
    });

    // Add a "data" event listener.
    this.ptyProcess.on("data", data => {
      // Whenever terminal generates any data, send that output to socket.io client to display on UI
      this.sendToClient(data);
    });
  }

  /**
   * Use this function to send in the input to Pseudo Terminal process.
   * @param {*} data Input from user like command sent from terminal UI
   */

  write(data) {
    this.ptyProcess.write(data);
  }

  sendToClient(data) {
    // Emit data to socket.io client in an event "output"
    this.socket.emit("output", data);
  }
}

module.exports = PTY;

总结

不足:

  • xterm的样式很难调节,本文示例中,我一直想要让输入字符输入达到DOM元素的长度再换行,但是没有做到。
  • 如何写一个容器的web终端的话,如何将会话一开始的kubectl exec等信息清除。

成果:

  • 实现了一个web终端的基础功能。
  • 多客户端同时可以连接
  网络协议 最新文章
使用Easyswoole 搭建简单的Websoket服务
常见的数据通信方式有哪些?
Openssl 1024bit RSA算法---公私钥获取和处
HTTPS协议的密钥交换流程
《小白WEB安全入门》03. 漏洞篇
HttpRunner4.x 安装与使用
2021-07-04
手写RPC学习笔记
K8S高可用版本部署
mySQL计算IP地址范围
上一篇文章      下一篇文章      查看所有文章
加:2022-03-30 19:06:09  更:2022-03-30 19:06:40 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/26 5:22:41-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码