这是我参与「第三届青训营 -后端场」笔记创作活动的的第2篇笔记
基本语法
先跟世界打个招呼
package main
import "fmt"
func main(){
fmt.Println("hello,world")
}
- Go语言类c++风格
- 每个Go程序必须包含一个main包以及一个main()函数。main()作为整个程序的入口,在程序执行时最先被执行。
- fmt包提供格式化文本和读入格式文本的函数,提供不同打印函数变体。fmt.Println()会整洁的打印输入的内容,fmt.Printf()使用占位符来提供良好的格式化输出控制能力。
- 程序没有分号,import语句也不用逗号分割,代码缩进仅仅为了提高代码可读性。从技术层面讲,Go的语句是以分号分隔,但这些是编译器自动添加,不需手动输入
- Go语言的函数和方法以关键字func定义,但main包里的main函数比较特别,既没有参数也没有返回值,当main.main()运行完毕,程序会自动终止并向操作系统返回0.
基本类型语法
package main
import (
"fmt"
"math"
)
func main(){
var a = "initial"
var b , c int = 1,2
var d=true
var e float64
f := float32(e)
g := a+"foo"
fmt.Println(a,b,c,d,e,f)
//initial 1 2 true 0 0
fmt.Println(g)
//initialfoo
const s string ="constant"
const h = 500000000
const i = 3e20/h
fmt.Println(s,h,i,math.Sin(h),math.Sin(i))
//constant 500000000 6e+11 -0.28470407323754404 0.7591864109375384
}
- 操作符 := 快速变量声明。可以同时声明并初始化一个变量,不必声明一个具体类型的变量,Go语言可以从其初始化值推导其类型。go是强类型语言。
- 常量使用const声明,变量使用var声明,也可以快捷变量声明。
- var/const a int/float=
- 变量e没有显示初始化,会赋零值,字符串会默认为空。
- Go语言提供了大量内置的数值类型。不能在不同的类型之间进行二进制数值运算或比较操作。无类型的数值常量可以兼容表达式中任何类型的数值。将类型转换成最大的类型防止精度丢失以进行运算。type(value)
- 3e20是科学计数法 3*10^20
- Math.Sin(x) 以弧度为单位的x的正弦值
if语句
package main
import "fmt"
func main(){
if 7%2==0 {
fmt.Println("7 is even")
}else {
fmt.Println("7 is odd")
}
//if optionalStatement1;booleanExpression1 {}
if num:=8;num<0 {
fmt.Println(num,"is negetive")
}else if num<10{
fmt.Println(num,"has 1 digit")
}
}
- if语句的大括号是强制性的,条件判断中的分号只有在可选语句optionalStatement出现的情况下需要。可选语句只能是表达式、发送到通道(使用<-操作符)、增减值语句、赋值语句或短变量声明语句。如果变量是在一个可选的声明语句中创建的(使用:=操作符创建),他们的作用于会从声明处扩展到if语句的完成处,他们在声明他们的if或elseif语句以及相应分支中一直存在,直到if语句的末尾。
- 第一个if语句的num变量扩展到了if...elseif...语句中,因此他们在每一个分支中都是可见的。
for循环
package main
import "fmt"
func main(){
i:=1
//for booleanExpression{block}
for{
fmt.Println("loop")
break
}
// for optionalPreStatement;booleanExpress;optionalPostStatement
for j:=7;j<9;j++{
fmt.Println(j)
}
for n:=9;n<5;n++ {
if n%2==0 {
continue
}
fmt.Println(n)
}
for i<=3 {
fmt.Println(i)
i=i+1
}
}
switch循环
package main
import (
"fmt"
"time"
)
func main(){
a := 2
//switch optionalStatement{
switch a {
//case expressionList1:
//block
case 1:
fmt.Println("one")
case 2:
fmt.Println("two")
case 3:
fmt.Println("three")
//default:bloakD
default:
fmt.Println("other")
}
t:=time.Now()
switch {
case t.Hour()<12:
fmt.Println("1")
default:
fmt.Println("2")
}
}
- 不同于c和java,go的switch语句不会自动向下贯穿,因此不必在每个case字句末尾添加break语句,需要case 语句向下贯穿时可以显式调用fallthrough
- 没有可选的表达式语句,编译器会把表达式语句设为true。意味着case语句的每一个表达式都必须计算为布尔类型。
字符串切片
package main
import "fmt"
func main(){
s:=make([]string,3)
s[0]="a"
s[1]="b"
s[2]="c"
fmt.Println("get:",s[2])
fmt.Println("len:",len(s))
s=append(s,"d")
s=append(s,"e","f")
//[a b c d e f]
fmt.Println(s)
c:=make([]string,len(s))
copy(c,s)
//[a b c d e f]
fmt.Println(c)
//[c d e]
fmt.Println(s[2:5])
// [a b c d e]
fmt.Println(s[:5])
//[c d e f]
fmt.Println(s[2:])
good := []string{"g","o","o","d"}
fmt.Println(good)
}
- [2:5] 从2-4
- [:5] 从0-4
- [2:] 从2到结尾 len(s)-1
- 切片 从开始到结束为止 不包含结束位置
make([]T,len)//长度为切片长度
make([]T,len,cap)//same as make([]T,cap)[:len] 长度为切片容量
- 内置函数make创建指定元素类型、长度和容量的slice
- 创建一个隐藏的初始化为零值的数组,然后返回一个引用该隐藏数组的切片。
- 在底层,make创建了一个匿名的数组变量,返回一个slice。只有通过返回的slice才能引用底层匿名的数组遍历。
- 一个切片是一个隐藏数组的引用
- 对于append函数,每次调用会检测slice底层数组是否有足够容量来保存新添加的元素。如果空间足够,直接扩展slice(在原有的底层数组上),将新添加的元素复制到新扩展的空间,并返回slice。输入和输出共享相同的底层数组。
- 如果没有足够的增长空间,会先分配一个足够大的slice用于保存新的结果,将输入复制到新的空间,然后添加一个新元素。输入和输出引用的是不同的底层数组。
- 内置的append函数可能使用比appendInt更复杂的内存扩展策略。因此通常不知道append是否导致了内存的重新分配,也不能确定原始slice和新的slice引用的是否是相同的底层数组空间。通常是将append返回的结果直接赋值给输入的slice变量。
- 内置的copy元素可以方便的将一个slice复制到另一个相同类型的slice。copy的第一个参数是要复制的目前slice,第二个参数是源slice。两个slice可以共享一个底层数组,有重叠也没问题。copy函数将返回成功赋值的元素的个数(这里没有用到),等于两个slice中较小的长度,所以无须担心复制会超出目标slice的范围。
map
package main
import "fmt"
func main(){
m:=make(map[string]int)
m["one"]=1
m["two"]=2
//map[one:1 two:2]
fmt.Println(m)
//2
fmt.Println(len(m))
//1
fmt.Println(m["one"])
//0
fmt.Println(m["unknow"])
r,ok := m["unknow"]
//0 false
fmt.Println(r,ok)
delete(m,"one")
m2:=map[string]int{"one":1,"two":2}
var m3 = map[string]int{"one":1,"two":2}
fmt.Println(m2,m3)
}
- map保存键值对的无序结合。键唯一
- m[k]=v 用键k来将值v赋值给映射m。如果映射m中的k已存在,将之前的值丢弃。
- v,found := m[k] 从映射m中取得键相对应的值并将其赋值给v,并将found的值赋值为true。如果k在映射中不存在,则将映射类型为0值赋值给v,并将found的值赋值为false。
- delete(m,k) 将键k及相关的值从映射m中删除,如果k不存在则安全地不执行任何操作
- v:=m[k] 从映射m中取得键k相对应的值并将其赋值给v。如果k在映射中不存在,将映射类型的0值赋值给v。类似于Java的getOrDefault(k,0)
- len(m) 返回映射m中项("键/值"对)的数目
- map[KeyType]ValueType{key1:value1,key2:value2...}
- 用make()创建的映射得到的是空的映射,指定容量就会预先申请到足够的内存,随着加入的项越来越多映射会自动扩容。
- make(map[KeyType]ValueType)
函数
- golang中变量后置
package main
import "fmt"
func add(a int,b int)int {
return a+b
}
func add2(a,b int) int{
return a+b
}
func add3(n int){
n+=2
}
func add3ptr(n *int){
*n+=2
}
func exists(m map[string]string,k string)(v string,ok bool){
v,ok=m[k]
return v,ok
}
func main(){
res:=add(1,2)
fmt.Println(res)
v,ok:=exists(map[string]string{"a":"A"},"a")
fmt.Println(v,ok)
n:=5
add3(n)
fmt.Println(n)//5
add3ptr(&n)
fmt.Println(n)//7
}
- 函数声明包括函数名、形式参数列表、返回值列表(可省略)以及函数体
func name(parameter-list)(result-list){
body
}
- 实参通过值传递,函数的形参是实参的拷贝。对形参的修改不会影响实参。但是如果实参包括引用类型,如指针、slice、map、function、channel等类型,实参可能由于函数的间接引用被修改。
- 使用指针,让参数的传递成本最低且内容可修改,让参数的生命周期独立于作用域。指针是保存了另一个变量内存地址的变量。创建的类型是用来指向某种类型的变量,可以让Go知道该指针指向的值占用多大的空间。保存了另一个变量的内存地址的变量可以被认为指向该变量。
- 一元操作符&有时也被称为取地址操作
- pi:=&x 之后 *pi和x可以相互交换着使用,他们与同一块内存地址相关联,一个变量的改变会影响另一个。
- 函数add3prt接收指针而非值类型的参数,必须传入指向int类型值的指针,而非int类型。当&被用于函数调用,我们就要假设对应的变量值可能在函数内被修改。
- 在函数内部,我们更关心指针所指向的值,因此使用*。
结构体
package main
import (
"fmt"
)
type user struct{
name string
password string
}
func main(){
a := user{name:"li",password:"1024"}
b := user{"li","1024"}
c :=user{name:"li"}
c.password="1024"
var d user
d.name="wang"
d.password="1024"
fmt.Println(a,b,c,d)
//false
fmt.Println(checkPassword(a,"haha"))
//false
fmt.Println(checkPassword2(&a,"haha"))
a.resetPassword("2048")
//true
fmt.Println(a.checkPassword("2048"))
}
func checkPassword(u user,password string) bool{
return u.password==password
}
func checkPassword2(u *user,password string) bool {
return u.password==password
}
func (u *user)resetPassword(password string){
u.password=password
}
func (u user)checkPassword(password string)bool{
return u.password==password
}
- 结构体是一种聚合的数据类型
- type T struct{}
错误处理
package main
import (
"errors"
"fmt"
)
type user struct {
name string
password string
}
func findUser(users []user,name string)(v *user,err error){
for _,u := range users{
if u.name==name {
return &u,nil
}
}
return nil,errors.New("not found")
}
func main(){
u,err := findUser([]user{{"wang","1024"}},"wang")
if err!=nil{
//wang
fmt.Println(err)
return
}
fmt.Println(u.name)
if u,err:=findUser([]user{{"wang","1024"}},"li");err!=nil{
//not found
fmt.Println(err)
return
}else{
fmt.Println(u.name)
}
}
- 内置的err是接口类型。可能是nil或non-nil.nil意味着函数运行成功,non-nil表示失败。对于non-nil的error类型,我们可以通过调用error的Error函数或输出函数获得字符串类型的错误信息。
- for...range语句也是一种循环。每次循环迭代,range产生一对值:索引以及在该索引处的元素值。range的语法要求,处理元素必须处理索引。一种思路是把索引赋值给临时变量后忽略,但go不允许使用无用的局部变量,这会导致编译错误。因此Go使用空标识符来解决这个问题,即_(下划线).空标识符可用于任何语法需要变量名而程序逻辑不需要的时候,在循环中丢弃不需要的循环索引保留元素值。
- 创建一个error最简单的方法是调用errors.New,根据传入的错误信息返回一个新的error。
字符串操作
package main
import (
"fmt"
"strings"
)
type point struct{
x,y int
}
func main(){
a:="hello"
//true
fmt.Println(strings.Contains(a,"ll"))
//2
fmt.Println(strings.Count(a,"l"))
//true
fmt.Println(strings.HasPrefix(a,"he"))
//true
fmt.Println(strings.HasSuffix(a,"lo"))
//2
fmt.Println(strings.Index(a,"ll"))
//he-llo
fmt.Println(strings.Join([]string{"he","llo"},"-"))
//hEllo
fmt.Println(strings.Replace(a,"e","E",-1))
//hellohello
fmt.Println(strings.Repeat(a,2));
//[a b c]
fmt.Println(strings.Split("a-b-c","-"))
//hello
//HELLO
fmt.Println(strings.ToLower(a))
fmt.Println(strings.ToUpper(a))
//5
fmt.Println(len(a))
b:="你好"
//6
fmt.Println(len(b))
s:="hello"
n:=123
p:=point{1,2}
fmt.Println(s,n)
fmt.Println(p)
//s=hello
fmt.Printf("s=%v\n",s)
//n=123
fmt.Printf("n=%v\n",n)
//p={1,2}
fmt.Printf("p=%v\n",p)
//p={x:1,y:2}
fmt.Printf("p=%+v\n",p)
//p=main.point{x:1,y:2}
fmt.Printf("p=%#v\n",p)
f:=3.1415926535
//3.14
fmt.Printf("%.2f\n",f)
}
- strings.Contains(s,t) 如果t在s中,返回true
- strings.Count(s,t) t在s中出现了多少次
- strings.HasPrefix(s,t) 如果字符串s是以t开头的返回true
- strings.HasSuffix(s,t) 如果字符串是以t结尾的返回true
- strings.Index(s,t) t在s中第一次出现的索引位置
- strings.Join(xs,t) 将xs中所有字符串按照t分隔符进行合并
- strings.Replace(s,old,new,i) 返回一个新字符串,对s中旧的非重叠字符串用新的字符串替换,进行i次替换操作,i=-1则全部替换
- strings.Repeat(s,i) 重复i次字符串s
- strings.ToUpper(s) 返回一个新的字符串,对原s中所有字母大写转换处理
- strings.ToLower(s) 返回一个新的字符串,对原s进行字母小写转换处理
- strings.split(s,t) 返回一个新的字符串切片,在原s上所有出现t的位置进行切分
- %v 使用默认格式输出的内置的或自定义类型的值
- # 让格式以另一种格式输出数据
- %#v 使用go语法将值自身输出
- + 格式指令在数值前输出+号或-号,为字符串输出asc字符,为结构体输出字段名称
json操作
package main
import (
"encoding/json"
"fmt"
)
type userinfo struct{
Name string
Age int `json:"age"`
Hobby []string
}
func main(){
a := userinfo{Name:"wang",Age:18,Hobby: []string{"Golang","TypeScript"}}
buf,err := json.Marshal(a)
if err!=nil {
panic(err)
}
//[123 34 78 97 109 101 34 58 34 119 97 110 103 34 44 34 97 103 101 34 58 49 56 44 34 72 111 98 98 121 34 58 91 34 71 111 108 97 110 103 34 44 34 84 121 112 101 83 99 114 105 112 116 34 93 125]
fmt.Println(buf)
//{"Name":"wang","age":18,"Hobby":["Golang","TypeScript"]}
fmt.Println(string(buf))
buf,err=json.MarshalIndent(a,"","\t")
if err!=nil {
panic(err)
}
/*{
"Name": "wang",
"age": 18,
"Hobby": [
"Golang",
"TypeScript"
]
}
*/
fmt.Println(string(buf))
var b userinfo
err = json.Unmarshal(buf,&b)
if err!=nil {
panic(err)
}
fmt.Printf("%#v\n",b)
}
- 将slice转换为json的过程叫编组(marshaling),编组通过调用json.Marshal完成。返回编码后的slice,包含很长的字符串,并且没有空白缩进。
- Marshal returns the JSON encoding of v.
- json.MarshalIndent()函数将产生整齐的缩进,有两个格外的字符串参数表示每一行输出的前缀和每个层级的递进
- 编码时,默认使用go语言结构体的成员名字作为json对象(通过reflect反射技术。只有导出的结构体会被编码,因此使用大写字母开头的结构体成员。
- 编码的逆操作是解码,对于将json数据编码为go语言的数据结构,一般叫unmarshaling.通过json.Unmarshal().
- panic异常是来自背调函数的信号,表明发生某个已知bug。
- 某些错误只能在运行时检查,如数组访问越界、空指针引用等,这些错误会引起panic异常
- panic异常发送,程序会中断执行,并立即执行在该goroutinue中被延迟的函数。随后程序崩溃输出日志信息,日志信息包括Panic value和函数调用的堆栈跟踪信息。
- 不是所有的panic异常都来自运行时,直接调用内置panic函数也会引起panic异常。panic函数接收任何值作为参数。当某些不应该发生的场景发生,我们就调用panic。
- panic会引起程序崩溃,因此一般用于严重错误。
时间处理
package main
import (
"fmt"
"time"
)
func main(){
now:=time.Now()
// 2022-05-08 19:40:52.334781 +0800 CST m=+0.000200612
fmt.Println(now)
t:=time.Date(2022,3,27,1,25,36,0,time.UTC)
t2:=time.Date(2022,3,27,2,30,36,0,time.UTC)
//2022-03-27 01:25:36 +0000 UTC
fmt.Println(t)
//2022 1 27 March 25
fmt.Println(t.Year(),t.Hour(),t.Day(),t.Month(),t.Minute())
//2022-03-27 01:25:22
fmt.Println(t.Format("2006-01-02 15:04:06"))
diff :=t2.Sub(t)
//1h5m0s
fmt.Println(diff)
//65 3900
fmt.Println(diff.Minutes(),diff.Seconds())
t3,err:=time.Parse("2006-01-02 15:04:06","2022-03-27 01:25:36")
if err!=nil{
panic(err)
}
//false
fmt.Println(t3==t)
//1652010052
fmt.Println(now.Unix())
}
- time.Date()把年月日时分秒输入,转化成相应的时间 yyyy-mm-dd hh:mm:ss + nsec nanoseconds
- now.Unix() Unix 返回与给定 Unix 时间相对应的本地时间,自 1970 年 1 月 1 日 UTC 以来的秒秒和纳秒纳秒。在 [0, 999999999] 范围之外传递 nsec 是有效的。并非所有秒值都有对应的时间值。一个这样的值是 1<<63-1(最大的 int64 值)
- parse(layout,value) Parse 解析格式化的字符串并返回它所代表的时间值。第二个参数必须可以使用作为第一个参数提供的格式字符串(布局)进行解析。
- sub() Sub 返回持续时间 t-u。如果结果超过 Duration 中可以存储的最大(或最小值)值,则将返回最大(或最小)持续时间。要计算持续时间 d 的 t-d,请使用 t.Add(-d)。
数字解析
package main
import (
"fmt"
"strconv"
)
func main(){
f,_ := strconv.ParseFloat("1.234",64)
//1.234 float64
fmt.Println(f)
//int64 111
n,_ := strconv.ParseInt("111",10,64)
fmt.Println(n)
//int64 十进制0
n1,_:=strconv.ParseInt("0x000",0,64)
fmt.Println(n1)
//123 int
n2,_:=strconv.Atoi("123")
fmt.Println(n2)
//0 strconv.Atoi: parsing "AAA": invalid syntax
n2,err:=strconv.Atoi("AAA")
fmt.Println(n2,err)
}
- strconv.Atoi(s) 返回转换后的int类型和一个error
- strconv.ParseInt(s,base,bits) 如果s能转换成为一个整数,返回int值和nil,否则返回0和error。如果base为0,表示要从s判断进制,0X或0x开头是十六进制,0开头是八进制,其他是十进制。或者base指定大小(2-36)。如果需要转换成int那么bits为0,否则会转换成有长度的整型。如bits=16转换为int16.
- strconv.ParseFloat(s,bits) s能转换成浮点数,返回一个float的值和nil。否则返回0和error。
进程信息
package main
import (
"fmt"
"os"
"os/exec"
)
func main(){
//[/private/var/folders/mz/9bcyzh5x0x31c1w01zh_81k80000gn/T/___go_build_processInfo_go]
fmt.Println(os.Args)
/*
/Users/luna/opt/anaconda3/bin:/Users/luna/opt/anaconda3/condabin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Applications/VMware Fusion.app/Contents/Public:/usr/local/go/bin:/Library/Apple/usr/bin:/Users/luna/sdk/go1.17/bin
*/
fmt.Println(os.Getenv("PATH"))
// <nil>
fmt.Println(os.Setenv("AA","BB"))
buf,err := exec.Command("grep","127.0.0.1","/etc/hosts").CombinedOutput()
if err!=nil{
panic(err)
}
/**
127.0.0.1 localhost
127.0.0.1 eureka7001.com
127.0.0.1 eureka7002.com
127.0.0.1 config-3344.com
*/
fmt.Println(string(buf))
}
- os包以跨平台的方式,提供了一些与操作系统交互的函数和变量。程序的命令行参数可从os包的Args变量获取;os包外部使用os.Args访问该变量。
- os.Args[0]是命令本身的名字,其他的元素是程序启动时传给它的参数
- Getenv 检索由键命名的环境变量的值。它返回值,如果变量不存在,该值将为空。要区分空值和未设置值,请使用 LookupEnv。
- Setenv 设置由键命名的环境变量的值。如果有的话,它会返回一个错误
- exec.Command命令返回 Cmd 结构以执行具有给定参数的命名程序。
实战
math/rand
- rand.Intn() Intn 以 int 形式返回来自默认 Source 的半开区间[0,n) 中的非负伪随机数。如果 n <= 0,它会panic
- rand.Seed() Seed 使用提供的种子值将默认 Source 初始化为确定性状态。如果未调用 Seed,则生成器的行为就像由 Seed(1) 播种一样。除以 2³¹-1 时具有相同余数的种子值生成相同的伪随机序列。与 Rand.Seed 方法不同,Seed 可以安全地同时使用。
time
- time.UnixNano() UnixNano 返回 t 作为 Unix 时间,即自 1970 年 1 月 1 日 UTC 以来经过的纳秒数。如果 Unix 时间(以纳秒为单位)不能用 int64 表示(1678 年之前或 2262 年之后的日期),则结果未定义。请注意,这意味着在零时间调用 UnixNano 的结果是未定义的。结果不依赖于与 t 关联的位置。
bufio
- bufio.newReader(os.Stdin)NewReader 返回一个新的 Reader,其缓冲区具有默认大小。
- os.Stdin指向标准输入文件/dev/stdin,即os.Stdin是标准输入文件/dev/stdin的指针。
- ReadString 读取直到输入中第一次出现 delim,返回一个字符串,其中包含直到并包括分隔符的数据。如果 ReadString 在找到分隔符之前遇到错误,它会返回错误之前读取的数据和错误本身(通常是 io.EOF)。当且仅当返回的数据不以 delim 结尾时,ReadString 才会返回 err != nil。对于简单的使用,扫描仪可能更方便。
strings
- strings.TrimSuffix(s,suffix) trimSuffix 返回没有提供的尾随后缀字符串的 s。如果 s 不以 suffix 结尾,则 s 原样返回。
- strings.newReader(s)创建一个字符串s的对象
json
type DictRequest struct {
TransType string `json:"trans-type"`
Source string `json:"source"`
UserId string `json:"user_id"`
}
构体成员Tag,是和在编译阶段关联到该成员的元信息字符串。Tag可以是任意的字符串面值,编码后成员变成对应Tag指定的Json名字。
defer
defer语句用于延迟一个函数或方法的执行,会在外围函数或者方法返回之前但是返回值计算之后执行,可以在一个延迟执行的函数内部修改函数的命名返回值
如果一个函数有多个defer语句,以LIFO的顺序执行
该模式创建了一个值,在垃圾收集之前延迟执行一些关闭函数来清理该值
defer conn.Close()
fmt
- %w Golang并没有提供什么 Wrap 函数,而是扩展了 fmt.Errorf 函数,加了一个 %w 来生成一个可以Wrapping Error.
context
context包用来简化对于处理单个请求的多个goroutine之间与请求域的数据、超时、退出等操作。
在线词典
- 在彩云小译发起翻译请求,对good进行翻译,同时检查->network->dict请求->copy as cURL
- 在convert curl commands to code进行转换cURL,得到go程序,运行后输出good翻译结果的json格式
- 更改部分代码
- 增加结构体封装翻译后的结果
- 将结果使用json序列化
type DictRequest struct{
TransType string `json:"trans_type"`
Source string `json:"source"`
UserId string `json:"user_id"`
}
func main() {
client := &http.Client{}
//转换为流
//var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
request := DictRequest{TransType: "en2zh",Source: "good"}
//序列化 变成byte数组
buf,err :=json.Marshal(request)
if err!=nil{
log.Fatal(err)
}
var data = bytes.NewReader(buf)
//创建请求
req, err := http.NewRequest("POST", "https://api.interpreter.caiyunai.com/v1/dict", data)
if err != nil {
log.Fatal(err)
}
- 在json转golang struct粘贴response中的json串,进行转换-展开,得到对应的golang代码
- 将得到的golang结构体加入源代码,并使用结构体初始化反序列化的结果。运行代码,得到所有结果。
//发起请求
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
//fmt.Printf("%s\n", bodyText)
var dictResponse DictResponse
//反序列化
err = json.Unmarshal(bodyText,&dictResponse)
if err!=nil{
log.Fatal(err)
}
fmt.Printf("%#v\n",dictResponse)
}
打印结果:
main.DictResponse{Rc:0, Wiki:main.Wiki{KnownInLaguages:63, Description:main.Description{Source:"tangible and intangible thing, except labor tied services, that satisfies human wants and provides utility", Target:interface {}(nil)}, ID:"Q28877", Item:main.Item{Source:"good", Target:"商品"}, ImageURL:"www.caiyunapp.com/imgs/link_d…", IsSubject:"true", Sitelink:"www.caunapp.com/read_mode/?…, Dictionary:main.Dictionary{Prons:main.Prons{EnUs:"[gʊd]", En:"[gud]"}, Explanations:[]string{"a.好的;善良的;快乐的;真正的;宽大的;有益的;老练的;幸福的;忠实的;优秀的;完整的;彻底的;丰富的", "n.利益;好处;善良;好人", ne", "nice", "splendid", "proper"}, Antonym:[]string{"bad", "wrong", "evil", "harmful", "poor"}, WqxExample:[][]string{[]string{"to the good", "有利,有好处"}, []string{"good, bad and indifferent", "好的,坏的和一般的"}, []string{"good innings", "长寿"}, []string{"g很,颇;完全,彻底"}, []string{"do somebody's heart good", "对某人的心脏有益,使某人感到愉快"}, []string{"do somebody good", "对某人有益, "对…有效,适合,胜任"}, []string{"be good at", "在…方面(学得,做得)好;善于"}, []string{"as good as one's word", "信守诺言,值得信赖"},实际上,几乎等于"}, []string{"all well and good", "也好,还好,很不错"}, []string{"a good", "相当,足足"}, []string{"He is good at figur, Entry:"good", Type:"word", Related:[]interface {}{}, Source:"wenquxing"}}
- 此时打印的是关于字段的所有信息,更改部分代码进行筛选和格式化
//发起请求
resp, err := client.Do(req)
if err != nil {
log.Fatal(err)
}
defer resp.Body.Close()
bodyText, err := ioutil.ReadAll(resp.Body)
if err != nil {
log.Fatal(err)
}
if resp.StatusCode!=200{
log.Fatal("bad statusCode:",resp.StatusCode,"body",string(bodyText))
}
//fmt.Printf("%s\n", bodyText)
var dictResponse DictResponse
//反序列化
err = json.Unmarshal(bodyText,&dictResponse)
if err!=nil{
log.Fatal(err)
}
//fmt.Printf("%#v\n",dictResponse)
fmt.Println(word,"UK:",dictResponse.Dictionary.Prons.En,"US:",dictResponse.Dictionary.Prons.EnUs)
for _,item :=range dictResponse.Dictionary.Explanations{
fmt.Println(item)
}
打印结果:
good UK: [gud] US: [gʊd] a.好的;善良的;快乐的;真正的;宽大的;有益的;老练的;幸福的;忠实的;优秀的;完整的;彻底的;丰富的 n.利益;好处;善良;好人 ad.=well
- 将功能封装为query(word string)函数,更具有通用性
func main() {
if len(os.Args) != 2 {
fmt.Fprintf(os.Stderr, `usage:simpleDict WORD example:simpleDict hello`)
os.Exit(1)
}
//转换为流
//var data = strings.NewReader(`{"trans_type":"en2zh","source":"good"}`)
//var word = "good"
word := os.Args[1]
query(word)
}
使用 go run onlineDic.go word 进行单词的查询
SOCKS5代理
通过单个端口访问内部所有资源baike.baidu.com/item/socks5…
过程
- 握手阶段。浏览器向socks5代理发送请求,包的内容包括协议的版本号,支持的认证种类。socks5选取一个认证方式,返回给浏览器。返回00代表不需要认证。
- 认证阶段
- 请求阶段。认证后浏览器向socks5服务器发送请求,主要消息包括版本号、请求方式,一般是connection请求。代表代理服务器要和某个ip地址某个端口建立tcp连接。代理服务器接收响应,会真正和后端服务器建立连接。
- relay阶段。浏览器正常发送请求,代理服务器收到请求后,直接把请求转换到正常的服务器。如果真正的服务器返回响应,也会转给浏览器。代理服务器不关心流量细节,可以是http流量,或者tcp流量。
package main
import (
"bufio"
"log"
"net"
)
//tcp echo server
func main(){
//Listen监听一个网络端口上的连接
server,err:=net.Listen("tcp","127.0.0.1:1080")
if err!=nil{
panic(err)
}
for{
//Accept()会阻塞 直到新的连接被创建 并返回一个net.Conn来表示该连接
client,err:=server.Accept()
if err!=nil{
log.Printf("Accept Failed %v",err)
continue
}
//加入go关键字 每一次handleConn的调用都进入一个独立的goroutine 使其支持并发
go process(client)
}
}
func process(conn net.Conn){
//defer语句用于延迟一个函数或方法的执行,会在外围函数或者方法返回之前但是返回值计算之后执行,可以在一个延迟执行的函数内部修改函数的命名返回值
//如果一个函数有多个defer语句,以LIFO的顺序执行
//该模式创建了一个值,在垃圾收集之前延迟执行一些关闭函数来清理该值
defer conn.Close()
//创建缓冲流
reader:=bufio.NewReader(conn)
for{
//每次读一个字节
b,err:=reader.ReadByte()
if err!=nil{
break
}
//将字节写入
_,err=conn.Write([]byte{b})
if err!=nil{
break
}
}
}
该段代码实现了最基本的将字符流读入后输出的功能。运行后在终端输入nc 127.0.0.1 1080 可能出现1080端口已被占用的情况,解决方法
/**
实现认证功能
*/
func auth(reader *bufio.Reader,conn net.Conn)(err error){
// VER NMETHODS METHODS
// 1 1 1~255
//VER:协议版本,socks5为0x05
//NMTHODS 支持认证的方法数量
//METHODS 对于NMTHODS,NMTHODS的值为多少,METHODS就有多少个字节
// X'00'NO AUTHENTICATION REQUIRED
// X'02' USERNAME/PASSWORD
//鉴传
ver,err:= reader.ReadByte()
if err!=nil{
//扩展了 fmt.Errorf 函数,加了一个 %w 来生成一个可以Wrapping Error,
return fmt.Errorf("read ver failed:%w",err)
}
if ver!=socks5Ver{
return fmt.Errorf("not supported ver:%v",ver)
}
methodSize ,err:= reader.ReadByte()
if err!=nil{
return fmt.Errorf("read methodSize failed:%v",err)
}
//使用methodsSize创建一个缓冲区slice
method:=make([]byte,methodSize)
_,err=io.ReadFull(reader,method)
if err!=nil{
return fmt.Errorf("read method failed:%v",err)
}
log.Println("ver",ver,"method",method)
_,err=conn.Write([]byte{socks5Ver,0x00})
if err!=nil{
return fmt.Errorf("write failed:%v",err)
}
return nil
}
加入认证功能后进行验证,目前无法连接成功,但是日志正常打印。
认证逻辑:浏览器向代理服务器发送一个验证包,包括版本号(socks5Ver,认证的方法数目,认证编码(0:不需要认证2:用户名密码认证)。而后代理服务器返回一个reponse,包括Version和method(选中的鉴传方式)
完善功能 加入请求阶段
/**
请求阶段
*/
func connect(reader *bufio.Reader,conn net.Conn)(err error){
// VER CMD RSV ATYP DST.ADDR DST.PORT
// 1 1 X'00 1 Variable 2
//VER 版本号 socks5 0x05
//CMD 请求的类型 0x01表示connect请求
//RSV 保留字段 0x00
//ATYP 目标地址类型 DST.ADDR的数据对应这个字段的类型
// 0x01表示IPV4 DST.ADDR是4个字节
// 0x03表示域名 DST.ADDR是一个可变长的域名
//DST.ADDR
//DST.PORT 目标端口 默认两个字节
//向buf中读四个字节
buf:=make([]byte,4)
//浏览器向代理服务器发送报文
_,err=io.ReadFull(reader,buf)
if err!=nil{
return fmt.Errorf("read header failed:%w",err)
}
ver,cmd,atyp:=buf[0],buf[1],buf[3]
if ver!=socks5Ver{
return fmt.Errorf("not supported ver:%v",ver)
}
if cmd!=cmdBind{
return fmt.Errorf("not supported cmd:%v",cmd)
}
addr:=""
switch atyp {
case atypeIPV4:
//四个字节
_, err := io.ReadFull(reader, buf)
if err != nil {
return fmt.Errorf("read atyp failed:%w", err)
}
addr = fmt.Sprintf("%d.%d.%d.%d", buf[0], buf[1], buf[2], buf[3])
case atypeHOST:
hostSize,err:=reader.ReadByte()
if err!=nil{
return fmt.Errorf("read hostsize failed:%w",err)
}
host:=make([]byte,hostSize)
_,err=io.ReadFull(reader,host)
if err!=nil{
return fmt.Errorf("read host failed:%w",err)
}
addr=string(host)
case atypeIPV6:
return errors.New("IPV6:NOT SUPPORTED NOW")
default:
return errors.New("invalid atype")
}
_,err=io.ReadFull(reader,buf[:2])
if err!=nil{
return fmt.Errorf("read port failed:%w",err)
}
port:=binary.BigEndian.Uint16(buf[:2])
//与真正的服务器建立tcp连接
dest,err:= net.Dial("tcp",fmt.Sprintf("%v:%v",addr,port))
if err!=nil{
return fmt.Errorf("dial dst failed:%w",err)
}
defer dest.Close()
log.Println("dial",addr,port)
//response报文
_,err=conn.Write([]byte{0x05,0x00,0x00,0x01,0,0,0,0,0,0})
if err!=nil{
return fmt.Errorf("write failed:%w",err)
}
//建立浏览器和下游服务器的双向数据转发
//WithCancel 返回具有新 Done 通道的 parent 副本。返回的上下文的完成通道在调用返回的取消函数或父上下文的完成通道关闭时关闭,以先发生者为准。
//取消此上下文会释放与其关联的资源,因此代码应在此上下文中运行的操作完成后立即调用取消
ctx,cancel:=context.WithCancel(context.Background())
defer cancel()
go func(){
//从浏览器拷贝到服务器
_,_=io.Copy(dest,reader)
//调用cancel()通知后台Goroutine退出,避免泄露
cancel()
}()
go func(){
//从服务器拷贝到浏览器
_,_=io.Copy(conn,dest)
cancel()
}()
//代表退出协程
<-ctx.Done()
return nil
}
继续使用curl测试 **curl --socks5 127.0.0.1:1080 -v www.qq.com **
也可以在谷歌商店使用SwitchyOmega使用方法