Golang远程桌面唤醒工具 - 通过Wake-On-Lan实现桌面连接
Golang远程桌面唤醒工具 - 通过Wake-On-Lan实现桌面连接 如何用Go开发一个通过网络连接桌面电脑的Wake-On-Lan工具?
预期功能:
- 基于Web的应用程序,无需登录和密码(我们将通过IP控制访问,无需基础设施的反向代理)
- 桌面列表应用程序
- 每个工作空间旁边显示其启用或禁用状态的信息
- 用于开启桌面的按钮
详细信息:
- 要列出桌面,我们可以使用一个配置文件,其中包含桌面名称和已配置WoL的MAC地址。
- 要检查桌面是否已开启,我们可以研究通过MAC地址使用“ping”的方法(例如Linux中的工具:arping)。
- 要连接桌面,我们可以使用发送WoL数据包的库,甚至调用我们手动使用的外部命令。
- 我们将使用go modules,而不是gopath。
更多关于Golang远程桌面唤醒工具 - 通过Wake-On-Lan实现桌面连接的实战教程也可以访问 https://www.itying.com/category-94-b0.html
1 回复
更多关于Golang远程桌面唤醒工具 - 通过Wake-On-Lan实现桌面连接的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html
以下是一个完整的Go语言实现,包含Web界面、配置文件解析、WoL唤醒和状态检测功能:
// main.go
package main
import (
"encoding/json"
"fmt"
"html/template"
"log"
"net"
"net/http"
"os"
"os/exec"
"strings"
"sync"
"time"
)
// Desktop 结构体定义
type Desktop struct {
Name string `json:"name"`
MAC string `json:"mac"`
IP string `json:"ip"`
}
// Config 配置文件结构
type Config struct {
Desktops []Desktop `json:"desktops"`
Port int `json:"port"`
}
// 全局变量
var (
config Config
configFile = "config.json"
tmpl *template.Template
mu sync.RWMutex
)
func init() {
// 加载模板
tmpl = template.Must(template.ParseFiles("templates/index.html"))
}
func main() {
// 加载配置
loadConfig()
// 设置路由
http.HandleFunc("/", handleIndex)
http.HandleFunc("/wake", handleWake)
http.HandleFunc("/status", handleStatus)
http.HandleFunc("/api/desktops", handleAPI)
// 启动服务器
addr := fmt.Sprintf(":%d", config.Port)
log.Printf("服务器启动在 http://localhost%s", addr)
log.Fatal(http.ListenAndServe(addr, nil))
}
// 加载配置文件
func loadConfig() {
data, err := os.ReadFile(configFile)
if err != nil {
// 创建默认配置
config = Config{
Port: 8080,
Desktops: []Desktop{
{Name: "办公室电脑", MAC: "AA:BB:CC:DD:EE:FF", IP: "192.168.1.100"},
{Name: "开发服务器", MAC: "11:22:33:44:55:66", IP: "192.168.1.101"},
},
}
saveConfig()
return
}
if err := json.Unmarshal(data, &config); err != nil {
log.Fatalf("配置文件解析错误: %v", err)
}
}
// 保存配置
func saveConfig() {
data, err := json.MarshalIndent(config, "", " ")
if err != nil {
log.Printf("配置序列化错误: %v", err)
return
}
if err := os.WriteFile(configFile, data, 0644); err != nil {
log.Printf("保存配置文件错误: %v", err)
}
}
// 发送Wake-on-LAN魔术包
func sendWOL(mac string) error {
// 解析MAC地址
hwAddr, err := net.ParseMAC(mac)
if err != nil {
return fmt.Errorf("无效的MAC地址: %v", err)
}
// 创建魔术包: 6字节FF + 16次重复MAC地址
packet := make([]byte, 102)
for i := 0; i < 6; i++ {
packet[i] = 0xFF
}
for i := 1; i <= 16; i++ {
copy(packet[i*6:], hwAddr)
}
// 发送到广播地址
conn, err := net.Dial("udp", "255.255.255.255:9")
if err != nil {
// 尝试使用本地广播地址
conn, err = net.Dial("udp", "192.168.1.255:9")
if err != nil {
return fmt.Errorf("网络连接失败: %v", err)
}
}
defer conn.Close()
_, err = conn.Write(packet)
return err
}
// 检查桌面状态(使用ping)
func checkStatus(ip string) (bool, error) {
var cmd *exec.Cmd
// 根据操作系统使用不同的ping命令
if strings.Contains(strings.ToLower(os.Getenv("OS")), "windows") {
cmd = exec.Command("ping", "-n", "1", "-w", "1000", ip)
} else {
cmd = exec.Command("ping", "-c", "1", "-W", "1", ip)
}
err := cmd.Run()
return err == nil, nil
}
// 使用arping检查状态(Linux)
func checkStatusWithArping(mac, ip string) (bool, error) {
cmd := exec.Command("arping", "-c", "1", "-w", "1", ip)
output, err := cmd.CombinedOutput()
if err != nil {
return false, nil
}
// 检查输出中是否包含MAC地址
return strings.Contains(string(output), strings.ToUpper(mac)), nil
}
// 处理主页请求
func handleIndex(w http.ResponseWriter, r *http.Request) {
mu.RLock()
defer mu.RUnlock()
data := struct {
Desktops []Desktop
}{
Desktops: config.Desktops,
}
tmpl.Execute(w, data)
}
// 处理唤醒请求
func handleWake(w http.ResponseWriter, r *http.Request) {
if r.Method != "POST" {
http.Error(w, "方法不允许", http.StatusMethodNotAllowed)
return
}
mac := r.FormValue("mac")
if mac == "" {
http.Error(w, "缺少MAC地址", http.StatusBadRequest)
return
}
// 发送WoL包
if err := sendWOL(mac); err != nil {
http.Error(w, fmt.Sprintf("唤醒失败: %v", err), http.StatusInternalServerError)
return
}
w.WriteHeader(http.StatusOK)
w.Write([]byte("唤醒信号已发送"))
}
// 处理状态检查
func handleStatus(w http.ResponseWriter, r *http.Request) {
mac := r.URL.Query().Get("mac")
ip := r.URL.Query().Get("ip")
if mac == "" || ip == "" {
http.Error(w, "缺少参数", http.StatusBadRequest)
return
}
var isOnline bool
var err error
// 尝试使用arping(Linux)
if _, err := exec.LookPath("arping"); err == nil {
isOnline, err = checkStatusWithArping(mac, ip)
} else {
// 回退到ping
isOnline, err = checkStatus(ip)
}
if err != nil {
http.Error(w, fmt.Sprintf("状态检查失败: %v", err), http.StatusInternalServerError)
return
}
status := "offline"
if isOnline {
status = "online"
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]interface{}{
"status": status,
"mac": mac,
"ip": ip,
})
}
// API接口:获取所有桌面状态
func handleAPI(w http.ResponseWriter, r *http.Request) {
mu.RLock()
defer mu.RUnlock()
type DesktopStatus struct {
Desktop
Status string `json:"status"`
Timestamp int64 `json:"timestamp"`
}
var results []DesktopStatus
var wg sync.WaitGroup
var muResult sync.Mutex
for _, desktop := range config.Desktops {
wg.Add(1)
go func(d Desktop) {
defer wg.Done()
isOnline, _ := checkStatus(d.IP)
status := "offline"
if isOnline {
status = "online"
}
muResult.Lock()
results = append(results, DesktopStatus{
Desktop: d,
Status: status,
Timestamp: time.Now().Unix(),
})
muResult.Unlock()
}(desktop)
}
wg.Wait()
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(results)
}
<!-- templates/index.html -->
<!DOCTYPE html>
<html>
<head>
<title>远程桌面唤醒工具</title>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<style>
body {
font-family: Arial, sans-serif;
max-width: 800px;
margin: 0 auto;
padding: 20px;
background-color: #f5f5f5;
}
.container {
background: white;
border-radius: 8px;
padding: 20px;
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
}
h1 {
color: #333;
border-bottom: 2px solid #4CAF50;
padding-bottom: 10px;
}
.desktop-list {
margin-top: 20px;
}
.desktop-item {
display: flex;
justify-content: space-between;
align-items: center;
padding: 15px;
margin: 10px 0;
background: #f9f9f9;
border-radius: 5px;
border-left: 4px solid #ddd;
}
.desktop-info {
flex: 1;
}
.desktop-name {
font-weight: bold;
font-size: 16px;
color: #333;
}
.desktop-details {
font-size: 14px;
color: #666;
margin-top: 5px;
}
.status {
padding: 4px 12px;
border-radius: 12px;
font-size: 12px;
font-weight: bold;
margin-right: 15px;
}
.status.online {
background: #d4edda;
color: #155724;
}
.status.offline {
background: #f8d7da;
color: #721c24;
}
.btn {
padding: 8px 16px;
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: bold;
transition: background 0.3s;
}
.btn-wake {
background: #4CAF50;
color: white;
}
.btn-wake:hover {
background: #45a049;
}
.btn-wake:disabled {
background: #cccccc;
cursor: not-allowed;
}
.loading {
display: inline-block;
width: 20px;
height: 20px;
border: 3px solid #f3f3f3;
border-top: 3px solid #4CAF50;
border-radius: 50%;
animation: spin 1s linear infinite;
margin-right: 10px;
vertical-align: middle;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
</style>
</head>
<body>
<div class="container">
<h1>远程桌面唤醒工具</h1>
<div class="desktop-list">
{{range .Desktops}}
<div class="desktop-item" id="desktop-{{.MAC}}">
<div class="desktop-info">
<div class="desktop-name">{{.Name}}</div>
<div class="desktop-details">
MAC: {{.MAC}} | IP: {{.IP}}
</div>
</div>
<div>
<span class="status offline" id="status-{{.MAC}}">离线</span>
<button class="btn btn-wake" onclick="wakeDesktop('{{.MAC}}')" id="btn-{{.MAC}}">
唤醒
</button>
</div>
</div>
{{end}}
</div>
</div>
<script>
// 更新桌面状态
async function updateStatus(mac, ip) {
const statusElem = document.getElementById(`status-${mac}`);
const btnElem = document.getElementById(`btn-${mac}`);
statusElem.textContent = '检查中...';
statusElem.className = 'status';
btnElem.disabled = true;
try {
const response = await fetch(`/status?mac=${encodeURIComponent(mac)}&ip=${encodeURIComponent(ip)}`);
const data = await response.json();
if (data.status === 'online') {
statusElem.textContent = '在线';
statusElem.className = 'status online';
btnElem.disabled = true;
} else {
statusElem.textContent = '离线';
statusElem.className = 'status offline';
btnElem.disabled = false;
}
} catch (error) {
console.error('状态检查失败:', error);
statusElem.textContent = '错误';
btnElem.disabled = false;
}
}
// 唤醒桌面
async function wakeDesktop(mac) {
const btnElem = document.getElementById(`btn-${mac}`);
const originalText = btnElem.textContent;
btnElem.textContent = '唤醒中...';
btnElem.disabled = true;
try {
const formData = new FormData();
formData.append('mac', mac);
const response = await fetch('/wake', {
method: 'POST',
body: formData
});
if (response.ok) {
alert('唤醒信号已发送');
// 等待一段时间后重新检查状态
setTimeout(() => {
const desktop = getDesktopByMAC(mac);
if (desktop) {
updateStatus(mac, desktop.ip);
}
}, 5000);
} else {
alert('唤醒失败');
}
} catch (error) {
console.error('唤醒失败:', error);
alert('网络错误');
} finally {
btnElem.textContent = originalText;
btnElem.disabled = false;
}
}
// 获取桌面信息(简化实现)
function getDesktopByMAC(mac) {
// 这里应该从服务器获取或从页面数据中提取
// 简化实现:假设IP已知或从页面元素获取
const elem = document.getElementById(`desktop-${mac}`);
if (elem) {
const ipMatch = elem.textContent.match(/IP:\s*([\d\.]+)/);
return {
mac: mac,
ip: ipMatch ? ipMatch[1] : ''
};
}
return null;
}
// 页面加载时初始化状态检查
document.addEventListener('DOMContentLoaded', function() {
// 初始检查所有桌面状态
{{range .Desktops}}
setTimeout(() => updateStatus('{{.MAC}}', '{{.IP}}'), 100);
{{end}}
// 每30秒自动更新状态
setInterval(() => {
{{range .Desktops}}
updateStatus('{{.MAC}}', '{{.IP}}');
{{end}}
}, 30000);
});
</script>
</body>
</html>
// config.json 配置文件示例
{
"port": 8080,
"desktops": [
{
"name": "办公室电脑",
"mac": "AA:BB:CC:DD:EE:FF",
"ip": "192.168.1.100"
},
{
"name": "开发服务器",
"mac": "11:22:33:44:55:66",
"ip": "192.168.1.101"
}
]
}
// go.mod
module wol-tool
go 1.21
require (
golang.org/x/net v0.17.0
)
使用说明:
- 安装依赖:
go mod init wol-tool
go mod tidy
- 创建目录结构:
project/
├── main.go
├── go.mod
├── config.json
└── templates/
└── index.html
-
配置桌面: 编辑
config.json文件,添加你的桌面MAC地址和IP -
运行程序:
go run main.go
- 访问Web界面:
打开浏览器访问
http://localhost:8080
功能特点:
- 纯Go实现:不依赖外部命令(WoL包发送)
- 双状态检测:优先使用
arping,回退到ping - 实时状态更新:前端每30秒自动刷新
- 响应式设计:简洁的Web界面
- 并发处理:同时检查多个桌面状态
- 配置驱动:JSON配置文件管理桌面列表
注意事项:
- 确保网络设备支持Wake-on-LAN
- 需要在BIOS中启用WoL功能
- 网络交换机可能需要配置相关设置
- 防火墙需要允许UDP端口9的广播流量
- 对于跨子网唤醒,需要配置路由器转发WoL包
这个实现完全符合你的需求,无需登录认证(依赖IP访问控制),提供了完整的桌面列表、状态显示和唤醒功能。

