Go module
1.配置方法
安装完 go 之后通过 go env 查看当前的环境
开启go module
go env -w GO111MODULE=on或者export GO111MODULE=on
GoPROXY配置镜像
默认GoPROXY配置是:GOPROXY=https://proxy.golang.org,direct,由于国内访问不到[https://proxy.golang.org](https://proxy.golang.org),所以我们需要换一个PROXY
# 阿里云镜像GOPROXY=https://mirrors.aliyun.com/goproxy/#中国golang镜像GOPROXY=https://goproxy.io#七牛云为中国的gopher提供了一个免费合法的代理goproxy.cn,其已经开源。只需一条简单命令就可以使用该代理:GOPROXY=https://goproxy.cn,direct
初始化go module环境
带git的项目
直接从github上面clone一个项目下来。
直接执行 go mod init 会自动生成带git地址的packagename
执行命令:
go mod init
不带git的项目
切换到当前目录下:
cd mysql 
直接执行
go mod init mysql
可以看到当前文件夹下多了go.mod 文件
运行项目
go run main.go
就可以自动下载依赖包,也可以手动通过 go get github.com/go-sql-driver/mysql 来下载
但是注意:
由于go的一些官方文档被墙的原因,会导致网络连接超时,下载失败。
要通过代理地址来下载
执行命令:
go env -w GOPROXY=https://goproxy.cn
再次执行下载就成功了。
下载成功之后就多了一个go.sum文件
https://www.cnblogs.com/wongbingming/p/12941021.html
https://studygolang.com/articles/24517?fr=sidebar
基础语法
一、变量声明以及使用
变量名 变量类型 a int,声明后若不赋值,就是用默认值 ```javascript package main
import ( “fmt” “math” ) // 变量名 变量类型 没有初始值的情况下 int 默认是0,string默认是 “” func variable() { var a int var s string fmt.Printf(“%d %q\n”, a, s) // Printf可以打印空值 0 “” }
// 函数外面定义变量必须用 var 不能用 := func main() { fmt.Println(“yyyy”) variable() }
2. 可同时声明多个变量 a, b int = 3, 4```javascript// 同时定义多个变量并且赋初始值func variableInitialValue(){var a, b int = 3, 4var s string = "abc"fmt.Println(a, b, s) // 3, 4, abc}
省略类型,编译器可根据初始值推测出类型
// 省略类型,并且多类型可以一起定义,通过赋的初始值,编译器可推测出类型func variableTypeDeduction() {var a, b, c, s = 3, 4, true, "def"fmt.Println(a, b, c, s)}
省略var,使用 := 定义变量
// 函数内部使用 := 定义变量, 省略var, 可以重复赋值func variableShorter() {a, b, c, s := 3, 4, true, "def"fmt.Println(a,b,c,s)}
注意:这种方式定义变量仅限于函数内部,全局定义变量不能使用 :=
定义常量,使用const 关键字
// 常量的定义func consts() {const filename = "abc.txt"fmt.Println(filename)}
使用常量定义枚举类型,通过const 块来定义 ```javascript // 使用常量定义枚举类型 通过const块来定义 func enums() { //普通枚举 // const ( // filename = “abc” // a, b = 3, 4 // ) // 自增值枚举 const (
cpp = iota // iota 表示自增值javapyhtongolang
) fmt.Println(cpp, java, pyhton, golang) // 0, 1, 2, 3 }
func enums2() { const ( cpp = iota // iota 表示自增值 _ // 表示跳过 1 java pyhton golang ) fmt.Println(cpp, java, pyhton, golang) // 0, 1, 2, 3 }
iota表示自增, _ 表示跳过7. 类型转换, go只有强制类型转换,没有隐式类型转换```javascript// 强制类型转换,没有隐式类型转换func triangle() {var a, b int = 3, 4var c intc = int(math.Sqrt(float64(a*a + b*b))) // Sqrt 接收的参数是 float64,返回的也是 float64fmt.Println(c)}
二、条件语句
if if条件里不需要括号
func main(){const filename = "abc.txt"contents, err := ioutil.ReadFile(filename) // ioutil.ReadFile 返回两个参数,第一个是文件内容、第二个是报错信息if err != nil {fmt.Println(err) // open abc.txt: no such file or directory}else {fmt.Printf("%s\n", contents)}}
if语句还支持初始化语句,初始化语句和条件表达式之间用分号隔开
func main(){const filename = "abc.txt"if contents, err := ioutil.ReadFile(filename); err != nil {fmt.Println(err)}else {fmt.Printf("%s\n", contents)}}
swich
switch会自动break,除非使用 fallthrough
func grade(score int) string { // 传入 int类型的参数,返回值是string类型g := ""switch{case score < 0 || score > 100 :panic(fmt.Sprintf("Wrong score: %d", score))case score < 60:g = "F"case score < 80:g = "C"case score < 90:g = "B"case score <= 100:g = "A"}return g}
三、for循环
for 的条件里不需要括号
for的条件里可以省略初始条件,结束条件,递增表达式
整数转二进制:
package mainimport ("fmt""strconv")func covertToBin(n int) string {result := ""for ; n > 0; n/=2 { //lsb := n % 2result = strconv.Itoa(lsb) + result}return result}func main() {fmt.Println(covertToBin(13)) // 1101}
四、函数
1.函数定义       
函数名在前,函数返回值的类型在后
package mainimport ("fmt")// 参数 a, b int, op string 返回值 intfunc eval(a, b int, op string) (int, error) { // 可以让它返回两个值,一个是成功时返回的值,一个是出错时返回的值可以处理错误,而不用中断程序的执行switch op {case "+":return a + b, nilcase "-":return a - b, nilcase "*":return a * b, nilcase "/":return a / b, nildefault:// panic("unsupported operation:" + op)return 0, fmt.Errorf("unsupported operation: %s", op,)}}func main() {fmt.Println(eval(2, 5, "l")) // 0 unsupported operation: l}
2.多返回值
// 多个返回值func div1(a, b int) (q, r int) {return a / b, a % b}// 函数返回多个值时可以起名字,仅用于非常简单的函数func div2(a, b int) (q, r int) {q = a / br = a % breturn}
3.可变参数列表
// 可变参数列表func sum(numbers ...int) int { // ... int 表示numbers里面可以有任意多个参数fmt.Println(numbers) // [1 2 3 4 5]s := 0for i := range numbers {s += numbers[i]}return s}func main() {fmt.Println(sum(1,2,3,4,5)) // 15}
五、指针
go参数传递只有值传递
相当于拷贝了一份新的值,这份新的可以任意修改,都不会影响到旧的
通过指针可以实现 引用传递的效果
- int类型
 

&a 和 pa的指针同时指向 a
当修改了 pa 的同时也修改了 a
- object
 
cache本身是一个指向data的指针,参数传递的时候相当于是拷贝了一份指针,都指向 data

go语言中的自定义类型在定义的时候就需要考虑到我们定义的是当作值来用呢还是指针呢
像第二个例子就可以当作值来用,拷贝一份新的,让它修改data
但如果cache维护的状态比较多那就不能当作指针来用。
值交换,要改变变量的值必须传指针进去
// 指针参数传递func swap(a, b *int) {// b, a = a, b // 这么不生效,因为是值传递,怎么修改都不会改变原始值*b, *a = *a, *b}func main() {a, b := 3, 4swap(&a, &b)fmt.Println(a, b) // 4, 3}
第二种方式:
// 有返回值的可以func swap(a, b int) (int, int){return b, a}func main() {a, b := 3, 4a, b = swap(a, b)fmt.Println(a, b) // 4, 3}
六、数组
1.数组的定义 数组名 [数组长度] 数组类型,若不给数组长度就是slice切片
package mainimport ("fmt")func main() {var arr1 [5]intarr2 := [3]int{1, 2, 3}arr3 := [...]int{2,4,6,8,10}var grid [4][5]intfmt.Println("array definitions:")fmt.Println(arr1, arr2, arr3)fmt.Println(grid)}
通过var定义的数组可以不给初始值,通过 := 定义的数组必须给初始值,也可以通过 […] 的方式定义不定长度的数组
2.遍历数组的方法
有传统 for 循环和 range 方法
// 不推荐for i:= 0; i < len(arr3); i++ {fmt.Println(arr3[i])}// range 关键字可同时获取到 index和valuefor i, v := range arr3 {fmt.Println(i, v)}// 可通过 _ 省略变量 若不想获取 i 可使用 _ 忽略for _, v := range arr3 {fmt.Println(v)}
3.数组是值传递类型
package mainimport "fmt"func printArray(arr [5]int) {arr[0] = 100for i, v := range arr {fmt.Println(i, v)}}func main() {var arr1 [5]intarr2 := [3]int{1, 3, 5}arr3 := [...]int{2, 4, 6, 8, 10}var grid [4][5]intfmt.Println("printArray(arr1)")printArray(arr1)fmt.Println("printArray(arr3)")printArray(arr3)fmt.Println("arr1 and arr3")fmt.Println(arr1, arr3)}
如下结果:
arr1 和 arr3 会拷贝,改变拷贝数组不会影响到原数组
可使用指针改变原数组
package mainimport ("fmt")//数组是值类型func printArray(arr *[5]int){arr[0] = 100for i, v := range arr {fmt.Println(i, v)}}func main() {var arr1 [5]intarr2 := [3]int{1, 2, 3}arr3 := [...]int{2,4,6,8,10}var grid [4][5]intfmt.Println("printArray(arr1)")printArray(&arr1) // 100fmt.Println("printArray(arr3)")printArray(&arr3) // 100fmt.Println("arr1 and arr3")fmt.Println(arr1, arr3) // 不变}
结果如下:
这样就可以改变原数组
但是这样使用数组有些麻烦,go语言中一般不用数组,而是使用切片
七、切片slice
go语言中一般不使用数组,而是用slice
slice 并不是数组或数组指针。而是数组的一个引用,因此是引用类型,但自身是结构体,是值拷贝传递。
1.创建切片
和数组创建的方式唯一不同的是创建数组需要声明数组长度 arr [5] int,切片不用声明数组长度 arr [] int
全局:var arr = [...]int{0, 1, 2, 3, 4, 5, 6, 7, 8, 9}var slice0 []int = arr[start:end]var slice1 []int = arr[:end]var slice2 []int = arr[start:]var slice3 []int = arr[:]var slice4 = arr[:len(arr)-1] //去掉切片的最后一个元素局部:arr2 := [...]int{9, 8, 7, 6, 5, 4, 3, 2, 1, 0}slice5 := arr[start:end]slice6 := arr[:end]slice7 := arr[start:]slice8 := arr[:]slice9 := arr[:len(arr)-1] //去掉切片的最后一个元素
package mainimport "fmt"func main() {// 第一种arr := [...]int {0, 1, 2, 3, 4, 5, 6, 7}fmt.Println("arr[2:6] =", arr[2:6]) // [2 3 4 5]fmt.Println("arr[:6] =", arr[:6]) // [0 1 2 3 4 5]// 第二种var s []int // Zero value for slice is nilfor i := 0; i < 100; i++ {printSlice(s)s = append(s, 2*i+1)}fmt.Println(s)// 第三种s1 := []int{2, 4, 6, 8} // 创建了一个数组,让 s1 映射 这个数组printSlice(s1)// 第四种s2 := make([]int, 16) // 创建一个长度 为 16 的slices3 := make([]int, 10, 32) // slice长度为10,cap为32,也就是这个slice可以映射一个长度为 32 的数组printSlice(s2)printSlice(s3)}
2.更新slice
package mainimport "fmt"func updataSlice(s []int) {s[0] = 100}func main() {arr := [...]int {0, 1, 2, 3, 4, 5, 6, 7}s1 := arr[2:]updataSlice(s1)fmt.Println("s1 =", s1) // [100 3 4 5 6 7]fmt.Println("arr =", arr) // [0 1 100 3 4 5 6 7]}
slice本身是没有数据的,是对底层数组的一个映射,所以改变slice也就改变了原数组
3.slice的扩展
package mainimport "fmt"func updataSlice(s []int) {s[0] = 100}func main() {arr := [...]int {0, 1, 2, 3, 4, 5, 6, 7}fmt.Println("Extending slice")arr[0], arr[2] = 0, 2fmt.Println("arr =", arr)s1 := arr[2:6]s2 := s1[3:5]fmt.Println(s1)fmt.Println(s2)}
s1 = [2, 3, 4, 5]
s2 只能取到第3个,也就是 5,但是实际的结果是 s2 = [5, 6],这是怎么回事呢?
看下图
这是因为slice是对底层数组的映射,引用,虽然 s1 不包含 5,6,但是它依然能映射到 5,6
4.slice的底层实现

slice底层实现包含这三个。
ptr 指向slice的开始位置,
len是slice的长度,通过下标取值的时候不能超过len,
cap 是 整个 arr ,所以扩展 slice 的时候,只要不超过cap就可以。所以上面的slice扩展例子可以取到 5,6
注意:slice只能向后扩展,不能向前扩展
5.向slice添加元素
package mainimport "fmt"func updataSlice(s []int) {s[0] = 100}func main() {arr := [...]int {0, 1, 2, 3, 4, 5, 6, 7}s1 = arr[2:6]s2 := s1[3:5]fmt.Println(s1) // [2 3 4 5]fmt.Println(s2) // [5 6]// 向slice添加元素s3 := append(s2, 10)s4 := append(s3, 11)s5 := append(s4, 12)fmt.Println("s3, s4, s5 =", s3, s4, s5)// s4 and s5 no longer view arr.fmt.Println("arr =", arr)}

s3 append 10 ,就会把 原数组的 7 改变成 10
s4, s5 append的元素超出了arr 的长度,也就是alice里面的 cap, 所以就不再在arr里面映射,系统会重新分配一个 更大的底层 arr,新的arr 的长度会是旧的长度的2倍,并把原来的arr拷贝过去,如果原数组没被引用就会垃圾回收掉,否则依旧存在。
由于值传递的关系,必须接收append的返回值。
6.slice的其他操作
package mainimport "fmt"func printSlice(s []int) {fmt.Printf("%v, len=%d, cap=%d\n",s, len(s), cap(s))}func sliceOps() {fmt.Println("Creating slice")// 创建slice的方法var s []int // Zero value for slice is nilfor i := 0; i < 100; i++ {printSlice(s)s = append(s, 2*i+1)}fmt.Println(s)s1 := []int{2, 4, 6, 8} // 创建了一个数组,让 s1 映射 这个数组printSlice(s1)s2 := make([]int, 16) // 创建一个长度 为 16 的slices3 := make([]int, 10, 32) // slice长度为10,cap为32,也就是这个slice可以映射一个长度为 32 的数组printSlice(s2)printSlice(s3)// copyfmt.Println("Copying slice")copy(s2, s1)printSlice(s2) // [2,4,6,8,0,0,0,0,0]fmt.Println("Deleting elements from slice")// 删掉 8s2 = append(s2[:3], s2[4:]...)printSlice(s2)fmt.Println("Popping from front")front := s2[0]s2 = s2[1:]fmt.Println(front)printSlice(s2)fmt.Println("Popping from back")tail := s2[len(s2)-1]s2 = s2[:len(s2)-1]fmt.Println(tail)printSlice(s2)}
八、Map
1.创建
有两种创建方式
package mainimport "fmt"func main() {m := map[string]string{"name": "ccmouse","course": "golang","site": "imooc","quality": "notbad",}// 第一种m2 := make(map[string]int) // m2 == empty map// 第二种var m3 map[string]int // m3 == nil nil类似与 null 但是可进行运算fmt.Println("m, m2, m3:")fmt.Println(m, m2, m3)}
2.获取元素 m[key]
当key不存在时,获得的 value是初始值或者是默认值,不会报错。
package mainimport "fmt"func main() {m := map[string]string{"name": "ccmouse","course": "golang","site": "imooc","quality": "notbad",}m2 := make(map[string]int) // m2 == empty mapvar m3 map[string]int // m3 == nil nil类似与 null 但是可进行运算fmt.Println("m, m2, m3:")fmt.Println(m, m2, m3)fmt.Println("Getting values")courseName := m["course"] // 获取 course 名字// courseName := m["couse"] // 不存在的key,依旧会打印出空值,go语言不初始化也能用,拿到的值就是默认值fmt.Println(`m["course"] =`, courseName)if causeName, ok := m["cause"]; ok {fmt.Println(causeName)} else {fmt.Println("key 'cause' does not exist")}}
设置 value ok, 当key不存在的时候 ok 为 false,存在时 ok为 true
3.判断某个键是否存在
Go语言中有个判断map中键是否存在的特殊写法,格式如下:
value, ok := map[key]
4.delete
package mainimport "fmt"func main() {m := map[string]string{"name": "ccmouse","course": "golang","site": "imooc","quality": "notbad",}m2 := make(map[string]int) // m2 == empty mapvar m3 map[string]int // m3 == nil nil类似与 null 但是可进行运算fmt.Println("Getting values")courseName := m["course"] // 获取 course 名字// courseName := m["couse"] // 不存在的key,依旧会打印出空值,go语言不初始化也能用,拿到的值就是默认值fmt.Println(`m["course"] =`, courseName)if causeName, ok := m["cause"]; ok {fmt.Println(causeName)} else {fmt.Println("key 'cause' does not exist")}fmt.Println("Deleting values")name, ok := m["name"]fmt.Printf("m[%q] before delete: %q, %v\n","name", name, ok)delete(m, "name")name, ok = m["name"]fmt.Printf("m[%q] after delete: %q, %v\n","name", name, ok)}
5.使用range遍历
使用range遍历key,或者遍历key,value
func main() {m := map[string]string{"name": "ccmouse","course": "golang","site": "imooc","quality": "notbad",}m2 := make(map[string]int) // m2 == empty mapvar m3 map[string]int // m3 == nil nil类似与 null 但是可进行运算fmt.Println("m, m2, m3:")fmt.Println(m, m2, m3)fmt.Println("Traversing map m")for k, v := range m {fmt.Println(k, v) // map是无序的,是一个哈希map}}
不能保证遍历顺序,如需顺序,需要手动对key排序,取出所有的key,放进一个 slice中进行排序
func main() {rand.Seed(time.Now().UnixNano()) //初始化随机数种子var scoreMap = make(map[string]int, 200)for i := 0; i < 100; i++ {key := fmt.Sprintf("stu%02d", i) //生成stu开头的字符串value := rand.Intn(100) //生成0~99的随机整数scoreMap[key] = value}//取出map中的所有key存入切片keysvar keys = make([]string, 0, 200)for key := range scoreMap {keys = append(keys, key)}//对切片进行排序sort.Strings(keys)//按照排序后的key遍历mapfor _, key := range keys {fmt.Println(key, scoreMap[key])}}
6.map的key
map使用哈希表,必须可以比较相等
除了slice,map,function 的内建类型都可以作为key
struct类型不包含上述字段,也可也作为key
7.Map的底层实现
九、面向对象
go语言只支持封装,不支持多态和继承
1.结构体
以下是新建一个结构体的方法
package main //import "fmt"type Node struct {Value intleft, right *Node}func main() {var root Noderoot = Node{Value: 3}root.left = &Node{} // 通过 & 获取指针root.right = &Node{5, nil, nil}root.right.left = new(Node)nodes := []Node{{Value: 3},{},{6, nil, &root},}fmt.Println(nodes) // [{3 <nil> <nil>} {0 <nil> <nil>} {6 <nil> 0xc00000c060}]}
2.自定义工厂函数
package main //import "fmt"type Node struct {Value intleft, right *Node}// 自定义工厂函数func creatNode(value int) *Node {return &Node{Value: value} // 返回了一个局部变量}func main() {var root Noderoot = Node{Value: 3}root.left = &Node{} // 通过 & 获取指针root.right = &Node{5, nil, nil}root.right.left = new(Node)root.left.right = creatNode(2)fmt.Println(root.left.right) // {2 <nil> <nil>}nodes := []Node{{Value: 3},{},{6, nil, &root},}fmt.Println(nodes) // [{3 <nil> <nil>} {0 <nil> <nil>} {6 <nil> 0xc00000c060}]}
工厂函数是一个普通函数,返回一个指针,这个指针是一个局部变量的地址。
通过以上代码我们创建了一棵如下的 树:
3.内存分配
结构的创建是在堆上还是栈上?
其他语言,像局部变量分配在栈上,函数退出局部变量就会立即销毁,如果要穿出去就需要在堆上,C++ 需要手动释放,其他语言会进行GC垃圾回收。
go语言中编译器会根据运行环境自动分配,如果变量没有取地址并且没有返回出去就会在栈上分配,如果返回出去就会分配在堆中,会参与垃圾回收。
3.方法
定义方法不能写在结构体里面,而是写在结构体外面。
它的特点是需要有一个接收者。
package main //import "fmt"type Node struct {Value intleft, right *Node}func (node Node) Print() {fmt.Print(node.Value, " ")}func main() {var root Noderoot = Node{Value: 3}root.Print()}
go语言的方法是需要有一个接收者,node Node 就是Print的接收者。
相当于下面的写法:
package main //import "fmt"type Node struct {Value intleft, right *Node}// 方法func (node Node) Print() {fmt.Print(node.Value, " ")}// 上面的写法等价与下面的func Print1(node Node) {fmt.Print(node.Value, " ")}func main() {var root Noderoot = Node{Value: 3}root.Print()Print(root)}
4.值传递和指针传递
package main //import "fmt"type Node struct {Value intleft, right *Node}// 自定义工厂函数func creatNode(value int) *Node {return &Node{Value: value} // 返回了一个局部变量}// 方法func (node Node) Print1() {fmt.Print(node.Value, " ")}// 上面的写法等价与下面的func Print2(node Node) {fmt.Print(node.Value, " ")}// 值传递func (node Node) setValue(value int){node.Value = value}func main() {var root Noderoot = Node{Value: 3}root.left = &Node{} // 通过 & 获取指针root.right = &Node{5, nil, nil}root.right.left = new(Node)root.left.right = creatNode(2)Print2(root)root.setValue(13)root.Print1() // 依旧是 3root.right.left.setValue(10)root.right.left.Print1() // 依旧是 0}
方法里面的参数都是值传递,改变拷贝参数并不能修改原参数。要想改变原参数必须使用指针,通过指针可以修改原参数,如下:
package main //import "fmt"type Node struct {Value intleft, right *Node}// 自定义工厂函数func creatNode(value int) *Node {return &Node{Value: value} // 返回了一个局部变量}// 方法func (node Node) Print1() {fmt.Print(node.Value, " ")}// 上面的写法等价与下面的func Print2(node Node) {fmt.Print(node.Value, " ")}// 值传递func (node *Node) setValue(value int){node.Value = value}func main() {var root Noderoot = Node{Value: 3}root.left = &Node{} // 通过 & 获取指针root.right = &Node{5, nil, nil}root.right.left = new(Node)root.left.right = creatNode(2)Print2(root)root.setValue(13)root.Print1() // 依旧是 3root.right.left.setValue(10)root.right.left.Print1() // 依旧是 0}
*Node 指针引用传递之后,调用 setValue 就是将 root.right.left 的地址传递给了参数,从而可以改变原值。
5.包和封装
封装:
名字一般使用 驼峰写法
首字母大写:public
首字母小写:private
包:
每个目录一个包,包名可以和目录名不同。
main包包含可执行入口,一个目录下只能有一个main包。
为结构定义的方法必须放在同一个包内,可以不是同一个文件。
6.扩展已有类型
定义别名或者使用组合
func (myNode *myTreeNode) postOrder() {if myNode == nil || myNode.node == nil {return}left := myTreeNode{myNode.node.Left}right := myTreeNode{myNode.node.Right}left.postOrder()right.postOrder()myNode.node.Print()}
十、面向接口
1.接口的定义
实现者和使用者
接口由使用者定义
实现一个接口 interface
在接口里面定义方法,
只要某个结构体实现了这个接口里的方法,就实现了这个接口。
如下:
package mainimport "fmt"// 接口type Sayer interface {say()}// dog 是结构体 实现了接口的 say方法func ( d dog) say {fmt.Println("哇哇哇")}// cat 是结构体 实现了接口的 say方法func (c cat) say {fmt.Println("喵喵喵")}func main() {var x Sayerd := dog{}x = dfmt.Println(x.say())}
type Cat struct{}type Dog struct{}
十一、函数式编程
十二、并发
1.goroutine
2.runtime
3.channel
4.Goroutine
5.select
十三、http
1.服务端
通过 ListenAndServe 使用指定的监听地址和处理器启动一个HTTP服务端。处理器参数通常是nil,这表示采用包变量 DefaultServeMux 作为处理器。
Handle和HandleFunc函数可以向DefaultServeMux添加处理器。
package mainimport "net/http"func f1(w http.ResponseWriter, r *http.Request) {str := `<h1>hello world</h1>`w.Write([]byte(str))}func main() {http.HandleFunc("/posts/Go/15_socket/", f1) // 处理端口下的某个路径http.ListenAndServe("127.0.0.1:9000", nil) // 监听端口并开启服务}
将上面的代码编译之后执行,打开你电脑上的浏览器在地址栏输入127.0.0.1:9000//posts/Go/15_socket/回车,此时就能够看到hello world。
2.客户端
2.1 get
客户端发送get请求
package mainimport ("fmt""io/ioutil""net/http")func main() {resp, err := http.Get("http://127.0.0.1:9000/test/?name=lyt&age=18")if err != nil {fmt.Printf("get url failed")return}defer resp.Body.Close() // 关闭连接body, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println("read from resp.Body failed")return}fmt.Println(string(body))}
resp是客户端发送过来的响应体,通过 ioutil.ReadAll(resp.Body) 就可以拿到想要的响应内容。
服务端监听请求
package mainimport ("fmt""io/ioutil""net/http")func f1(w http.ResponseWriter, r *http.Request) {//str := `<h1>hello world</h1>`res, err := ioutil.ReadFile("./test.txt") // ioutil.ReadFile 读文件if err != nil {w.Write([]byte(fmt.Sprintf("%v", err)))}w.Write(res) // 响应内容}func f2(w http.ResponseWriter, r *http.Request) {fmt.Println(r.URL) // 请求url// 对于Get请求,参数都放在Url上,queryParam := r.URL.Query()name := queryParam.Get("name")age :=queryParam.Get("age")fmt.Println(name)fmt.Println(age)fmt.Println(r.Method) // 请求方法fmt.Println(ioutil.ReadAll(r.Body)) // 请求体w.Write([]byte("ok")) // 响应内容}func main() {http.HandleFunc("/posts/Go/15_socket/", f1) // 处理端口下的某个路径http.HandleFunc("/test/", f2)http.ListenAndServe("127.0.0.1:9000", nil) // 监听端口并开启服务}
服务端要处理客户端传参可以使用 URL.Query() 拿到参数体。
w.Write([]byte(“ok”)) 发送响应内容。
2.2 post请求
客户端发送post请求
func postRequest() {url := "http://127.0.0.1:9000/posttest"contentType := "application/json"data := `{"name": "小王子", "age": 18}`resp, err := http.Post(url, contentType, strings.NewReader(data))if err != nil {fmt.Println("post failed")return}defer resp.Body.Close()b, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println("get resp failed")return}fmt.Println(string(b)) // b 返回的是字节类型,要转成string}
对应的服务端代码
package mainimport ("fmt""io/ioutil""net/http")func f3(w http.ResponseWriter, r *http.Request) {defer r.Body.Close()// 1.请求类型是application/x-www-form-urlencoded时解析form数据r.ParseForm()fmt.Println(r.PostForm) // 打印form数据fmt.Println(r.PostForm.Get("name"), r.PostForm.Get("age"))// 2. 请求类型是application/json时从r.Body读取数据b, err := ioutil.ReadAll(r.Body)if err != nil {fmt.Printf("read request.Body failed, err:%v\n", err)return}fmt.Println(string(b))answer := `{"status": "ok"}`w.Write([]byte(answer))}func main() {http.HandleFunc("/posttest/", f3)http.ListenAndServe("127.0.0.1:9000", nil) // 监听端口并开启服务}
自定义请求
package mainimport ("fmt""io/ioutil""net/http""net/url""strings")// 共用一个client连接,适用于请求比较频繁var (client = http.Client{Transport: &http.Transport{DisableKeepAlives:true,},})// 自定义请求func customRequest () {data := url.Values{}urlObj, _ := url.Parse("http://127.0.0.1:9000/test/") // Parse函数解析rawurl为一个URL结构体,rawurl可以是绝对地址,也可以是相对地址。data.Set("name", "lyt")data.Set("age", 18)queryStr := data.Encode() // URL Encode转译之后的urlfmt.Println(queryStr)urlObj.RawQuery = queryStr // RawQuery 编码之后的url, 没有 ?req, err := http.NewRequest("GET", urlObj.String(), nil)resp, err := http.DefaultClient.Do(req) // 造一个客户端if err != nil {fmt.Println("get url failed")return}defer resp.Body.Close()b, err := ioutil.ReadAll(resp.Body)if err != nil {fmt.Println("read resp.Body failed")return}fmt.Println(string(b))//自定义client头部 禁用keepAlive 短连接 适用于请求没那么频繁tr := &http.Transport{Proxy: nil,DialContext: nil,Dial: nil,DialTLS: nil,TLSClientConfig: nil,TLSHandshakeTimeout: 0,DisableKeepAlives: true,DisableCompression: false,MaxIdleConns: 0,MaxIdleConnsPerHost: 0,MaxConnsPerHost: 0,IdleConnTimeout: 0,ResponseHeaderTimeout: 0,ExpectContinueTimeout: 0,TLSNextProto: nil,ProxyConnectHeader: nil,MaxResponseHeaderBytes: 0,}client := http.Client{Transport: tr,CheckRedirect: nil,Jar: nil,Timeout: 0,}client.Do(req)}func main() {getRequest()postRequest()customRequest ()}
url.Values{} 创建一个url参数体
url.Parse 解析一个地址为url结构体
data.Encode() 将参数部分转译成一个编码之后的
urlObj.RawQuery = queryStr 将参数拼接到url后并且转译
http.NewRequest 开启一个新的请求
要管理代理、TLS配置、keep-alive、压缩和其他设置,创建一个Transport:
tr := &http.Transport{Proxy: nil,DialContext: nil,Dial: nil,DialTLS: nil,TLSClientConfig: nil,TLSHandshakeTimeout: 0,DisableKeepAlives: true,DisableCompression: false,MaxIdleConns: 0,MaxIdleConnsPerHost: 0,MaxConnsPerHost: 0,IdleConnTimeout: 0,ResponseHeaderTimeout: 0,ExpectContinueTimeout: 0,TLSNextProto: nil,ProxyConnectHeader: nil,MaxResponseHeaderBytes: 0,}client := http.Client{Transport: tr,CheckRedirect: nil,Jar: nil,Timeout: 0,}client.Do(req)
Client和Transport类型都可以安全的被多个go程同时使用。出于效率考虑,应该一次建立、尽量重用。
