原先家里的台式Windows电脑是常开的,但最近想折腾一下,用一台老笔记本电脑作为常开服务器,然后可以通过Wake On LAN技术按需的远程开启台式电脑。

首先,需要在BIOS中开启相关的设置(微星主板),包括:

  1. 进入高级–整合周边设备–网卡ROM启动,设置为允许。
  2. 进入高级–电源管理设置–Erp ,设置为禁止。
  3. 进入高级–唤醒事件设置–PCIE设备唤醒和网络唤醒,设置为允许。
  4. 最后,完成所有的设置之后,按下F10键保存BIOS并退出即可。

然后使用Golang写了一个简易的web唤醒程序,部署在老的笔记本电脑上。代码如下:

  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
package main

import (
	"encoding/hex"
	"log"
	"net"
	"net/http"

	"github.com/gin-gonic/gin"
	"github.com/spf13/viper"

	_ "embed"
)

var (
	hwAddr net.HardwareAddr
	ipAddr net.IP
)

//go:embed index.html
var htmlStr string

// 网页应用
// 1. 添加主机,包括ip、端口、mac地址(可以预先手动添加)
// 2. 发送数据包
// 3. 检测对应主机是否已启动
func main() {
	viper.SetConfigFile("config.yml")
	if err := viper.ReadInConfig(); err != nil {
		if _, ok := err.(viper.ConfigFileNotFoundError); ok {
			log.Fatalf("config file not found, create one: %v", err)
		} else {
			log.Fatalf("config file read error: %v", err)
		}
	}

	ip := viper.GetString("ip")
	mac := viper.GetString("mac")

	log.Printf("ip: %s, mac: %s", ip, mac)

	var err error
	hwAddr, err = net.ParseMAC(mac)
	if err != nil {
		log.Fatalf("invalid mac address: %s", mac)
	}
	ipAddr = net.ParseIP(ip)

	r := gin.Default()

	r.Use(gin.BasicAuth(gin.Accounts{
		"user": "password",
	}))
	r.GET("/wol", func(c *gin.Context) {
		err := WakeOnLAN(hwAddr, ipAddr, 9)
		if err != nil {
			log.Printf("error: %v", err)
			c.JSON(http.StatusInternalServerError, gin.H{
				"message": err.Error(),
			})
			return
		}

		c.JSON(http.StatusOK, gin.H{
			"message": "ok",
		})
	})

	r.GET("/", func(c *gin.Context) {
		c.Status(http.StatusOK)
		c.Header("Content-Type", "text/html")
		c.Writer.Write([]byte(htmlStr))
	})

	r.Run(viper.GetString("server_addr"))
}

func newMagicPacket(addr net.HardwareAddr) []byte {
	const (
		magicPacketHeader = "ffffffffffff"
		macRepeatCount    = 16
	)
	header, _ := hex.DecodeString(magicPacketHeader)
	packet := make([]byte, len(header)+(len(addr)*macRepeatCount))
	copy(packet, header)
	macs := make([]byte, len(addr)*macRepeatCount)
	for i := range macs {
		macs[i] = addr[i%len(addr)]
	}
	copy(packet[len(header):], macs)
	return packet
}

func WakeOnLAN(hwAddr net.HardwareAddr, ipAddr net.IP, wolPort int) error {
	conn, err := net.DialUDP("udp", nil, &net.UDPAddr{
		IP:   ipAddr,
		Port: wolPort,
	})
	if err != nil {
		return err
	}
	defer conn.Close()
	packet := newMagicPacket(hwAddr)
	for i := 0; i < 3; i++ {
		_, err = conn.Write(packet)
		if err != nil {
			return err
		}
	}
	return nil
}