go语言实战案例 | 青训营笔记

62 阅读2分钟

这是我参与「第五届青训营 」伴学笔记创作活动的第 1 天

由于作者原来有一些go语言基础,故跳过第一节基础语法讲解,直接进行第二节实战案例的演示

go猜谜游戏

这是作者在课前阅读题目后写出的go代码

package main
​
import (
"fmt"
"math/rand"
"time"
)
​
func main() {
guessNumber:=rand.Intn(100)
var guess int
for{
fmt.Scan(&guess)
if guess==guessNumber{
fmt.Println("you are right")
break
}else if guess>guessNumber{
fmt.Println("to big")
continue
}else if guess<guessNumber{
fmt.Println("to small")
continue
}
}
return
}

但是代码随机生成的随机数每一次都是81,这完全违背了整个猜谜游戏的初衷。我们可以通过添加一个随机数种子来解决这个问题

rand.Seed(time.Now().UnixNano())

现在看一下运行效果

35
to big
33
to small
34
you are right

可以看到运行结果,完全符合我们的预期。

同时通过和课程代码相比

reader := bufio. NewReader(os.Stdin)
​
input, err := reader .ReadString("\n')
​
input = strings. TrimSuffix(input, "In")

课程中使用了一个bufio的包来接收用户输入,是由于后面的课会使用bufio这个包,所以没有使用scanf来进行接收用户输入。

在线词典

这个项目通过使用http包来对彩云小译的词典接口进行post获取response,通过使用json包对req的序列化以及对resp的反序列化获取正常的json数据通过中断显示出来

这个项目使用了两个根据curd数据自动生成代码以及根据json自动生成struct的网站

curlconverter.com/go/

oktools.net/json2go

通过json包对数据进行序列化后发送

request:=DictRequest{TransType: "en2zh", Source: word}
buf,err:=json.Marshal(request)

通过json包对数据进行反序列化后输出

var dictResponse DictResponse
err=json.Unmarshal(bodyText, &dictResponse)

运行示例

liulongxin@liulongxindeAir go_guess_number % go run main.go good 
good UK: [gud] US: [gʊd]
a.好的;善良的;快乐的;真正的;宽大的;有益的;老练的;幸福的;忠实的;优秀的;完整的;彻底的;丰富的n.利益;好处;善良;好人ad.=well%      

SOCKS5

代理auth

func auth(reader *bufio.Reader,conn net.Conn) (err error) {
  ver, err := reader.ReadByte()
  if err != nil {
     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:›w", err)
  }
  method := make([]byte, methodSize)
  _,err=io.ReadFull(reader,method)
  if err != nil {
     return fmt. Errorf("read method failed:%w", err)
  }
  log.Println("ver", ver, "method", method)
​
  _,err = conn.Write([]byte{socks5Ver,0x00})
  if err != nil {
     return fmt.Errorf("write failed:%w", err)
  }
  return nil
}

通过reader.ReadByte()这个函数把传递进来的*bufio.Reader接收后将ver,methodSize和method解析出来

请求阶段

func connect(reader *bufio.Reader,conn net.Conn) (err error) {
  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 supperted ver:%v",ver)
  }
  if cmd!=cmdBuild{
    return fmt.Errorf("not supperted cmd:%v",ver)
  }
  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: no supported yet")
  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.LittleEndian.Uint16(buf[:2])
  log.Println("dial",addr,port)
  _,err=conn.Write([]byte{0x05,0x00,0x00,0x01,0,0,0,0,0,0})
  if err != nil {
    return fmt.Errorf("write failed:%w",err)
  }
  return nil
}

relay阶段

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()
ctx,cancel:=context.WithCancel(context.Background())
defer cancel()
go func() {
  _,_=io.Copy(dest,reader)
  cancel()
}()
go func() {
  _,_=io.Copy(conn,dest)
  cancel()
}()
<-ctx.Done()

relay阶段使用了一个context包来进行goroutine与主线程的同步,在不使用context的情况下函数执行不会等待goroutine执行结束就会直接返回nil,但是当引入context包后ctx,cancel:=context.WithCancel(context.Background())<-ctx.Done()机制可以让主线程等待goroutine执行结束后<-ctx.Done()停止阻塞,函数继续执行。