golang开发windows服务winsvc & service

winsvc

winsvc 包是对 golang.org/x/sys/windows 的封装,方便调用。底层调用的是sc命令来完成对服务的管理。

windows服务是脱离于登录用户而存在的,因此必须由管理员来管理。

// 管理员身份

// 注册服务
sc create ServiceName binpath="/path/to/exe"

// 删除服务
sc delete ServiceName

// 启动服务
sc start ServiceName

// 停止服务
sc stop ServiceName
示例代码
package main

import (
	"flag"
	"fmt"
	"log"
	"net/http"
	"os"
	"path/filepath"
	"time"

	"github.com/chai2010/winsvc"
)

var (
	appPath string

	flagServiceName = flag.String("service-name", "hello-winsvc", "Set service name")
	flagServiceDesc = flag.String("service-desc", "hello windows service", "Set service description")

	flagServiceInstall   = flag.Bool("service-install", false, "Install service")
	flagServiceUninstall = flag.Bool("service-remove", false, "Remove service")
	flagServiceStart     = flag.Bool("service-start", false, "Start service")
	flagServiceStop      = flag.Bool("service-stop", false, "Stop service")
)

func init() {
	flag.Usage = func() {
		fmt.Fprintf(os.Stderr, `
Usage:
  hello [options]...

Options:
`)
		flag.PrintDefaults()
		fmt.Fprintf(os.Stderr, "%s\n", `
Example:
  # run hello server
  $ go build -o hello.exe hello.go
  $ hello.exe

  # install hello as windows service
  $ hello.exe -service-install

  # start/stop hello service
  $ hello.exe -service-start
  $ hello.exe -service-stop

  # remove hello service
  $ hello.exe -service-remove

  # help
  $ hello.exe -h

Report bugs to <chaishushan{AT}gmail.com>.`)
	}

	// change to current dir
	var err error
	if appPath, err = winsvc.GetAppPath(); err != nil {
		log.Fatal(err)
	}
	if err := os.Chdir(filepath.Dir(appPath)); err != nil {
		log.Fatal(err)
	}
}

func main() {
	flag.Parse()

	// install service
	if *flagServiceInstall {
		if err := winsvc.InstallService(appPath, *flagServiceName, *flagServiceDesc); err != nil {
			log.Fatalf("installService(%s, %s): %v\n", *flagServiceName, *flagServiceDesc, err)
		}
		fmt.Printf("Done\n")
		return
	}

	// remove service
	if *flagServiceUninstall {
		if err := winsvc.RemoveService(*flagServiceName); err != nil {
			log.Fatalln("removeService:", err)
		}
		fmt.Printf("Done\n")
		return
	}

	// start service
	if *flagServiceStart {
		if err := winsvc.StartService(*flagServiceName); err != nil {
			log.Fatalln("startService:", err)
		}
		fmt.Printf("Done\n")
		return
	}

	// stop service
	if *flagServiceStop {
		if err := winsvc.StopService(*flagServiceName); err != nil {
			log.Fatalln("stopService:", err)
		}
		fmt.Printf("Done\n")
		return
	}

	// run as service
	if !winsvc.InServiceMode() {
		log.Println("main:", "runService")
		if err := winsvc.RunAsService(*flagServiceName, StartServer, StopServer, false); err != nil {
			log.Fatalf("svc.Run: %v\n", err)
		}
		return
	}

	// run as normal
	StartServer()
}

func StartServer() {
	log.Println("StartServer, port = 8080")
	http.HandleFunc("/", func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, "winsrv server", time.Now())
	})
	http.ListenAndServe(":8080", nil)
}

func StopServer() {
	log.Println("StopServer")
}

go get -u github.com/chai2010/winsvc
go build

// 注册服务
go-srv.exe -service-name="go-srv" -service-desc="test go service" -service-install
2021/11/30 17:06:29 installService(go-srv, test go service): Access is denied.

// 移除服务
go-srv.exe -service-name="go-srv" -service-remove
......

Access is denied这个错误是权限的问题。本机是 win10,用的账号是 Administrator。

于是我在另一个电脑(win7)上做了同样的操作,发现是可以注册成功的。打开任务管理器,就可以找到go-srv服务,右键,启动服务,访问 localhost:8080正常。

尝试1:

打开注册表

win + r --> regedit -->找到路径 HKEY_LOCAL_MACHINE\Software\Microsoft\Windows\CurrentVersion\Policies\System  --> 将 EnableLUA 改成 0

发现还是不行,难道要重启吗,想了想,这个地方还是不能改,恢复为1。

于是我又打开了win7电脑,发现这个值是1。

尝试2:

在同级目录下创建srv.bat

d:
cd dev\php\magook\trunk\server\go-srv
go-srv.exe -service-name="go-srv" -service-desc="test go service" -service-install
cmd

右键,以管理员身份运行。

输出

2021/12/01 11:42:41 installService(go-srv, test go service): A system shutdown is in progress.

这次不是权限的问题了,看来在win10中,administrator账户并不是管理员,或者说他也不具备全部的权限,或者说这个电脑被前面的同事做了什么设置。

那么这个A system shutdown is in progress.错误又是什么鬼?第六感觉得重启应该可以解决这个问题,但是又要开一堆软件,嫌麻烦。

尝试3:

win + Q --> 输入 cmd --> 右键,以管理员身份运行

> d:
> cd dev\php\magook\trunk\server\go-srv
> go-srv.exe -service-name="go-srv" -service-desc="test go service" -service-install
2021/12/01 11:42:41 installService(go-srv, test go service): A system shutdown is in progress.

效果是一样的。

service

service包也是一样的思路。

示例代码
package main

import (
	"fmt"
	"log"
	"os"

	"github.com/kardianos/service"
)

var serviceConfig = &service.Config{
	Name:        "go-srv",
	DisplayName: "go-srv",
	Description: "test go service",
}

func main() {

	// 构建服务对象
	prog := &Program{}
	s, err := service.New(prog, serviceConfig)
	if err != nil {
		log.Fatal(err)
	}

	// 用于记录系统日志
	logger, err := s.Logger(nil)
	if err != nil {
		log.Fatal(err)
	}

	if len(os.Args) < 2 {
		err = s.Run()
		if err != nil {
			logger.Error(err)
		}
		return
	}

	cmd := os.Args[1]

	if cmd == "install" {
		err = s.Install()
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println("安装成功")
	}
	if cmd == "uninstall" {
		err = s.Uninstall()
		if err != nil {
			log.Fatal(err)
		}
		fmt.Println("卸载成功")
	}

	// install, uninstall, start, stop 的另一种实现方式
	// err = service.Control(s, os.Args[1])
	// if err != nil {
	// 	log.Fatal(err)
	// }
}

type Program struct{}

func (p *Program) Start(s service.Service) error {
	log.Println("开始服务")
	go p.run()
	return nil
}

func (p *Program) Stop(s service.Service) error {
	log.Println("停止服务")
	return nil
}

func (p *Program) run() {
	// 此处编写具体的服务代码
}
go get -u github.com/kardianos/service
go build

win + Q --> 输入 cmd --> 右键,以管理员身份运行

go-srv.exe install
2021/12/01 16:11:43 A system shutdown is in progress.

还是一样的错误。

到这里,不得不重启了!!

重启之后果然就好了。接着测试。

// 管理员身份运行cmd
打开任务管理器,点到服务栏

> go-srv.exe -service-name="go-srv" -service-desc="test go service" -service-install
  Done
但是并没有多出一个 go-srv 服务,当然重新打开任务管理器就可以看到了。但是我们可以调用启动命令来使其展示出来。

> go-srv.exe -service-name="go-srv" -service-start
看到了 go-srv 服务,且正在运行。

> go-srv.exe -service-name="go-srv" -service-remove
发现没有什么效果,服务依然在运行着。

> go-srv.exe -service-name="go-srv" -service-stop
停止服务,同时发现,服务被移除了,所以应该先 stop 再来 remove。
Go语言提供了一个内置的包`net/http`,可以用来创建HTTP服务器。在Windows上,可以将这个HTTP服务器作为一个Windows服务运行。 下面是一个简单的示例代码,演示如何使用Go语言创建一个Windows服务程序: ```go package main import ( &quot;fmt&quot; &quot;log&quot; &quot;net/http&quot; &quot;os&quot; &quot;os/signal&quot; &quot;syscall&quot; &quot;time&quot; &quot;golang.org/x/sys/windows/svc&quot; &quot;golang.org/x/sys/windows/svc/debug&quot; ) type myService struct{} func (m *myService) Execute(args []string, r &lt;-chan svc.ChangeRequest, changes chan&lt;- svc.Status) (ssec bool, errno uint32) { const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown changes &lt;- svc.Status{State: svc.StartPending} go func() { // 启动HTTP服务器 http.HandleFunc(&quot;/&quot;, func(w http.ResponseWriter, r *http.Request) { fmt.Fprintf(w, &quot;Hello, World!&quot;) }) err := http.ListenAndServe(&quot;:8080&quot;, nil) if err != nil { log.Fatal(err) } }() changes &lt;- svc.Status{State: svc.Running, Accepts: cmdsAccepted} loop: for { select { case c := &lt;-r: switch c.Cmd { case svc.Interrogate: changes &lt;- c.CurrentStatus time.Sleep(100 * time.Millisecond) changes &lt;- c.CurrentStatus case svc.Stop, svc.Shutdown: break loop default: log.Printf(&quot;unexpected control request #%d&quot;, c) } } } changes &lt;- svc.Status{State: svc.StopPending} return } func main() { isIntSess, err := svc.IsAnInteractiveSession() if err != nil { log.Fatal(err) } if isIntSess { runService() return } debug.Run(&quot;MyService&quot;, &amp;myService{}) } func runService() { err := svc.Run(&quot;MyService&quot;, &amp;myService{}) if err != nil { log.Fatal(err) } } ``` 这个示例代码创建了一个简单的HTTP服务器,监听在本地的8080端口。当将这个程序安装为Windows服务后,可以通过访问`http://localhost:8080`来访问该服务。 要将这个程序安装为Windows服务,可以使用`sc`命令或者使用第三方工具如`nssm`。下面是使用`sc`命令的示例: 1. 打开命令提示符,以管理员身份运行。 2. 进入程序所在的目录。 3. 执行以下命令安装服务:`sc create MyService binPath= &quot;&lt;程序路径&gt;&quot; start= auto` 4. 执行以下命令启动服务:`sc start MyService` 现在,你可以通过访问`http://localhost:8080`来测试这个服务
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值