引言、计划、和碎碎念
碎碎念
准备换工作了、、、现在工作傻逼卷王同事太多,不伺候了 本来上一次换工作,就是说一定要找远程的,后来遇到现在给的还可以的,就算了没去.... 前一年还行,因为还有钱,所以过得潇洒自在,也不用干啥事,但是现在资金紧张了,就开始给我找事了...快乐生活没有了,天天被卷王找事,不伺候了
目标和口号还是喊起来,这次一定要找一个远程的了,再也不想去办公室了...想隔段时间换一个城市的候鸟生活
计划
其实论远程的话,也不是远程,就是所有程序员的来讲,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掉的每一个内容,非常规范化,所以这个错误就可以不管了
- 注意点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的用法,打算调试运行下的,结果报错
低于最低版本了,看来只能命令行 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 就完全可以满足要求了
- 注意点,回到上面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
现在就能正常跑了
那么还遗留了问题没有解决
- 之前不是序列号按照ID的吗,怎么好像没有按照ID
- 为什么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))
}