WebTerminal功能实现与代码演示(基于Golang和Xterm.js)

目的

WebTerminal是一个比较有意思的功能,让我们可以脱离专门的软件,在浏览器中就可以与Linux设备进行交互。这篇文章将对这个功能做个简单的说明与演示。

例程地址如下:
https://github.com/NaisuXu/web-terminal-demo-with-golang-and-xterm

方案说说明

WebTerminal这个市面上叫法还是挺混乱的,大多数情况下还和WebSSH混在一起,两者本质上是有一些区别的。这里也把这两者混在一起列举下常见的几个方案:

在这里插入图片描述
上面这个情况常见都是用在运维管理等使用,可以在浏览器中管理多台设备或服务器。

在这里插入图片描述
上面这个情况也常用在运维管理等使用。

在这里插入图片描述
上面这个情况主要用于设备本身通过Web UI进行操作,想要更加高级的操作时可以直接通过终端进行。

这篇文章实现的是最后一种方式。另外因为我主要用在嵌入式Linux设备中,Web Server评估下来使用Golang是适应性和开发效率综合来说最好的。

实现过程与代码演示

前端页面(Xterm.js)

前端实现Terminal功能主要使用 Xterm.js 这个库,官方页面如下:
https://xtermjs.org/

VS Code中的中终端窗口就是使用这个库的,其中后端使用 node-pty

安装了Node.js的情况下新建项目目录并进入,然后初始化项目并下载xterm库:

npm init -y
npm install xterm

新建 index.html 文件,文件内容如下:

<!DOCTYPE html>
<html>
<head>
    <link rel="stylesheet" href="node_modules/xterm/css/xterm.css" />
    <script src="node_modules/xterm/lib/xterm.js"></script>
</head>
<body>
    <div id="terminal"></div>
    <script>
        const term = new Terminal();
        term.open(document.querySelector('#terminal'));
    </script>
</body>
</html>

这时候就可以测试 Xterm.js 的功能了,可以使用Terminal对象的 write 方法向终端窗口中输出内容,使用 onData 方法可以接收终端窗口中的键盘操作:
在这里插入图片描述

上面就是前端页面最核心的东西了,接下来只要处理与后端服务的数据交互即可。这里使用WebSocket方式,完成后的代码如下:

<!DOCTYPE html>
<html>

<head>
    <title>WebTerminal</title>
    <link rel="stylesheet" href="./node_modules/xterm/css/xterm.css" />
    <script src="./node_modules/xterm/lib/xterm.js"></script>
</head>

<body>
    <div style="width: 736px; height: 408px;">
        <!-- 目前版本的 Xterm 5.1.0 默认串口大小 24x80 -->
        <div id="terminal"></div>
    </div>
    <script>
        const term = new Terminal();

        term.open(document.querySelector('#terminal')); // 挂载

        const socket = new WebSocket(`ws://${window.location.host}/webterminal`); // 创建WebSocket连接

        term.onData((data) => { // 网页xterm窗口中有输入的数据
            // console.log('term.onData:', data);
            socket.send(data); // 通过WebSocket发送给服务器
        });
        
        socket.onmessage = (event) => { // 收到来自服务器的WebSocket消息
            // console.log('socket.onmessage:', event.data);
            term.write(event.data); // 向xterm对象写入数据
        };
    </script>
</body>

</html>

后端服务(Golang)

安装了Go的环境下,在刚才同目录中,使用下面命令初始化项目:

go mod init webterminal

新建 main.go 文件,文件内容如下:

package main

import (
	"embed"
	"net/http"
	"os/exec"

	"github.com/creack/pty"
	"github.com/olahol/melody"
)

//go:embed index.html node_modules/xterm/css/xterm.css node_modules/xterm/lib/xterm.js
var content embed.FS

func main() {
	c := exec.Command("sh") // 系统默认shell交互程序
	f, err := pty.Start(c)  // pty用于调用系统自带的虚拟终端
	if err != nil {
		panic(err)
	}

	m := melody.New() // melody用于实现WebSocket功能

	go func() { // 处理来自虚拟终端的消息
		for {
			buf := make([]byte, 1024)
			read, err := f.Read(buf)
			if err != nil {
				return
			}
			// fmt.Println("f.Read: ", string(buf[:read]))
			m.Broadcast(buf[:read]) // 将数据发送给网页
		}
	}()

	m.HandleMessage(func(s *melody.Session, msg []byte) { // 处理来自WebSocket的消息
		// fmt.Println("m.HandleMessage: ", string(msg))
		f.Write(msg) // 将消息写到虚拟终端
	})

	http.HandleFunc("/webterminal", func(w http.ResponseWriter, r *http.Request) {
		m.HandleRequest(w, r) // 访问 /webterminal 时将转交给melody处理
	})

	fs := http.FileServer(http.FS(content))
	http.Handle("/", http.StripPrefix("/", fs)) // 设置静态文件服务

	http.ListenAndServe("0.0.0.0:22333", nil) // 启动服务器,访问 http://本机(服务器)IP地址:22333/ 进行测试
}

代码比较简单,核心功能就是调用系统中的虚拟终端,然后通过WebSocket和网页进行双向通讯。

上面代码只是用于功能测试使用的,这个代码有个问题是并没有对每个客户端进行单独处理,所以打开多个网页时操作都会同步响应。

使用下面命令安装相关依赖:

go mod tidy

编译与测试

在项目目录下创建 build.sh 文件,文件内容如下:

#!/bin/sh
GOOS=linux GOARCH=amd64 go build -o webterminal_linux_x86-64
GOOS=linux GOARCH=arm GOARM=7 go build -o webterminal_linux_armv7
GOOS=linux GOARCH=arm GOARM=5 go build -o webterminal_linux_armv5

后面三行命令分别用于编译生成三个平台的程序,你也可以根据需要来编写。

使用下面命令进行编译(注意window中需要使用 git-bash 等操作):

# chmod +x ./build.sh
./build.sh

将编译生成的程序拷贝到对应的平台中就可以进行测试了。

linux_x86-64 (Ununtu 22.04 AMD Ryzen 5 PRO 4650U):
在这里插入图片描述

linux_armv5 (NUC980 Linux buildroot 5.10.103+ armv5tejl GNU/Linux):
在这里插入图片描述

这里没有测试 linux_armv7 平台,有条件的话可以找一个树莓派(4B)进行测试。

总结

基于Golang和Xterm.js实现WebTerminal功能原理上比较简单,实际应用时更多的是需要根据需求进行功能和使用上的优化。

  • 3
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
实现802.1x认证功能的最常用的方式是通过EAP协议(Extensible Authentication Protocol)进行认证。以下是一个简单的golang代码示例,用于实现基于EAP的802.1x认证功能。 ```go package main import ( "crypto/tls" "fmt" "net" "bytes" "encoding/binary" ) const ( EAPCodeRequest = 1 EAPCodeResponse = 2 EAPCodeSuccess = 3 EAPCodeFailure = 4 ) const ( EAPTypeIdentity = 1 EAPTypeMD5Challenge = 4 ) func main() { // 连接认证服务器 conn, err := tls.Dial("tcp", "auth-server:1812", nil) if err != nil { fmt.Println(err) return } defer conn.Close() // 生成EAP Request Identity包 reqIdPacket := generateEapPacket(EAPTypeIdentity, EAPCodeRequest, []byte{}) conn.Write(reqIdPacket) // 接收EAP Response Identity包 resIdPacket := make([]byte, 1024) len, err := conn.Read(resIdPacket) if err != nil { fmt.Println(err) return } // 解析EAP Response Identity包 resIdType := binary.BigEndian.Uint16(resIdPacket[18:20]) resIdData := resIdPacket[20:len] if resIdType != EAPTypeIdentity || len <= 20 { fmt.Println("Invalid EAP Response Identity packet") return } // 发送EAP Request MD5-Challenge包 reqMd5Packet := generateEapPacket(EAPTypeMD5Challenge, EAPCodeRequest, resIdData) conn.Write(reqMd5Packet) // 接收EAP Response MD5-Challenge包 resMd5Packet := make([]byte, 1024) len, err = conn.Read(resMd5Packet) if err != nil { fmt.Println(err) return } // 解析EAP Response MD5-Challenge包 resMd5Type := binary.BigEndian.Uint16(resMd5Packet[18:20]) resMd5Data := resMd5Packet[20:len] if resMd5Type != EAPTypeMD5Challenge || len <= 20 { fmt.Println("Invalid EAP Response MD5-Challenge packet") return } // 认证成功 successPacket := generateEapPacket(0, EAPCodeSuccess, []byte{}) conn.Write(successPacket) fmt.Println("Authentication succeeded") } func generateEapPacket(eapType uint8, eapCode uint8, eapData []byte) []byte { buf := new(bytes.Buffer) binary.Write(buf, binary.BigEndian, uint8(0)) // EAP Version binary.Write(buf, binary.BigEndian, eapCode) // EAP Code binary.Write(buf, binary.BigEndian, uint16(5+len(eapData))) // EAP Packet Length binary.Write(buf, binary.BigEndian, uint8(eapType)) // EAP Type binary.Write(buf, binary.BigEndian, uint8(0)) // EAP Identifier binary.Write(buf, binary.BigEndian, uint16(len(eapData))) // EAP Data Length binary.Write(buf, binary.BigEndian, eapData) // EAP Data return buf.Bytes() } ``` 以上代码连接到认证服务器,并使用EAP Identity和EAP MD5-Challenge协议进行认证。如果认证成功,将发送EAP Success包,如果认证失败,将发送EAP Failure包。 需要注意的是,以上代码只是一个简单的示例,实际的802.1x认证通常比这复杂得多。在实际应用中,您需要根据您的网络环境和认证服务器的要求进行相应的调整和修改。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Naisu Xu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值