<?xml version="1.0" encoding="utf-8" standalone="yes" ?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
  <channel>
    <title>Windows on chai2010 的博客</title>
    <link>https://chai2010.cn/categories/windows/</link>
    <description>Recent content in Windows on chai2010 的博客</description>
    <generator>Hugo -- gohugo.io</generator>
    <language>zh-CN</language>
    <lastBuildDate>Sun, 21 Oct 2018 00:00:00 +0000</lastBuildDate>
    
        <atom:link href="https://chai2010.cn/categories/windows/index.xml" rel="self" type="application/rss+xml" />
    
    
    <item>
      <title>Go语言和Windows服务</title>
      <link>https://chai2010.cn/post/2018/window-service/</link>
      <pubDate>Sun, 21 Oct 2018 00:00:00 +0000</pubDate>
      
      <guid>https://chai2010.cn/post/2018/window-service/</guid>
      
        <description>&lt;p&gt;Windows服务使您能够创建在后台Windows会话中可长时间运行的可执行应用程序。Windows服务可以在计算机启动时自动启动，管理员也可以临时暂停和重新启动服务。Windows服务非常适合运行一些需要长时间在后台运行的服务器程序，例如Web服务器等应用。&lt;/p&gt;

&lt;p&gt;&lt;/p&gt;

&lt;p&gt;Go语言的官方扩展包&lt;code&gt;&amp;quot;golang.org/x/sys/windows&amp;quot;&lt;/code&gt;以及其子包对Windows服务提供了必要的支持。不过这个扩展包比较偏向底层使用比较繁琐，为了简化Windows服务的开发作者在此基础上封装了一个简化的&lt;code&gt;&amp;quot;github.com/chai2010/winsvc&amp;quot;&lt;/code&gt;包。通过封装的&lt;code&gt;winsvc&lt;/code&gt;包我们可以很容易构造一个windows服务。&lt;/p&gt;

&lt;h2 id=&#34;简单的web服务&#34;&gt;简单的web服务&lt;/h2&gt;

&lt;p&gt;因为Windows服务一般是在后台长时间运行的程序，为了便于演示我们先构造一个简单的现实当前服务器时间的http服务程序。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;package main

import (
	&amp;quot;context&amp;quot;
	&amp;quot;net&amp;quot;
	&amp;quot;net/http&amp;quot;
	&amp;quot;time&amp;quot;
)

var (
	server *http.Server
)

func main() {
	StartServer()
}

func StartServer() {
	log.Println(&amp;quot;StartServer, port = 8080&amp;quot;)
	http.HandleFunc(&amp;quot;/&amp;quot;, func(w http.ResponseWriter, r *http.Request) {
		fmt.Fprintln(w, &amp;quot;winsrv server&amp;quot;, time.Now())
	})

	server = &amp;amp;http.Server{Addr: &amp;quot;:8080&amp;quot;}
	server.ListenAndServe()
}

func StopServer() {
	if server != nil {
		server.Shutdown(context.Background()) // Go 1.8+
	}
	log.Println(&amp;quot;StopServer&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;其中，&lt;code&gt;StartServer&lt;/code&gt;和&lt;code&gt;StopServer&lt;/code&gt;函数分别对应服务的启动和停止操作。在这个程序中，&lt;code&gt;StopServer&lt;/code&gt;函数并没有用到，我们只需要通过&lt;code&gt;CTRL+C&lt;/code&gt;强制停止服务就可以了。但是对于Windows服务程序，我们不能用暴力的方式强制终止程序，因此需要封装一个程序可以主动停止的函数。&lt;/p&gt;

&lt;h2 id=&#34;windows服务的运行环境&#34;&gt;Windows服务的运行环境&lt;/h2&gt;

&lt;p&gt;因为普通的程序无法处理Windows服务特有的消息，普通的Go程序也无法在服务模式运行。我们通过&lt;code&gt;&amp;quot;github.com/chai2010/winsvc&amp;quot;&lt;/code&gt;包启动的服务可以吹Windows服务特有的消息，因此也就可以支持服务模式运行。同时Windows服务程序需要在后台长时间运行不能随意退出，普通的小程序是不能作为Windows服务来运行的。&lt;/p&gt;

&lt;p&gt;如果要提供Windows服务模式的支持, main需要做适当调整:&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;import (
	&amp;quot;github.com/chai2010/winsvc&amp;quot;
)

func main() {
	// run as service
	if !winsvc.IsAnInteractiveSession() {
		log.Println(&amp;quot;main:&amp;quot;, &amp;quot;runService&amp;quot;)
		if err := winsvc.RunAsService(&amp;quot;myserver&amp;quot;, StartServer, StopServer, false); err != nil {
			log.Fatalf(&amp;quot;svc.Run: %v\n&amp;quot;, err)
		}
		return
	}

	// run as normal
	StartServer()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;程序中通过&lt;code&gt;winsvc.IsAnInteractiveSession&lt;/code&gt;来判断是否运行在交互模式，普通程序运行一般都是交互模式，windows服务则是运行在非交互模式。当程序处在非交互模式时，我们通过&lt;code&gt;winsvc.RunAsService&lt;/code&gt;来运行服务，也就是以Windows服务的模式运行。同时该程序依然可以在普通模式下运行。&lt;/p&gt;

&lt;p&gt;当程序运行在名为&lt;code&gt;myserver&lt;/code&gt;服务模式时，提供对Windows服务相关消息的处理支持。可以通过管理员手工注册Windows服务，这时需要指定服务名称和服务程序的绝对路径。下面四个命令分别是注册服务、启动服务、停止服务、删除服务：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;sc  create myserver binPath= &amp;quot;C:\path\to\myserver.exe -data-dir=C:\path\myserver.data&amp;quot;
net start  myserver
net stop   myserver
sc  delete myserver
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;因为Windows服务启动时并不需要登录用户帐号，因此程序不能引用普通帐号的环境变量，同时要尽量避免通过相对路径依赖当前目录。&lt;/p&gt;

&lt;h2 id=&#34;自动注册服务&#34;&gt;自动注册服务&lt;/h2&gt;

&lt;p&gt;手工注释Windows服务比较繁琐，我们可以在程序的命令行参赛中增加自动注册服务的支持。&lt;/p&gt;

&lt;p&gt;要在程序中将程序本身注册为服务，首先需要获取当前程序的绝对路径。我们可以通过&lt;code&gt;winsvc.GetAppPath()&lt;/code&gt;来获取当前程序的绝对路径。同时，为了让服务程序在运行时有一个固定的当前目录，我们一般可以在启动的时候将当前目录切换到进程所在目录，这些工作可以在&lt;code&gt;init&lt;/code&gt;函数中完成：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;var (
	appPath string // 程序的绝对路径
)

func init() {
	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)
	}
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;注册服务可以通过&lt;code&gt;winsvc.InstallService&lt;/code&gt;实现，注册服务是需要指定服务程序的路径和唯一服务的名称：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func main() {
	if err := winsvc.InstallService(appPath, &amp;quot;myserver&amp;quot;, &amp;quot;myserver service&amp;quot;); err != nil {
		log.Fatal(err)
	}
	fmt.Printf(&amp;quot;Done\n&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;和注册服务相对应的是取消注册服务，取消注册服务可以通过&lt;code&gt;winsvc.RemoveService&lt;/code&gt;实现，直接通过服务的名称就可以删除服务：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func main() {
	if err := winsvc.RemoveService(&amp;quot;myserver&amp;quot;); err != nil {
		log.Fatal(err)
	}
	fmt.Printf(&amp;quot;Done\n&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;Windows服务在成功注册之后就可以以服务模式运行了，可以通过&lt;code&gt;winsvc.StartService&lt;/code&gt;向服务发送启动消息：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func main() {
	if err := winsvc.StartService(&amp;quot;myserver&amp;quot;); err != nil {
		log.Fatal(err)
	}
	fmt.Printf(&amp;quot;Done\n&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;对于已经在运行的Windows服务，可以通过&lt;code&gt;winsvc.StopService&lt;/code&gt;向服务发送停止运行的命令。Windows服务在收到停止运行的命令后，会在程序退出之前调用&lt;code&gt;StopServer&lt;/code&gt;函数，&lt;code&gt;StopServer&lt;/code&gt;函数是在启动Windows服务时由&lt;code&gt;winsvc.RunAsService&lt;/code&gt;函数参数指定。&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;func main() {
	if err := winsvc.StopService(&amp;quot;myserver&amp;quot;); err != nil {
		log.Fatal(err)
	}
	fmt.Printf(&amp;quot;Done\n&amp;quot;)
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;现在我们可以将这些功能整合在一起，然后通过命令行参数来选择具体的命令。下面是完整的例子：&lt;/p&gt;

&lt;pre&gt;&lt;code class=&#34;language-go&#34;&gt;var (
	appPath string

	flagServiceName = flag.String(&amp;quot;service-name&amp;quot;, &amp;quot;myserver&amp;quot;, &amp;quot;Set service name&amp;quot;)
	flagServiceDesc = flag.String(&amp;quot;service-desc&amp;quot;, &amp;quot;myserver service&amp;quot;, &amp;quot;Set service description&amp;quot;)

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

func init() {
	// 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(&amp;quot;installService(%s, %s): %v\n&amp;quot;, *flagServiceName, *flagServiceDesc, err)
		}
		fmt.Printf(&amp;quot;Done\n&amp;quot;)
		return
	}

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

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

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

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

	// run as normal
	StartServer()
}
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;假设程序构成的目标文件为&lt;code&gt;myserver.exe&lt;/code&gt;，那么我们现在可以通过以下命令来分别注册服务、启动和停止服务、删除服务：&lt;/p&gt;

&lt;pre&gt;&lt;code&gt;# 普通模式运行
$ go build -o myserver.exe myserver.go
$ myserver.exe

# 注册为Windows服务
$ myserver.exe -service-install

# 启动和停止Windows服务
$ myserver.exe -service-start
$ myserver.exe -service-stop

# 删除服务
# 删除之前需要先停止服务
$ myserver.exe -service-remove
&lt;/code&gt;&lt;/pre&gt;

&lt;p&gt;在前面的章节中，我们演示过一个WebDAV的服务。读者可以尝试实现一个支持Windows后台服务模式运行的WebDAV的服务器。&lt;/p&gt;</description>
      
    </item>
    
  </channel>
</rss>