21天速成go-第一天

181 阅读9分钟

引言、计划、和碎碎念


碎碎念

准备换工作了、、、现在工作傻逼卷王同事太多,不伺候了 本来上一次换工作,就是说一定要找远程的,后来遇到现在给的还可以的,就算了没去.... 前一年还行,因为还有钱,所以过得潇洒自在,也不用干啥事,但是现在资金紧张了,就开始给我找事了...快乐生活没有了,天天被卷王找事,不伺候了

目标和口号还是喊起来,这次一定要找一个远程的了,再也不想去办公室了...想隔段时间换一个城市的候鸟生活

计划

其实论远程的话,也不是远程,就是所有程序员的来讲,Python还是很少的,如果想找远程的话,还是得go和rust 鉴于go的市场大,还是来一个速成go 到时候背景全部换成go 再去面试

另外go 还有一点深得我心,就是打包出来的程序或者镜像真的是太小了,对于Python大体积的镜像我真是受够了

论计划的话,打算21天速成go,现在是2024年05月08日20:41:17, 相当于月底的时候,就要对go非常熟悉了

不需要从基础语法开始了,打算直接从 实际的项目或者案例开始,然后配合上看的go的文章进行学习和测试

这个21天速成的文档就当做一个笔记,用来记录速成go上的每一个历程,包括刚开始是怎么入门学习的,有哪些想法,看到了哪些文章再去实验的过程

用go写一个ping程序


下午的时候刷到了用go 写一个ping的程序,这个入口我觉得非常棒,因为这算是一个实战的项目,而且代码量少,可以通过这个程序,来加深对go应用的理解,熟悉相关语法

另外为了有深刻的印证和对比,也找了 Python版本的实现一个ping的代码,用于对比这2个之间的区间,深刻理解Python和go 之间的异同

先从python写一个ping开始

跳过,怕时间不够,还是先从go开始好了。。。直接上go,比较和心得什么的,以后再说

go实现一个ping

先看看这台电脑上的go版本是多少 : go version go1.12.5 darwin/amd64 既然有的话,那么就直接用了

goland还没下载,发现城通网盘下了客户端之后,还强制扫码登录,又被迫下了APP再来注册扫码登录,结果下载时间还要一个小时,干脆不下载了,直接先用 vscode 来写

tip1

才刚开始写,就出错了,

  • 出错点1:我写的是 package main; 然后结果发现不需要后面的;go 的每一句也是不需要;分号的,这个要记住
  • 出错点2:我写的是 import ( "os", "math", "net" ) 后来发现如果是换行写的话,是不需要逗号的,直接分行就可以了,也是多了逗号
  • 出错点3:我写的import ("format") 实际上应该就是 fmt
  • 另外还引入了一个flag 我看用法应该就是用于命令行解析的
  • 注意点4:写完import之后,然后再定义命令行参数的时候,发现全部都划上红线了,以为是哪里语法不对,多打了什么符号,后来看了提示才知道,是因为import but not use 因为我还没写完,肯定是没有use到的,所以这个倒是可以忽略了,但是也能作为一个警醒,强行让你使用掉你import掉的每一个内容,非常规范化,所以这个错误就可以不管了

image.png

  • 注意点5:在定义type int8 的时候,报错了,是因为不能使用 type关键字,所以遇到需要使用type的时候要改成 typ int8 = 8 这样子默认值的形式
  • 说type 就用到了type 在定义结构体的时候,type 就是关键词,形如
type ICMP struct {
   Type uint8
   Code uint8
   CheckSum uint16 // 校验和
   ID uint8 
   SequenceNum uint16 // 序号
}

这样定义一个结构体,先除开type 不谈,这里注意到的是,因为都确定是正整数,所以都是 uint,确定了不包含负数,现在再来看下type的用法 看了一下type的用法,打算调试运行下的,结果报错

image.png

低于最低版本了,看来只能命令行 go 运行了,运行结果是

day1 $ go run test-type.go
sss cbb
server cb

那么为什么是这个结果呢 ?可以这么来看,type cb 是定义一个新的类型,这个新的类型是一个函数,接受string的参数,本来这个类型应该是个函数类型的,现在是叫做 cb类型 而 cb(...)我觉得可以把它看为一种强制转化,就是把本身括号里面的函数类型,强制转化为cb类型,这样看就合理了(实际上说,cb(...)是go的一种初始化方式。 ,cb(func(s string) { ... }) 并不是将函数转换为 cb 类型,而是直接使用一个函数字面量来声明和初始化一个 cb 类型的变量。

另外注意,type 还有一个alias 别名的功能,如果直接 type A int32 是定义出一个新的类型,而type A=int32 仅仅是别名的定义,类型还是和原来一样的类型,区别就是有没有等号,这个在类型迁移或者重构的时候就非常有用

回到最开始,所以我们定义结构体就是 type ICMP struct 这么来定义

  • 注意点 data := make([]byte size) 我最开始写成了 make(byte size)这个肯定是不对的,因为都用make进行初始化了,所以肯定是一个数组,必须有数组的【】标志
  • 注意点:log.SetFlags(log.Llongfile) 是设置日志格式,基本是log.Llongfile 就完全可以满足要求了

image.png

  • 注意点,回到上面2个,[]byte, size 是有逗号的。。。data := make([]byte, size)
  • 注意点,在初始化struct的时候,是有逗号的,一定是 参数名:参数值 + 逗号的形式
		icmp := &ICMP{
			Type:	typ,
			Code:	code,
			CheckSum:	uint16(0),
			ID: uint16(i),
			SequenceNum:	uint16(i),
		}

定义的时候没有逗号,但是初始化的时候是有逗号的

  • 注意点,重点!! 运行的时候报错了
./go-ping.go:171:3: n declared and not used
./go-ping.go:199:3: MaxTime declared and not used
./go-ping.go:200:3: MinTime declared and not used

报错排查了很久,之前好像没有打印出来行号,后来打印出来了行号然后比较才发现,是 := 和 = 的区别

我写的是
MaxTime := Max(MaxTime, t)
MinTime := Min(MinTime, t)
实际上是要写
MaxTime = Max(MaxTime, t)
MinTime = Min(MinTime, t)
                

另外n的问题也是类似,之所以之前写成n2 是因为写n的时候报错 n2, err := conn.Read(buf) 就是因为也是 = 而不是:= 是等于的话,就是可以继续使用n的

  • 注意点,还有错误,自己的是只能跑一次

而实际上应该能跑好多次的,直接复制人家的代码能跑好多次,同时格式化也出问题了 最终找到错误,是计算校验和时候的问题

	for length > 1{
		// 拼接且求和
		sum += uint32(data[index]) << 8 + uint32(data[index + 1])
		length -= 2
		index += 1
	}
       这里应该是index += 2 我写成了+=1

现在就能正常跑了

那么还遗留了问题没有解决

  1. 之前不是序列号按照ID的吗,怎么好像没有按照ID
  2. 为什么checksum 写入了data[2]和data[3] 两次呢

等等....等待明天更新解决

另附完整代码


package main


import (
	"os"
	"time"
	"net"
	"math"
	"log"
	"fmt"
	"flag"
	"bytes"
	"encoding/binary"
)

var (
	helpFlag bool
	timeout int64 // 耗时
	size int  // 大小?
	count int // 请求次数
	typ uint8 = 8 
	code uint8 = 0
	SendCnt int
	RecCnt int
	MaxTime int64 = math.MinInt64 // 最大耗时
	MinTime int64 = math.MaxInt64 // 最小耗时
	SumTime int64 // 总计耗时
)


type ICMP struct {
	Type uint8
	Code uint8
	CheckSum uint16 // 校验和
	ID uint16
	SequenceNum uint16 // 序号
}


func GetCommandArgs(){
	flag.Int64Var(&timeout, "w", 1000, "请求超时时间")
	flag.IntVar(&size, "l", 32, "发送字节数")
	flag.IntVar(&count, "n", 4, "请求次数")
	flag.BoolVar(&helpFlag, "h", false, "显示帮助信息")
	flag.Parse()
}

func displayHelp(){
	fmt.Println(`选项:
		-n count 要发送的回显请求数
		-l size 发送缓冲区的大小
		-w timeout 等待每次回复的超时时间(毫秒)
		-h 帮助选项
	`)
}


func Max(a, b int64) int64 {
	if a > b{
		return a
	}
	return b
}

func Min(a, b int64) int64 {
	if a < b{
		return a
	}
	return b
}

func checkSum(data []byte) uint16 {
	// 第一步,两两拼接求和
	length := len(data)
	index := 0
	var sum uint32

	for length > 1{
		// 拼接且求和
		sum += uint32(data[index]) << 8 + uint32(data[index + 1])
		length -= 2
		index += 2
	}

	// 奇数情况下,还剩下一个,直接求和过去
	if length == 1 {
		sum += uint32(data[index])
	}

	// 第二部,高16位 低16位相加,直到高16位为0
	hi := sum >> 16
	for hi != 0 {
		// sum 本身是uint32类型的,这里为什么转一下16又转为uint32呢
		sum = hi + uint32( uint16(sum) )
		hi = sum >> 16
	}

	// 返回sum值取反
	return uint16( ^sum )

}


func main() {
	fmt.Println()
	log.SetFlags( log.Llongfile )
	GetCommandArgs()

	if helpFlag {
		displayHelp()
		os.Exit(0)
	}

	desIP := os.Args[ len(os.Args) -1 ]

	// 构建链接
	conn, err := net.DialTimeout("ip:icmp", desIP, time.Duration(timeout) * time.Millisecond )
	if err != nil {
		log.Println(err.Error())
		return
	}
	// defer 反正是最后运行,这样就能开启了之后确保能欧关闭
	defer conn.Close()

	// 远程地址
	remoteaddr := conn.RemoteAddr()
	fmt.Printf("正在Ping %s [%s] 具有 %d 字节的数据:\n", desIP, remoteaddr, size)
	
	// 开始循环执行
	for i:=0; i<count; i++ {
		
		// 构建请求
		icmp := &ICMP{
			Type:	typ,
			Code:	code,
			CheckSum:	uint16(0),
			ID: uint16(i),
			SequenceNum:	uint16(i),
		}
		
		// 将请求转为二进制流
		var buffer bytes.Buffer
		binary.Write(&buffer, binary.BigEndian, icmp)

		// 请求的数据
		data := make([]byte, size)

		// 将请求数据写入到icmp报头
		buffer.Write(data)
		data = buffer.Bytes()

		// 计算ICMP校验和
		checkSum := checkSum(data)

		// 签名复制到data里面
		data[2] = byte(checkSum >> 8)
		data[3] = byte(checkSum)
		
		startTime := time.Now()

		// 设置超时时间
		conn.SetDeadline( time.Now().Add( time.Duration( timeout ) * time.Millisecond ))

		// 将data写入连接中
		n, err := conn.Write(data)
		if err != nil {
			log.Println(err)
			continue
		}

		// 发送数 ++
		SendCnt++

		// 接收相应
		buf := make([]byte, 1024)
		// 这里我是用n 不行,必须使用n2
		n, err = conn.Read(buf)
		if err != nil {
			log.Println( err )
			continue
		}

		// 接收数++
		RecCnt++

		// 打印信息
		// t := time.Since(startTime).Millisecond()
		// t := time.Since(startTime).Milliseconds()
		t := time.Since(startTime).Nanoseconds() / 1e6
		// 这里的n 是由conn.Read返回的内容
		fmt.Printf("来自 %d.%d.%d.%d 的回复: 字节=%d, 时间=%d TTL=%d\n",buf[12], buf[13], buf[14], buf[15], n-28, t, buf[8])

		MaxTime = Max(MaxTime, t)
		MinTime = Min(MinTime, t)
		SumTime += t
		time.Sleep(time.Second)
	}

	fmt.Printf("\n%s 的 Ping 数据统计信息:\n", remoteaddr)
	fmt.Printf("	数据包: 已发送 = %d, 已接收: %d, 丢失: %d (%.f%% 丢失), \n", SendCnt, RecCnt, count*2 - SendCnt - RecCnt, float64(count*2 - SendCnt - RecCnt) / float64(count*2) * 100 )
	fmt.Println("往返行程的估计时间(以毫秒为单位)")
	// 这里为什么是int64 count  oo 因为count 之前是int类型,而SumTime 是int64类型,类型不一样 所以要转化一下
	fmt.Printf("	最短 = %d, 最长 = %d, 平均 = %d\n", MinTime, MaxTime, SumTime / int64(count))

}