请教一个golang的问题,sync和channel不能一起使用么?我开发了一个开源的小应用,自动检测和更新版本,不过执行到Wait()好像总是死锁?
代码:https://coding.net/u/wangfeiping/p/Going/git/blob/master/src/GoHttpGet/main.go
问题出现在 374行 到 389行。
我想备份复制文件和下载同时进行,两个都完成之后再继续进行后面的处理。
```go
// GoHttpGet project main.go
package main
import (
"archive/zip"
//"bytes"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"log"
"net/http"
"os"
"os/exec"
"path/filepath"
"strings"
"sync"
)
type ConfigDetail struct {
//首字母必须大写,否则无法正常解析
Version string `json:"version"`
UpdateTime string `json:"updateTime"`
UpdateUrl string `json:"updateUrl"`
ServiceName string `json:"serviceName"`
DeployPath string `json:"deployPath"`
DeployWar string `json:"deployWar"`
BackupPath string `json:"backupPath"`
}
type UpdateDetail struct {
//首字母必须大写,否则无法正常解析
Version string `json:"version"`
UpdateTime string `json:"updateTime"`
DownloadUrl string `json:"downloadUrl"`
DeleteContents []string `json:"deleteContents"`
}
//golang使用http client发起get和post请求示例 http://ju.outofmemory.cn/entry/87147
//golang下载文件 http://outofmemory.cn/code-snippet/32779/originally-golang-file
//golang写个windows服务 http://my.oschina.net/u/130746/blog/226050
//windows service操作 https://github.com/kardianos/service
/**
用sc可打开被禁用的服务。
sc是用于与服务控制管理器和服务进行通信的命令行程序,其语法是:
sc config 服务名 start= demand //手动
sc condig 服务名 start= auto //自动
sc config 服务名 start= disabled //禁用
sc start 服务名
sc stop 服务名
*/
func httpGet(url string) (UpdateDetail, string, error) {
resp, err := http.Get(url)
if err != nil {
// handle error
}
defer resp.Body.Close()
body, err := ioutil.ReadAll(resp.Body)
if err != nil {
// handle error
}
str := string(body)
fmt.Println(str)
var res UpdateDetail
//如果使用err := 则会报错“no new variables on left side of :=”
//因为:= 表示声明和赋值,同一个变量重复声明是不允许的
//因此改为err =
err = json.Unmarshal([]byte(str), &res)
return res, str, err
}
func readFile(filename string) (ConfigDetail, error) {
var conf ConfigDetail
bytes, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Println("ReadFile: ", err.Error())
str := `{ "version" : "0" ,
"updateTime": "2015-03-23 13:50",
"updateUrl" : "https://s3-ap-southeast-2.amazonaws.com/3dview-package/update.json"
}`
err := json.Unmarshal([]byte(str), &conf)
return conf, err
}
if err := json.Unmarshal(bytes, &conf); err != nil {
fmt.Println("Unmarshal: ", err.Error())
return conf, err
}
return conf, nil
}
func localUpdateDetail(filename string) (UpdateDetail, error) {
var conf UpdateDetail
bytes, err := ioutil.ReadFile(filename)
if err != nil {
fmt.Println("ReadFile: ", err.Error())
return conf, err
}
if err := json.Unmarshal(bytes, &conf); err != nil {
fmt.Println("Unmarshal: ", err.Error())
return conf, err
}
return conf, nil
}
func download(url, exePath string) (filename string, n int64, err error) {
//Go的异常处理 defer, panic, recover http://blog.csdn.net/wuwenxiang91322/article/details/9042503
defer func() {
if r := recover(); r != nil {
fmt.Println("[E]", r)
download(url, exePath)
}
}()
path := strings.Split(url, "/")
var name string
if len(path) > 1 {
name = exePath + path[len(path)-1]
}
fmt.Println(name)
out, err := os.Create(name)
defer out.Close()
resp, err := http.Get(url)
defer resp.Body.Close()
//ioutil.ReadAll(resp.Body) 方法会将文件整个读取到内存中后才返回,如果文件较大,经常会造成下载失败
//pix, err := ioutil.ReadAll(resp.Body)
//n, err = io.Copy(out, bytes.NewReader(pix))
n, err = io.Copy(out, resp.Body)
return name, n, err
}
func startService(serviceName string) {
command := "sc"
args := "start " + serviceName
argArray := strings.Split(args, " ")
cmd := exec.Command(command, argArray...)
buf, err := cmd.Output()
if err != nil {
fmt.Fprintf(os.Stderr, "The command failed to perform: %s (Command: %s, Arguments: %s)", err, command, args)
return
}
fmt.Fprintf(os.Stdout, "Result: %s", buf)
}
func stopService(serviceName string) {
command := "sc"
args := "stop " + serviceName
argArray := strings.Split(args, " ")
cmd := exec.Command(command, argArray...)
buf, err := cmd.Output()
if err != nil {
fmt.Fprintln(os.Stderr, "The command failed to perform: %s (Command: %s, Arguments: %s)", err, command, args)
return
}
fmt.Fprintln(os.Stdout, "Result: %s", buf)
}
type FileInfo struct {
RelPath string
Size int64
IsDir bool
Handle *os.File
}
//复制文件数据
func ioCopy(srcHandle *os.File, dstPth string) (err error) {
dstHandle, err := os.OpenFile(dstPth, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
return err
}
defer srcHandle.Close()
defer dstHandle.Close()
_, err = io.Copy(dstHandle, srcHandle)
return err
}
//遍历目录,将文件信息传入通道
func WalkFiles(srcDir, suffix string, c chan<- *FileInfo) {
suffix = strings.ToUpper(suffix)
filepath.Walk(srcDir, func(f string, fi os.FileInfo, err error) error { //遍历目录
if err != nil {
log.Println("[E]", err)
}
fileInfo := &FileInfo{}
if strings.HasSuffix(strings.ToUpper(fi.Name()), suffix) { //匹配文件
if fh, err := os.OpenFile(f, os.O_RDONLY, os.ModePerm); err != nil {
log.Println("[E]", err)
} else {
fileInfo.Handle = fh
fileInfo.RelPath, _ = filepath.Rel(srcDir, f) //相对路径
fileInfo.Size = fi.Size()
fileInfo.IsDir = fi.IsDir()
}
c <- fileInfo
}
return nil
})
close(c) //遍历完成,关闭通道
}
//写目标文件
func WriteFiles(dstDir string, c <-chan *FileInfo, w sync.WaitGroup) {
if _, err := os.Stat(dstDir); os.IsNotExist(err) {
log.Println("[I]", "try to mkdir:", dstDir)
if err := os.MkdirAll(dstDir, os.ModeDir); err != nil {
log.Println("[E]", err)
}
}
if err := os.Chdir(dstDir); err != nil { //切换工作路径
log.Fatalln("[F]", err)
}
for f := range c {
if fi, err := os.Stat(f.RelPath); os.IsNotExist(err) { //目标不存在
if f.IsDir {
if err := os.MkdirAll(f.RelPath, os.ModeDir); err != nil {
log.Println("[E]", err)
}
} else {
if err := ioCopy(f.Handle, f.RelPath); err != nil {
log.Println("[E]", err)
} else {
//log.Println("[I] CP:", f.RelPath)
}
}
} else if !f.IsDir { //目标存在,而且源不是一个目录
if fi.IsDir() != f.IsDir { //检查文件名被目录名占用冲突
log.Println("[E]", "filename conflict:", f.RelPath)
} else if fi.Size() != f.Size { //源和目标的大小不一致时才重写
if err := ioCopy(f.Handle, f.RelPath); err != nil {
log.Println("[E]", err)
} else {
//log.Println("[I] CP:", f.RelPath)
}
}
}
}
w.Done()
fmt.Println("backup current files.")
}
func unzip(zipfile, tmp string, conf ConfigDetail) {
r, err := zip.OpenReader(zipfile)
if err != nil {
log.Fatal(err)
}
defer r.Close()
for _, f := range r.File {
//fmt.Println("FileName : ", f.Name)
//rc, err := f.Open()
//if err != nil {
// log.Fatal(err)
//}
//_, err = io.CopyN(os.Stdout, rc, 68) //打印文件内容
//if err != nil {
// if err != io.EOF {
// log.Fatal(err)
// }
//}
fi := f.FileInfo()
if fi.IsDir() {
//fmt.Println("IsDir FileName : ", f.Name)
if err := os.MkdirAll(tmp+f.Name, os.ModeDir); err != nil {
log.Println("[E]", err)
}
} else {
//fmt.Println("IsFile FileName : ", f.Name)
rc, err := f.Open()
if err != nil {
log.Fatal(err)
}
defer rc.Close()
fw, err := os.Create(tmp + f.Name)
if err != nil {
panic(err)
}
defer fw.Close()
_, err = io.Copy(fw, rc)
if err != nil {
panic(err)
}
}
}
fmt.Println("unzip ok.")
}
func main() {
//解析执行程序所在路径
file, _ := exec.LookPath(os.Args[0])
fmt.Println("file: " + file)
exeFile := filepath.Base(file)
fmt.Println("exeFile: " + exeFile)
exePath, _ := filepath.Abs(file)
fmt.Println("exePath: " + exePath)
if len(exePath) > 1 {
rs := []rune(exePath)
exePath = string(rs[0:(len(exePath) - len(exeFile))])
}
//读取配置文件并解析
config, err := readFile("conf.json")
//fmt.Println("config: ", config)
if err != nil {
fmt.Println("Config: ", err.Error())
}
//fmt.Println("Version: ", config.Version)
//fmt.Println("UpdateTime: ", config.UpdateTime)
fmt.Println("UpdateUrl: ", config.UpdateUrl)
fmt.Println("ServiceName: ", config.ServiceName)
fmt.Println("DeployPath: ", config.DeployPath)
fmt.Println("DeployWar: ", config.DeployWar)
fmt.Println("BackupPath: ", config.BackupPath)
//读取网络更新配置文件
update, updateStr, err := httpGet(config.UpdateUrl)
if err != nil {
fmt.Printf("err was %v", err)
}
fmt.Println(update)
fmt.Println("Version: " + update.Version)
fmt.Println("UpdateTime: " + update.UpdateTime)
fmt.Println("DownloadUrl: " + update.DownloadUrl)
//校验版本是否需要更新
localUpdateFile := exePath + "update.json"
localUpdate, err := localUpdateDetail(localUpdateFile)
if err != nil {
fmt.Println("Config: ", err.Error())
}
if strings.EqualFold(localUpdate.UpdateTime, update.UpdateTime) {
fmt.Println("No update package! Here is the last version.")
} else {
fmt.Println("Start update...")
doDownload(config, update, exePath, updateStr, localUpdateFile)
}
}
//func doBackup(w sync.WaitGroup, config ConfigDetail, name string) {
// files_ch := make(chan *FileInfo, 100)
// go WalkFiles(config.DeployPath+name, "", files_ch, w)
// WriteFiles(config.BackupPath+name, files_ch)
// fmt.Println("backup current files.")
//}
func doDownload(config ConfigDetail, update UpdateDetail, exePath, updateStr, localUpdateFile string) {
//清除旧的部署备份
war := strings.Split(config.DeployWar, ".")
var name string
if len(war) > 0 {
name = war[0]
}
//fmt.Println("name: ", name)
//删除之前的备份文件
os.RemoveAll(config.BackupPath + name)
os.RemoveAll(localUpdateFile)
fmt.Println("delete old backup")
//golang中的并发 sync和channel http://studygolang.com/articles/2027
//http://www.cnblogs.com/yjf512/archive/2013/01/30/2882570.html
//http://ifeve.com/go-concurrent-waitgroup/
var w sync.WaitGroup
w.Add(1)
//备分部署环境
//go doBackup(w, config, name)
files_ch := make(chan *FileInfo, 100)
go WalkFiles(config.DeployPath+name, "", files_ch)
go WriteFiles(config.BackupPath+name, files_ch, w)
//下载更新包
fmt.Println("Start download...")
updatePackageName, _, err := download(update.DownloadUrl, exePath)
if err != nil {
fmt.Println("Error in download: ", err.Error())
} else {
fmt.Println("download ok. " + updatePackageName)
w.Wait()
doUpdate(config, update, name, exePath, updateStr, updatePackageName, localUpdateFile)
}
}
func doUpdate(config ConfigDetail, update UpdateDetail, name, exePath, updateStr, updatePackageName, localUpdateFile string) {
fmt.Println("updating...")
//停止应用服务器
//stopService(config.ServiceName)
//清理部署环境
for _, delContent := range update.DeleteContents {
os.RemoveAll(config.DeployPath + name + "/" + delContent)
fmt.Println("delContent: ", delContent)
}
//path := updatePackageName
fmt.Println(updatePackageName)
//解压缩下载文件
tmp := config.BackupPath + "tmp/"
unzip(updatePackageName, tmp, config)
//部署代码
var w sync.WaitGroup
w.Add(1)
files_tmp := make(chan *FileInfo, 100)
go WalkFiles(tmp, "", files_tmp) //在一个独立的 goroutine 中遍历文件
go WriteFiles(config.DeployPath+name, files_tmp, w)
w.Wait()
//清理更新包下载临时解压文件
os.RemoveAll(tmp)
fmt.Println("delete unzip temp path")
//保存本地更新记录
reader := strings.NewReader(updateStr)
fw, err := os.Create(localUpdateFile)
if err != nil {
panic(err)
}
defer fw.Close()
_, err = io.Copy(fw, reader)
if err != nil {
panic(err)
}
fmt.Println("update ok.")
//启动应用服务器
//startService(config.ServiceName)
//fmt.Println("restart service ok.")
}
```
抱歉代码比较乱!
也可以直接在这里看,可能好一些。
https://coding.net/u/wangfeiping/p/Going/git/blob/master/src/GoHttpGet/main.go
#2