LOGO
首页 网站广场 站长动态 活跃度榜 审核查询 逛逛好站 留言交流 提交申请 关于本站

站长动态

站长动态所展示的是已加入好站网成员站长文章
共同步 2422 篇博文
(每2小时更新一次)
阿川
入驻第1年
Manjaro Linux 双显卡切换解决方案
小十
入驻第1年
30岁了
等待30岁的这个过程很漫长,在迈向30岁这道门槛之前,无论是周围环境还是自身,都遇到了很多问题。 上篇博文是关于一些电脑上的小技巧,而上上篇博文在发表的时候,还没有疫情这回事情。 怎么也没想到,会有这么大影响的一件事,也没有想到,会在发生这件事之后,10月份还有幸陪父亲去他的部队看看,去之前没有去过的广东省走一遭,也见了一下真正的海景房,感受一下他人靠自己拼搏出来的成果,而对于我自己,这一年平平无奇,本以为的变化也没有发生,网站服务器经常卡顿得要死,之前以为自己这个年纪应该会很轻松承担一笔更贵的服务器费用,而现实却是连运维都没有怎么进行。 今年在疫情期间又重新学了C4D,又因为工作,荒废了学C4D的进展。自己在工作上感觉比以前更辛苦,也更没有收获,整个今年就笼罩在一个奇怪的氛围里面。房子已经交付,但没有心思装修,有了自己的一辆车,也没有觉得生活变化了什么。已经很久没有去买到自己喜欢的物件,也该为自己考虑一下,让自己开心一下。 也想过30岁的第一天会是什么样,可到了跟前其实还是差不了太多,过惯了这种平常没有波折的日子,也没有一些让自己感到开心的事情。开心是可以制造出来的,我应该会有往下努力的方向了吧。
阿川
入驻第1年
Manjaro Linux 自动禁用触摸板
阿川
入驻第1年
Docker Compose 快速构建 LNMP 笔记
Debug
入驻第1年
Go IP 段范围校验
近期做了一个需求,是检测某个 IP 是否在若干 IP 段内,做固定地点 IP 筛查,满足特定业务需求。 解决方案 PLAN A 点分十进制范围区分 简单来讲,就是将 IPv4 原有的四段,分别对比 IP 地址,查看每一段是否在 IP 段范围内,可以用于段控制在每一个特定段 0 ~ 255 内筛选,例如: 1 192.123.1.0 ~ 192.123.156.255 这样的比较规范的特定段可以实现简单的筛选,但是问题来了,不规则的连续 IP 段怎么排除? 如下: 1 2 IP段:192.168.1.0 ~ 192.172.3.255 IP: 192.160.0.255 这样就会出现问题,可以看到按照简单的分段对比,很明显校验不通过,但是这个 IP 还是存在在 IP 段中,方案只能针对统一分段下规则的IP段才可以区分。 PLAN B 转整型对别 IP 地址可以转换为整数,可以将 IP 范围化整为 整数范围进行排查。 这种方式只需要将授为范围内的地址转换为整数,就可以将 IP 排查在外了。 代码 以下是示例代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 package main import ( "fmt" "strconv" "strings" ) func main() { ipVerifyList := "192.168.1.0-192.172.3.255" ip := "192.170.223.1" ipSlice := strings.Split(ipVerifyList, `-`) if len(ipSlice) < 0 { return } if ip2Int(ip) >= ip2Int(ipSlice[0]) && ip2Int(ip) <= ip2Int(ipSlice[1]) { fmt.Println("ip in iplist") return } fmt.Println("ip not in iplist") } func ip2Int(ip string) int64 { if len(ip) == 0 { return 0 } bits := strings.Split(ip, ".") if len(bits) < 4 { return 0 } b0 := string2Int(bits[0]) b1 := string2Int(bits[1]) b2 := string2Int(bits[2]) b3 := string2Int(bits[3]) var sum int64 sum += int64(b0) << 24 sum += int64(b1) << 16 sum += int64(b2) << 8 sum += int64(b3) return sum } func string2Int(in string) (out int) { out, _ = strconv.Atoi(in) return } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
Go 标准库 限流器 time/rate 设计与实现
限流器是后台服务中十分重要的组件,在实际的业务场景中使用居多,其设计在微服务、网关、和一些后台服务中会经常遇到。限流器的作用是用来限制其请求的速率,保护后台响应服务,以免服务过载导致服务不可用现象出现。 限流器的实现方法有很多种,例如 Token Bucket、滑动窗口法、Leaky Bucket等。 在 Golang 库中官方给我们提供了限流器的实现golang.org/x/time/rate,它是基于令牌桶算法(Token Bucket)设计实现的。 令牌桶算法 令牌桶设计比较简单,可以简单的理解成一个只能存放固定数量雪糕?的一个冰箱,每个请求可以理解成来拿雪糕的人,有且只能每一次请求拿一块?,那雪糕拿完了会怎么样呢?这里会有一个固定放雪糕的工人,并且他往冰箱里放雪糕的频率都是一致的,例如他 1s 中只能往冰箱里放 10 块雪糕,这里就可以看出请求响应的频率了。 令牌桶设计概念: 令牌:每次请求只有拿到 Token 令牌后,才可以继续访问; 桶:具有固定数量的桶,每个桶中最多只能放设计好的固定数量的令牌; 入桶频率:按照固定的频率往桶中放入令牌,放入令牌不能超过桶的容量。 也就是说,基于令牌桶设计算法就限制了请求的速率,达到请求响应可控的目的,特别是针对于高并发场景中突发流量请求的现象,后台就可以轻松应对请求了,因为到后端具体服务的时候突发流量请求已经经过了限流了。 具体设计 限流器定义 1 2 3 4 5 6 7 8 type Limiter struct { mu sync.Mutex // 互斥锁(排他锁) limit Limit // 放入桶的频率 float64 类型 burst int // 桶的大小 tokens float64 // 令牌 token 当前剩余的数量 last time.Time // 最近取走 token 的时间 lastEvent time.Time // 最近限流事件的时间 } limit、burst 和 token 是这个限流器中核心的参数,请求并发的大小在这里实现的。 在令牌发放之后,会存储在 Reservation 预约对象中: 1 2 3 4 5 6 7 type Reservation struct { ok bool // 是否满足条件分配了 token lim *Limiter // 发送令牌的限流器 tokens int // 发送 token 令牌的数量 timeToAct time.Time // 满足令牌发放的时间 limit Limit // 令牌发放速度 } 消费 Token Limiter 提供了三类方法供用户消费 Token,用户可以每次消费一个 Token,也可以一次性消费多个 Token。而每种方法代表了当 Token 不足时,各自不同的对应手段。 Wait、WaitN 1 2 func (lim *Limiter) Wait(ctx context.Context) (err error) func (lim *Limiter) WaitN(ctx context.Context, n int) (err error) 其中,Wait 就是 WaitN(ctx, 1),在下面的方法介绍实现也是一样的。 使用 Wait 方法消费 Token 时,如果此时桶内 Token 数组不足 ( 小于 n ),那么 Wait 方法将会阻塞一段时间,直至 Token 满足条件。如果充足则直接返回。 Allow、AllowN 1 2 func (lim *Limiter) Allow() bool func (lim *Limiter) AllowN(now time.Time, n int) bool AllowN 方法表示,截止到当前某一时刻,目前桶中数目是否至少为 n 个,满足则返回 true,同时从桶中消费 n 个 token。 反之返回不消费 Token,false。 通常对应这样的线上场景,如果请求速率过快,就直接丢到某些请求。 Reserve、ReserveN 官方提供的限流器有阻塞等待式的 Wait,也有直接判断方式的 Allow,还有提供了自己维护预留式的,但核心的实现都是下面的 reserveN 方法。 1 2 func (lim *Limiter) Reserve() *Reservation func (lim *Limiter) ReserveN(now time.Time, n int) *Reservation 当调用完成后,无论 Token 是否充足,都会返回一个Reservation *对象。 你可以调用该对象的 Delay() 方法,该方法返回了需要等待的时间。如果等待时间为 0,则说明不用等待。 必须等到等待时间结束之后,才能进行接下来的工作。 或者,如果不想等待,可以调用 Cancel() 方法,该方法会将 Token 归还。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 func (lim *Limiter) reserveN(now time.Time, n int, maxFutureReserve time.Duration) Reservation { lim.mu.Lock() // 首先判断是否放入频率是否为无穷大 // 如果为无穷大,说明暂时不限流 if lim.limit == Inf { lim.mu.Unlock() return Reservation{ ok: true, lim: lim, tokens: n, timeToAct: now, } } // 拿到截至 now 时间时 // 可以获取的令牌 tokens 数量及上一次拿走令牌的时间 last now, last, tokens := lim.advance(now) // 更新 tokens 数量 tokens -= float64(n) // 如果 tokens 为负数,代表当前没有 token 放入桶中 // 说明需要等待,计算等待的时间 var waitDuration time.Duration if tokens < 0 { waitDuration = lim.limit.durationFromTokens(-tokens) } // 计算是否满足分配条件 // 1、需要分配的大小不超过桶的大小 // 2、等待时间不超过设定的等待时长 ok := n <= lim.burst && waitDuration <= maxFutureReserve // 预处理 reservation r := Reservation{ ok: ok, lim: lim, limit: lim.limit, } // 若当前满足分配条件 // 1、设置分配大小 // 2、满足令牌发放的时间 = 当前时间 + 等待时长 if ok { r.tokens = n r.timeToAct = now.Add(waitDuration) } // 更新 limiter 的值,并返回 if ok { lim.last = now lim.tokens = tokens lim.lastEvent = r.timeToAct } else { lim.last = last } lim.mu.Unlock() return r } 具体使用 rate 包中提供了对限流器的使用,只需要指定 limit(放入桶中的频率)、burst(桶的大小)。 1 2 3 4 5 6 func NewLimiter(r Limit, b int) *Limiter { return &Limiter{ limit: r, // 放入桶的频率 burst: b, // 桶的大小 } } 在这里,使用一个 http api 来简单的验证一下 time/rate 的强大: 1 2 3 4 5 6 7 8 9 10 11 12 13 func main() { r := rate.Every(1 * time.Millisecond) limit := rate.NewLimiter(r, 10) http.HandleFunc("/", func(writer http.ResponseWriter, request *http.Request) { if limit.Allow() { fmt.Printf("请求成功,当前时间:%s\n", time.Now().Format("2006-01-02 15:04:05")) } else { fmt.Printf("请求成功,但是被限流了。。。\n") } }) _ = http.ListenAndServe(":8081", nil) } 在这里,我把桶设置成了每一毫秒投放一次令牌,桶容量大小为 10,起一个 http 的服务,模拟后台 API。 接下来做一个压力测试,看看效果如何: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 func GetApi() { api := "http://localhost:8081/" res, err := http.Get(api) if err != nil { panic(err) } defer res.Body.Close() if res.StatusCode == http.StatusOK { fmt.Printf("get api success\n") } } func Benchmark_Main(b *testing.B) { for i := 0; i < b.N; i++ { GetApi() } } 效果如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 ...... 请求成功,当前时间:2020-08-24 14:26:52 请求成功,但是被限流了。。。 请求成功,但是被限流了。。。 请求成功,但是被限流了。。。 请求成功,但是被限流了。。。 请求成功,但是被限流了。。。 请求成功,当前时间:2020-08-24 14:26:52 请求成功,但是被限流了。。。 请求成功,但是被限流了。。。 请求成功,但是被限流了。。。 请求成功,但是被限流了。。。 ...... 在这里,可以看到,当使用 AllowN 方法中,只有当令牌 Token 生产出来,才可以消费令牌,继续请求,剩余的则是将其请求抛弃,当然在实际的业务处理中,可以用比较友好的方式反馈给前端。 在这里,先有的几次请求都会成功,是因为服务启动后,令牌桶会初始化,将令牌放入到桶中,但是随着突发流量的请求,令牌按照预定的速率生产令牌,就会出现明显的令牌供不应求的现象。 开源仓库 目前 time/rate 是一个独立的限流器开源解决方案,感兴趣的小伙伴可以给此项目一个 Star,谢谢。 https://github.com/golang/time References 限流器系列(2) – Token Bucket 令牌桶 Golang 限流器的使用和实现 Golang 标准库限流器 time/rate 使用介绍 https://github.com/golang/time/rate.go 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
阿川
入驻第1年
(转)一个关于if else容易迷惑的问题
阿川
入驻第1年
使用开源项目免费申请 JetBrains 开源许可证
Debug
入驻第1年
我的 MacBook Pro 又满血复活啦
经过近两个星期的检测,维修 ?,我的 MacBook 满血复活了,事情是这样的,两周前我的电脑突然之间就黑屏,有充电反馈,键盘,Bar 和触控板均失灵,拿到公司 IT 部门,给我的意见是去售后 ?,紧接着到了周末去了售后,给我的解决方案是更换硬件,告诉我说要更换主板,也就代表着硬盘数据没有了,允悲,我同时给他说明针对于我两次修复蝶形键盘的经历,售后人员决定给我申请键盘也更换,心中多少有些安慰,于是他给了我一个维修周期,届时来领取就可以了。 于是我就拿着维修单回去了,过了两天,接到了售后的电话,我本以为修好了,并没有,售后给我说做了检测,显示器也有问题,需要给我更换,这,,,不就是更换全部的部件么,直接给我换台多效率 ?,显然苹果并没有那么给我做,现在拿到手中的就是除了下底壳没有更换,其他全部更换的九成新新机,百感交集呀。 不过,最终是修好了,在公司入职的这么多天也学习了很多东西,近期在不断的整理,后续会总结分享的,感谢一个陌生网友的关怀,?,一个高更新的博客要跑路了,虽然技术很菜,分享的技术网上一大堆,但是经验是积累的,相信自己的努力? 最终会成为大牛的,加油,嘿嘿。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
Go 语言实现 RPC 调用
RPC 在分布式计算,远程过程调用(英语:Remote Procedure Call,缩写为 RPC)是一个计算机通信协议。该协议允许运行于一台计算机的程序调用另一个地址空间(通常为一个开放网络的一台计算机)的子程序,而程序员就像调用本地程序一样,无需额外地为这个交互作用编程(无需关注细节)。RPC 是一种服务器-客户端( Client/Server )模式,经典实现是一个通过 发送请求-接受回应 进行信息交互的系统。 wiki 维基百科 在这里引用一下维基百科对于 RPC 的解释, 可以针对与 HTTP 协议来比较分析,RPC 更适合于公司中大、中型项目分布式调用场景。 调用流程 客户端调用客户端 stub(client stub)。这个调用是在本地,并将调用参数 push 到栈(stack)中; 客户端 stub(client stub)将这些参数包装,并通过系统调用发送到服务端机器。打包的过程叫 marshalling。(常见方式:XML、JSON、二进制编码); 客户端本地操作系统发送信息至服务器。(可通过自定义TCP协议或HTTP传输); 服务器系统将信息传送至服务端stub(server stub); 服务端stub(server stub)解析信息。该过程叫 unmarshalling; 服务端stub(server stub)调用程序,并通过类似的方式返回给客户端。 RPC 与 HTTP 区别 RPC 调用实现的方式是和 HTTP 有异曲同工之处的,但是对于 RPC 与 HTTP 在 请求 / 响应中还是存在着差别的: HTTP 与 RPC 协议在实现上是不同的,大家都了解到 HTTP 原理就是 客户端请求服务端,服务端去响应并返回结果,但是 RPC 协议设计的时候采用的方式就是服务端给客户端提供 TCP 长连接服务,Client 端去调用 Server 提供的接口,实现特定的功能; RPC 可以同时提供同步调用及异步调用,而 HTTP 提供的方式就是同步调用,客户端会等待并接受服务端的请求处理的结果; RPC 服务设计可以提高代码编写过程中的解耦操作,提高代码的可移植性,每一个 服务可以设计成提供特定功能的小服务,客户端去调取远程的服务,而不用去关心远程是怎么实现的。 RPC 应用领域 大型网站的内部子系统设计; 为系统提供降级功能; 并发设计场景; 当然 RPC 也有缺点,每一个 RPC 服务都需要单独搭建,一旦服务出错或者更为严重的不提供支持,作为客户端的就会出现服务不可用,这对系统稳定性及可持续提供支持要求比较高,当然在设计过程中,这样也加大了对系统调试的难度,也就是说这种设计要求 RPC 服务的稳定性及正确性要求是比较大的。 实现代码 客户端实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 package main import ( "demo/common" "fmt" "net/rpc" ) func main() { var args = common.Args{A: 32, B: 14} var result = common.Result{} var client, err = rpc.DialHTTP("tcp", "127.0.0.1:9090") if err != nil { fmt.Printf("connect rpc server failed, err:%v", err) } err = client.Call("MathService.Divide", args, &result) if err != nil { fmt.Printf("call math service failed, err:%v", err) } fmt.Printf("call RPC server success, result:%f", result.Value) } 服务端实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 package main import ( "demo/common" "fmt" "net/http" "net/rpc" ) func main() { var ms = new(common.MathService) // 注册 RPC 服务 err := rpc.Register(ms) if err != nil { fmt.Printf("rpc server register faild, err:%s", err) } // 将 RPC 服务绑定到 HTTP 服务中去 rpc.HandleHTTP() fmt.Printf("server start ....") err = http.ListenAndServe(":9090", nil) if err != nil { fmt.Printf("listen and server is failed, err:%v\n", err) } fmt.Printf("server stop ....") } 功能实现 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 package common import "errors" type Args struct { A, B float32 } type Result struct { Value float32 } type MathService struct {} func (s *MathService) Add (args *Args, result *Result) error{ result.Value = args.A + args.B return nil } func (s *MathService) Divide(args *Args, result *Result) error{ if args.B == 0 { return errors.New("arge.B is 0") } result.Value = args.A / args.B return nil } References 简述RPC原理实现 - 博客园 Http和RPC区别 远程过程调用 - 维基百科 直观讲解–RPC调用和HTTP调用的区别 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
使用 GVM 工具管理 Go 版本
在 Go 项目开发中,团队要保持开发版本一致,怎么能够快速的安装及部署并且切换 Go 环境,在这里推荐一款工具 GVM ( Go Version Manager ),它可以便捷切换与自定义 Go Path 、Go Root 等参数,是一款实打实的多版本安装及管理利器。 GVM,类似于ruby 中的 RVM,java 中的 jenv(国产),可用于方便管理 Go 的版本,它有如下几个主要特性: 管理 Go 的多个版本,包括安装、卸载和指定使用 Go 的某个版本; 查看官方所有可用的 Go 版本,同时可以查看本地已安装和默认使用的 Go 版本; 管理多个 GOPATH,并可编辑 Go 的环境变量; 可将当前目录关联到 GOPATH; 可以查看 GOROOT 下的文件差异。 安装 Installing 1 bash < <(curl -s -S -L https://raw.githubusercontent.com/moovweb/gvm/master/binscripts/gvm-installer) 或者,如果您使用的是 zsh,只需使用 zsh 更改 bash。 使用 GVM 使用 gvm 可以查看支持的操作: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 ➜ ~ gvm Usage: gvm [command] Description: GVM is the Go Version Manager Commands: version - print the gvm version number get - gets the latest code (for debugging) use - select a go version to use (--default to set permanently) diff - view changes to Go root help - display this usage text implode - completely remove gvm install - install go versions uninstall - uninstall go versions cross - install go cross compilers linkthis - link this directory into GOPATH list - list installed go versions listall - list available versions alias - manage go version aliases pkgset - manage go packages sets pkgenv - edit the environment for a package set 安装 Go 版本 例如安装 go1.13 版本: 1 gvm install go1.13 查看 Go 版本 1 2 3 4 5 6 ➜ ~ gvm list gvm gos (installed) go1.12 => system 切换 Go 版本 1 gvm use go1.** 管理 Gopath 环境 GVM 提供了一个比较简单的工具 gvm pkgset 可以创建使用 GOPATH 环境: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 ➜ ~ gvm pkgset = gvm pkgset * http://github.com/moovweb/gvm == DESCRIPTION: GVM pkgset is used to manage various Go packages == Usage gvm pkgset Command == Command create - create a new package set delete - delete a package set use - select where gb and goinstall target and link empty - remove all code and compiled binaries from package set list - list installed go packages 卸载 Uninstall 卸载某个安装好的 Go 版本: 1 gvm uninstall go1.13 开源代码 GVM 是一款使用 Shell 脚本实现的便捷工具,作为开源项目,推荐大家给一个 Star 支持。 https://github.com/moovweb/gvm 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
Go 语言操作 MySQL 之 SQLX 包
SQLX 库 sqlx是 Go 的软件包,它在出色的内置database/sql软件包的基础上提供了一组扩展。 该库兼容 sql 原生包,同时又提供了更为强大的、优雅的查询、插入函数。 该库提供四个处理类型,分别是: sqlx.DB - 类似原生的sql.DB; sqlx.Tx - 类似原生的sql.Tx; sqlx.Stmt - 类似原生的 sql.Stmt, 准备 SQL 语句操作; sqlx.NamedStmt - 对特定参数命名并绑定生成 SQL 语句操作。 提供两个游标类型,分别是: sqlx.Rows - 类似原生的 sql.Rows, 从 Queryx 返回; sqlx.Row - 类似原生的 sql.Row, 从 QueryRowx 返回。 安装 SQLX 库 1 go get github.com/jmoiron/sqlx 使用操作 连接数据库 1 2 3 4 5 6 7 8 9 10 11 12 // 初始化数据库 func initMySQL() (err error) { dsn := "root:password@tcp(127.0.0.1:3306)/database" db, err = sqlx.Open("mysql", dsn) if err != nil { fmt.Printf("connect server failed, err:%v\n", err) return } db.SetMaxOpenConns(200) db.SetMaxIdleConns(10) return } SetMaxOpenConns 和 SetMaxIdleConns 分别为设置最大连接数和最大空闲数。 数据表达及引用 在这里提前声明一个用户结构体 user,将 *sqlx.DB 作为一个全局变量使用,当然也要提前引用 MySQL 的驱动包,如下设计: 1 2 3 4 5 6 7 8 9 10 11 12 13 import ( "fmt" _ "github.com/go-sql-driver/mysql" "github.com/jmoiron/sqlx" ) var db *sqlx.DB type user struct { Id int `db:"id"` Age int `db:"age"` Name string `db:"name"` } 查询操作 查询一行数据 查询一行数据使用 sqlx 库中的 Get 函数实现: 1 func (db *DB) Get(dest interface{}, query string, args ...interface{}) error dest 是用户声明变量接收查询结果,query 为查询 SQL 语句,args 为绑定参数的赋值。 1 2 3 4 5 6 7 8 9 10 11 // 查询一行数据 func queryRow() { sqlStr := "SELECT id, name, age FROM user WHERE id = ?" var u user if err := db.Get(&u, sqlStr, 1); err != nil { fmt.Printf("get data failed, err:%v\n", err) return } fmt.Printf("id:%d, name:%s, age:%d\n", u.Id, u.Name, u.Age) } 查询多行数据 而查询多行数据则使用的是Select 函数: 1 func (db *DB) Select(dest interface{}, query string, args ...interface{}) error 使用 Select 函数进行查询的时候,需要先声明一个结构体数组接收映射过来的数据: 1 2 3 4 5 6 7 8 9 10 11 12 13 // 查询多行数据 func queryMultiRow() { sqlStr := "SELECT id, name, age FROM user WHERE id > ?" var users []user if err := db.Select(&users, sqlStr, 0); err != nil { fmt.Printf("get data failed, err:%v\n", err) return } for i := 0; i < len(users); i++ { fmt.Printf("id:%d, name:%s, age:%d\n", users[i].Id, users[i].Name, users[i].Age) } } 插入、更新、删除操作 在 sqlx 库中,使用插入、更新、删除操作是和原生 sql 库实现是一致的,都是采用 Exec 函数来实现的: 插入操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 插入数据 func insertRow() { sqlStr := "INSERT INTO user(name, age) VALUES(?, ?)" result, err := db.Exec(sqlStr, "Meng小羽", 22) if err != nil { fmt.Printf("exec failed, err:%v\n", err) return } insertID, err := result.LastInsertId() if err != nil { fmt.Printf("get insert id failed, err:%v\n", err) return } fmt.Printf("insert data success, id:%d\n", insertID) } 更新操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 更新数据 func updateRow() { sqlStr := "UPDATE user SET age = ? WHERE id = ?" result, err := db.Exec(sqlStr, 22, 6) if err != nil { fmt.Printf("exec failed, err:%v\n", err) return } affectedRows, err := result.RowsAffected() if err != nil { fmt.Printf("get affected failed, err:%v\n", err) return } fmt.Printf("update data success, affected rows:%d\n", affectedRows) } 删除操作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 删除一行 func deleteRow() { sqlStr := "DELETE FROM user WHERE id = ?" result, err := db.Exec(sqlStr, 4) if err != nil { fmt.Printf("exec failed, err:%v\n", err) return } affectedRows, err := result.RowsAffected() if err != nil { fmt.Printf("get affected failed, err:%v\n", err) return } fmt.Printf("delete data success, affected rows:%d\n", affectedRows) } 参数绑定 在库中提供最常用的就是NamedQuery和NamedExec函数,一个是执行对查询参数命名并绑定,另一个则是对 CUD 操作的查询参数名的绑定: NamedQuery 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 绑定查询 func selectNamedQuery() { sqlStr := "SELECT id, name, age FROM user WHERE age = :age" rows, err := db.NamedQuery(sqlStr, map[string]interface{}{ "age": 22, }) if err != nil { fmt.Printf("named query failed failed, err:%v\n", err) return } defer rows.Close() for rows.Next() { var u user if err := rows.StructScan(&u); err != nil { fmt.Printf("struct sacn failed, err:%v\n", err) continue } fmt.Printf("%#v\n", u) } } NamedExec 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 // 使用 named 方法插入数据 func insertNamedExec() { sqlStr := "INSERT INTO user(name, age) VALUES(:name, :age)" result, err := db.NamedExec(sqlStr, map[string]interface{}{ "name": "里斯", "age": 18, }) if err != nil { fmt.Printf("named exec failed, err:%v\n", err) return } insertId, err := result.LastInsertId() if err != nil { fmt.Printf("get last insert id failed, err:%v\n", err) return } fmt.Printf("insert data success, id:%d\n", insertId) } 事务操作 使用Begin函数、Rollback函数及Commit函数实现事务操作: 1 2 3 4 5 6 // 开启事务 func (db *DB) Begin() (*Tx, error) // 回滚事务 func (tx *Tx) Rollback() error // 提交事务 func (tx *Tx) Commit() error 示例代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 // 事务操作 func updateTransaction() (err error) { tx, err := db.Begin() if err != nil { fmt.Printf("transaction begin failed, err:%v\n", err) return err } defer func() { if p := recover(); p != nil { _ = tx.Rollback() panic(p) } else if err != nil { fmt.Printf("transaction rollback") _ = tx.Rollback() } else { err = tx.Commit() fmt.Printf("transaction commit") return } }() sqlStr1 := "UPDATE user SET age = ? WHERE id = ? " reuslt1, err := tx.Exec(sqlStr1, 18, 1) if err != nil { fmt.Printf("sql exec failed, err:%v\n", err) return err } rows1, err := reuslt1.RowsAffected() if err != nil { fmt.Printf("affected rows is 0") return } sqlStr2 := "UPDATE user SET age = ? WHERE id = ? " reuslt2, err := tx.Exec(sqlStr2, 19, 5) if err != nil { fmt.Printf("sql exec failed, err:%v\n", err) return err } rows2, err := reuslt2.RowsAffected() if err != nil { fmt.Printf("affected rows is 0\n") return } if rows1 > 0 && rows2 > 0 { fmt.Printf("update data success\n") } return } 开源项目 最后将此开源项目放在此处,大家要是感兴趣可以给这个开源项目一个 Star,感谢。 https://github.com/jmoiron/sqlx References http://jmoiron.github.io/sqlx/ sqlx库使用指南 - 李文周的博客 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
小十
入驻第1年
删除WPS云文档的各种手段
删除电脑左侧栏 WPS 云文档 安装完 Adobe 的 Photoshop、After Effects 等软件后,资源管理器导航栏会出现让人讨厌的 Creative Cloud Files 目录,稍微修改注册表即可删除。 操作步骤: 快捷键 Windows + R 输入 regedit 打开注册表编辑器 定位到以下路径: HKEY_USERS\S-1-5-21-2145665870-3214923404-114890976-1001\Software\Microsoft\Windows\CurrentVersion\Explorer\Desktop\NameSpace\ 注意:S-1-5-……-1001 这一串,只要首尾一样即可,不同电脑这个地方不完全一样 NameSpace 目录下有两个目录,一个是 Onedrive 一个就是 Creative Cloud Files,删除即可 删除"我的电脑"中的 WPS 云文档 操作步骤: Win + R 输入 regedit 打开注册表 找到如下项目: \HKEY_CURRENT_USER\Software\Microsoft\Windows\CurrentVersion\Explorer\MyComputer\NameSpace 点击浏览里面的项目,你就会找到有 WPS 云文档的子项,删除即可 删除"上传到 WPS 云文档"右键菜单 打开注册表编辑器 regedit,然后在注册表里查找 qingshellext 这个键值,把找到的都删除就可以了。(火绒也是) Windows 7 原版镜像安装跳过输入用户名 操作步骤: 安装到需要输入用户名的时候按 Shift + F10 输入 lusrmgr.msc 回车 用户开启 administrator 开启管理员账户后输入 taskmgr 回车 资源管理器中结束 MSOOBE 进程 删除"上传到百度网盘"右键菜单 操作步骤: 打开 Windows 10 的注册表编辑器窗口
Debug
入驻第1年
Go 语言操作 MySQL 之 预处理
预处理 预处理是 MySQL 为了防止客户端频繁请求的一种技术,是对相同处理语句进行预先加载在 MySQL 中,将操作变量数据用占位符来代替,减少对 MySQL 的频繁请求,使得服务器高效运行。 在这里客户端并不是前台后台之间的 C/S 架构,而是后台程序对数据库服务器进行操作的 C/S 架构,这样就可以简要地理解了后台程序作为 Client 向 MySQL Server 请求并处理结果了。 普通 SQL 执行处理过程: 在客户端准备 SQL 语句; 发送 SQL 语句到 MySQL 服务器; 在 MySQL 服务器执行该 SQL 语句; 服务器将执行结果返回给客户端。 预处理执行处理过程: 将 SQL 拆分为结构部分与数据部分; 在执行 SQL 语句的时候,首先将前面相同的命令和结构部分发送给 MySQL 服务器,让 MySQL 服务器事先进行一次预处理(此时并没有真正的执行 SQL 语句); 为了保证 SQL 语句的结构完整性,在第一次发送 SQL 语句的时候将其中可变的数据部分都用一个数据占位符来表示; 然后把数据部分发送给 MySQL 服务端,MySQL 服务端对 SQL 语句进行占位符替换; MySQL 服务端执行完整的 SQL 语句并将结果返回给客户端。 预处理优点 预处理语句大大减少了分析时间,只做了一次查询(虽然语句多次执行); 绑定参数减少了服务器带宽,只需发送查询的参数,而不是整个语句; 预处理语句针对 SQL 注入是非常有用的,因为参数值发送后使用不同的协议,保证了数据的合法性。 Go 语言实现 在 Go 语言中,使用 db.Prepare() 方法实现预处理: 1 func (db *DB) Prepare(query string) (*Stmt, error) Prepare 执行预处理 SQL 语句,并返回 Stmt 结构体指针,进行数据绑定操作。 查询操作使用 db.Prepare() 方法声明预处理 SQL,使用 stmt.Query() 将数据替换占位符进行查询,更新、插入、删除操作使用 stmt.Exec() 来操作。 预处理查询示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 // 预处理查询数据 func prepareQuery() { sqlStr := "SELECT id,name,age FROM user WHERE id > ?" stmt, err := db.Prepare(sqlStr) if err != nil { fmt.Printf("prepare sql failed, err:%v\n", err) return } rows, err := stmt.Query(1) if err != nil { fmt.Printf("exec failed, err:%v\n", err) return } defer rows.Close() for rows.Next() { var u user err := rows.Scan(&u.id, &u.name, &u.age) if err != nil { fmt.Printf("scan data failed, err:%v\n", err) return } fmt.Printf("id:%d, name:%s, age:%d\n", u.id, u.name, u.age) } } 预处理更新示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 预处理更新数据 func prepareUpdate() { sqlStr := "UPDATE user SET age = ? WHERE id = ?" stmt, err := db.Prepare(sqlStr) if err != nil { fmt.Printf("prepare sql failed, err:%v\n", err) return } _, err = stmt.Exec(18, 2) if err != nil { fmt.Printf("exec failed, err:%v\n", err) return } fmt.Printf("prepare update data success") } 预处理插入示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 预处理更新数据 func prepareUpdate() { sqlStr := "UPDATE user SET age = ? WHERE id = ?" stmt, err := db.Prepare(sqlStr) if err != nil { fmt.Printf("prepare sql failed, err:%v\n", err) return } _, err = stmt.Exec(18, 2) if err != nil { fmt.Printf("exec failed, err:%v\n", err) return } fmt.Printf("prepare update data success") } 预处理删除示例 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 预处理删除数据 func prepareDelete() { sqlStr := "DELETE FROM user WHERE id = ?" stmt, err := db.Prepare(sqlStr) if err != nil { fmt.Printf("prepare sql failed, err:%v\n", err) return } result, err := stmt.Exec(3) n, err := result.RowsAffected() if err != nil { fmt.Printf("delete rows failed, err:%v\n", err) return } if n > 0 { fmt.Printf("delete data success") } else { fmt.Printf("delete data error") } } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
Go 语言操作 MySQL 之 事务操作
事务 数据库事务( transaction )是访问并可能操作各种数据项的一个数据库操作序列,这些操作要么全部执行,要么全部不执行,是一个不可分割的工作单位。事务由事务开始与事务结束之间执行的全部数据库操作组成。 MySQL 存储引擎分类有 MyISAM、InnoDB、Memory、Merge等,但是其中最为常用的就是 MyISAM 和 InnoDB 两个引擎,这两个引擎中,支持事务的引擎就是 Innodb(MySQL 默认引擎),在创建数据库中要注意对应引擎。 这里可以看一下针对 MySQL 选择引擎的文章: 怎么优雅的选择 MySQL 存储引擎 事务 ACID 通常事务必须满足4个条件( ACID ):原子性(Atomicity,或称不可分割性)、一致性(Consistency)、隔离性(Isolation,又称独立性)、持久性(Durability)。 条件 解释 原子性 一个事务(transaction)中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。 一致性 在事务开始之前和事务结束以后,数据库的完整性没有被破坏。这表示写入的资料必须完全符合所有的预设规则,这包含资料的精确度、串联性以及后续数据库可以自发性地完成预定的工作。 隔离性 数据库允许多个并发事务同时对其数据进行读写和修改的能力,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。事务隔离分为不同级别,包括读未提交(Read uncommitted)、读提交(read committed)、可重复读(repeatable read)和串行化(Serializable)。 持久性 事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。 Go 操作 MySQL 使用事务 Go语言中使用以下三个方法实现MySQL中的事务操作: 1 2 3 4 5 6 // 开始事务 func (db *DB) Begin() (*Tx, error) // 回滚事务 func (tx *Tx) Rollback() error // 提交事务 func (tx *Tx) Commit() error 示例代码: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 // 事务更新操作 func transActionUpdate() { tx, err := db.Begin() if err != nil { if tx != nil { _ = tx.Rollback() } fmt.Printf("begin trans action failed, err:%v\n", err) return } sqlStr1 := "UPDATE user SET age = ? WHERE id = ?" result1, err := tx.Exec(sqlStr1, 20, 1) if err != nil { _ = tx.Rollback() fmt.Printf("exec failed, err:%v\n", err) return } n1, err := result1.RowsAffected() if err != nil { _ = tx.Rollback() fmt.Printf("exec result1.RowsAffected() failed, err:%v\n", err) return } sqlStr2 := "UPDATE user SET age = ? WHERE id = ?" result2, err := tx.Exec(sqlStr2, 20, 6) if err != nil { _ = tx.Rollback() fmt.Printf("exec failed, err:%v\n", err) return } n2, err := result2.RowsAffected() if err != nil { _ = tx.Rollback() fmt.Printf("exec result1.RowsAffected() failed, err:%v\n", err) return } if n1 == 1 && n2 == 1 { _ = tx.Commit() fmt.Printf("transaction commit success\n") } else { _ = tx.Rollback() fmt.Printf("transaction commit error, rollback\n") return } } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
Go 语言操作 MySQL 之 CURD 操作
MySQL 是目前开发中最常见的关系型数据库,使用 Go 语言进行操控数据库需要使用 Go 自带database/sql和驱动go-sql-driver/mysql来实现, 创建好 Go 项目,需要引用驱动依赖: 1 go get -u github.com/go-sql-driver/mysql 使用 MySQL 驱动: 1 func Open(driverName, dataSourceName string) (*DB, error) Open 打开一个 dirverName 指定的数据库,dataSourceName 指定数据源,一般至少包括数据库文件名和其它连接必要的信息。 初始化连接 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 var db *sql.DB //声明一个全局的 db 变量 // 初始化 MySQL 函数 func initMySQL() (err error) { dsn := "root:password@tcp(127.0.0.1:3306)/dbname" db, err = sql.Open("mysql", dsn) if err != nil { return } err = db.Ping() if err != nil { return } return } func main() { // 初始化 MySQL err := initMySQL() if err != nil { panic(err) } defer db.Close() } 初始化连接 MySQL 后需要借助 db.Ping 函数来判断连接是否成功。 SetMaxOpenConns 1 func (db *DB) SetMaxOpenConns(n int) SetMaxOpenConns设置与数据库建立连接的最大数目。 如果 n 大于 0 且小于最大闲置连接数,会将最大闲置连接数减小到匹配最大开启连接数的限制。 如果 n <= 0,不会限制最大开启连接数,默认为0(无限制)。 SetMaxIdleConns 1 func (db *DB) SetMaxIdleConns(n int) SetMaxIdleConns设置连接池中的最大闲置连接数。 如果 n 大于最大开启连接数,则新的最大闲置连接数会减小到匹配最大开启连接数的限制。 如果 n <= 0,不会保留闲置连接。 CURD 进行 CURD 操作,需要对数据库建立连接,同时有供操作的数据(数据库与数据表): 初始化数据 建立数据库 sql_demo 1 2 CREATE DATABASE sql_demo; USE sql_demo; 创建数据表 user 1 2 3 4 5 6 CREATE TABLE `user` ( `id` BIGINT(20) NOT NULL AUTO_INCREMENT, `name` VARCHAR(20) DEFAULT '', `age` INT(11) DEFAULT '0', PRIMARY KEY(`id`) )ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=utf8mb4; 查询数据 SELECT 便于接收数据,定义一个 user 结构体接收数据: 1 2 3 4 5 type user struct { id int age int name string } 查询一行数据 db.QueryRow() 执行一次查询,并期望返回最多一行结果(即 Row )。QueryRow 总是返回非 nil 的值,直到返回值的 Scan 方法被调用时,才会返回被延迟的错误。(如:未找到结果) 1 func (db *DB) QueryRow(query string, args ...interface{}) *Row 实例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 // 查询一行数据 func queryRowDemo() (u1 *user, err error) { // 声明查询语句 sqlStr := "SELECT id,name,age FROM user WHERE id = ?" // 声明一个 user 类型的变量 var u user // 执行查询并且扫描至 u err = db.QueryRow(sqlStr, 1).Scan(&u.id, &u.age, &u.name) if err != nil { return nil, err } u1 = &u return } func main() { // 初始化 MySQL err := initMySQL() if err != nil { panic(err) } defer db.Close() u1, err := queryRowDemo() if err != nil { fmt.Printf("err:%s", err) } fmt.Printf("id:%d, age:%d, name:%s\n", u1.id, u1.age, u1.name) } 结果如下: 1 id:1, age:111, name:22 多行查询 db.Query()执行一次查询,返回多行结果(即 Rows ),一般用于执行 select 命令。参数 args 表示 query 中的占位参数。 1 func (db *DB) Query(query string, args ...interface{}) (*Rows, error) 实例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 // 查询多行数据 func queryMultiRowDemo() { sqlStr := "SELECT id,name,age FROM user WHERE id > ?" rows, err := db.Query(sqlStr, 0) if err != nil { fmt.Printf("query data failed,err:%s\n", err) return } // 查询完数据后需要进行关闭数据库链接 defer rows.Close() for rows.Next() { var u user err := rows.Scan(&u.id, &u.age, &u.name) if err != nil { fmt.Printf("scan data failed, err:%v\n", err) return } fmt.Printf("id:%d name:%s age:%d\n", u.id, u.name, u.age) } } 执行结果: 1 2 id:1 name:111 age:22 id:3 name:张三 age:22 使用 rows.Next() 循环读取结果集中的数据。 增加数据 INSERT 增加、删除、更新操作均使用 Exec 方法。 1 func (db *DB) Exec(query string, args ...interface{}) (Result, error) 实例代码如下: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 增加一行数据 func insertRowDemo() { sqlStr := "INSERT INTO user(name, age) VALUES(?, ?)" result, err := db.Exec(sqlStr, "小羽", 22) if err != nil { fmt.Printf("insert data failed, err:%v\n", err) return } id, err := result.LastInsertId() if err != nil { fmt.Printf("get insert lastInsertId failed, err:%v\n", err) return } fmt.Printf("insert success, id:%d\n", id) } 执行结果: 1 insert success, id:4 更新数据 UPDATE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 更新一组数据 func updateRowDemo() { sqlStr := "UPDATE user SET age = ? WHERE id = ?" result, err := db.Exec(sqlStr, 22, 1) if err != nil { fmt.Printf("update data failed, err:%v\n", err) return } n, err := result.RowsAffected() if err != nil { fmt.Printf("get rowsaffected failed, err:%v\n", err) return } fmt.Printf("update success, affected rows:%d\n", n) } 删除数据 DELETE 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 // 删除一行数据 func deleteRowDemo() { sqlStr := "DELETE FROM user WHERE id = ?" result, err := db.Exec(sqlStr, 2) if err != nil { fmt.Printf("delete data failed, err:%d\n", err) return } n, err := result.RowsAffected() if err != nil { fmt.Printf("get affected failed, err:%v\n", err) return } fmt.Printf("delete success, affected rows:%d\n", n) } 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
阿川
入驻第1年
今天我去钓鱼了
Debug
入驻第1年
Go 语言基础 数组、切片、映射
在 Go 语言中,为便于存储及管理用户数据,其数据结构设计分为数组 Array、切片 Slice、映射 Map 三种结构。 近期又看了 Go 语言基础的内容,看了一下这三种结构实现的原理: 数组 Array 数组是切片和映射的基础数据结构; 数组是长度固定的数据类型并且在内存中也是连续分配的,固索引数组数据速度是非常快的; 声明数组时需要指定数组存储的类型及数量(数组的长度); 数组变量的类型包括数组长度和元素的类型,只有两部分都相同的数组才可相互赋值。 创建及初始化 一旦声明了数组,其本身的数据类型及长度都是不可以进行变更。 1 2 3 4 5 6 7 8 9 // 使用数组字面量声明数组 array := [5]int{1, 2, 3, 4, 5} // 自动推导长度声明数组 array := [...]int{1, 2, 3, 4, 5, 6} // 使用 ... 代替长度,根据初始化元素个数推导 // 声明数组并指定特定元素值 array := [5]int{1:10, 2:20} 指针类型 数组元素的类型可以为任何内置类型,也可以是某种结构类型,也可以是指针类型。 1 2 3 4 5 6 7 // 声明一个元素长度为 3 的指向字符串的指针数组 var array1 [3]*string // 为指针数组指定元素 *array1[0] = "demo0" *array1[1] = "demo1" *array1[2] = "demo2" 多维数组 数组本身是一维数据,多维数组是由多个数组组合而来的。 1 2 3 4 5 6 // 声明一个二维数组 var array = [3][2]int // 声明了一个两个维度为 3 和 2 的元素 // 初始化二维数组 var array = [3][2]int{ {1, 2}, {3, 4}, {5, 6}} 在函数间传递数组:由于在函数间传递变量时,传递的总是变量的值的副本,所以在传递数组变量时将拷贝整个数组!在定义函数时,对于较大的数据类型应该把参数设计为指针类型,这样在调用函数时,只需在栈上分配给每个指针8字节的内存,但这意味着会改变指针指向的值(共享的内存),其实大部分情况下应该使用切片类型,而不是数组。 切片 Slice 切片 slice 是引用类型,它引用了其指针字段所指向的底层数组的一部分或全部; 切片是围绕动态数组的概念构建的; 切片的动态增长是通过 append 来实现的; 缩小则是通过对它再次切片来实现,通过再次切片获得的新切片将和原切片共享底层数组,它们的指针指向同一个底层数组。 创建及初始化 切片类型有3个字段: 指针:指向切片所包含的第一个元素在底层数组中的地址; 长度:切片所包含的底层数组的元素的个数(切片可访问的元素的个数); 容量:切片允许增长到的最大元素个数,即底层数组的长度。 make 和切片字面量 1 2 3 4 5 6 // 使用 make 创建一个切片 slice := make([]int, 3) // 创建一个具有长度和容量的切片 slice := make([]int, 1, 6) // 长度为 1,容量为 6 个元素 nil 和空切片 1 2 3 4 5 6 // nil 字符串切片 var slice []string // 空切片 slice := []int{} // 空的整形切片 由于切片只是引用了底层数组,底层数组的数据并不属于切片本身,所以一个切片只需要 24字节的内存(在 64位机器上):指针字段 8字节、长度字段 8字节、容量字段 8字节。所以在函数之间直接传递切片是高效的,只需分配 24字节的栈内存。 len函数可返还切片的长度、cap函数可返还切片的容量。 映射 Map 映射 map 是用来存储一系列的无序键值对; 映射是无序的集合,其实现使用了散列表; 映射的散列表包含一组桶,每个桶里存储着一部分键值对; 映射内部使用了两个数组: 第一个数组:存储着用于选择桶的散列键的高八位值,该数组用于区分每个键值对要存在哪个桶里; 第二个数组:每个桶里都有一个字节数组,先依次存储了该桶里的所有键,之后存储了该桶的所有值; 创建及初始化 1 2 3 4 5 6 7 8 9 10 11 12 // 创建一个映射 存储学生信息 students := map[string]string{ "name" : "mengxiaoyu", "age" : "22", "sex" : "boy", "hobby": "pingpang", } // 显示映射所有信息 for key, value := range students{ fmt.printf("key:%s, \t value:%s\n", key, value); } 遍历映射的键值对时的顺序是随机,若要有序的获得映射的键值对,则需要先遍历出映射的键存到一个切片中,然后排序该切片,最后遍历该切片,按切片中元素的顺序去映射中取对应的值。 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
Go 语言使用 net 包实现 Socket 网络编程
TCP/IP TCP/IP 传输协议,即传输控制/网络协议,也叫作网络通讯协议。它是在网络的使用中的最基本的通信协议。TCP/IP 传输协议对互联网中各部分进行通信的标准和方法进行了规定。并且,TCP/IP 传输协议是保证网络数据信息及时、完整传输的两个重要的协议。TCP/IP 传输协议是严格来说是一个四层的体系结构,应用层、传输层、网络层和数据链路层都包含其中。 TCP/IP 协议簇常见通信协议 应用层:TFTP,HTTP,SNMP,FTP,SMTP,DNS,Telnet 等等 传输层:TCP,UDP 网络层:IP,ICMP,OSPF,EIGRP,IGMP 数据链路层:SLIP,CSLIP,PPP,MTU Socket 两个进程如果需要进行通讯最基本的一个前提能能够唯一的标示一个进程,在本地进程通讯中我们可以使用 PID 来唯一标示一个进程,但 PID 只在本地唯一,网络中的两个进程 PID 冲突几率很大,这时候我们需要另辟它径了,我们知道 IP 层的 ip 地址可以唯一标示主机,而 TCP 层协议和端口号可以唯一标示主机的一个进程,这样我们可以利用 ip 地址+协议+端口号唯一标示网络中的一个进程。 能够唯一标示网络中的进程后,它们就可以利用 socket 进行通信了,什么是socket 呢?我们经常把 socket 翻译为套接字,socket 是在应用层和传输层之间的一个抽象层,它把 TCP/IP 层复杂的操作抽象为几个简单的接口供应用层调用已实现进程在网络中通信。 socket是一种"打开—读/写—关闭"模式的实现,服务器和客户端各自维护一个"文件",在建立连接打开后,可以向自己文件写入内容供对方读取或者读取对方内容,通讯结束时关闭文件。 Socket 是实现“打开–读/写–关闭”这样的模式,以使用 TCP 协议通讯的 socket 为例。如下图所示: TCP 实现 一个 TCP 客户端进行 TCP 通信的流程如下: 建立与服务端的链接 进行数据收发 关闭链接 server 端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 package main import ( "bufio" "fmt" "net" ) func process(conn net.Conn) { // 处理完关闭连接 defer conn.Close() // 针对当前连接做发送和接受操作 for { reader := bufio.NewReader(conn) var buf [128]byte n, err := reader.Read(buf[:]) if err != nil { fmt.Printf("read from conn failed, err:%v\n", err) break } recv := string(buf[:n]) fmt.Printf("收到的数据:%v\n", recv) // 将接受到的数据返回给客户端 _, err = conn.Write([]byte("ok")) if err != nil { fmt.Printf("write from conn failed, err:%v\n", err) break } } } func main() { // 建立 tcp 服务 listen, err := net.Listen("tcp", "127.0.0.1:9090") if err != nil { fmt.Printf("listen failed, err:%v\n", err) return } for { // 等待客户端建立连接 conn, err := listen.Accept() if err != nil { fmt.Printf("accept failed, err:%v\n", err) continue } // 启动一个单独的 goroutine 去处理连接 go process(conn) } } client 端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package main import ( "bufio" "fmt" "net" "os" "strings" ) func main() { // 1、与服务端建立连接 conn, err := net.Dial("tcp", "127.0.0.1:9090") if err != nil { fmt.Printf("conn server failed, err:%v\n", err) return } // 2、使用 conn 连接进行数据的发送和接收 input := bufio.NewReader(os.Stdin) for { s, _ := input.ReadString('\n') s = strings.TrimSpace(s) if strings.ToUpper(s) == "Q" { return } _, err = conn.Write([]byte(s)) if err != nil { fmt.Printf("send failed, err:%v\n", err) return } // 从服务端接收回复消息 var buf [1024]byte n, err := conn.Read(buf[:]) if err != nil { fmt.Printf("read failed:%v\n", err) return } fmt.Printf("收到服务端回复:%v\n", string(buf[:n])) } } UDP 实现 UDP 协议(User Datagram Protocol)中文名称是用户数据报协议,是OSI(Open System Interconnection,开放式系统互联)参考模型中一种无连接的传输层协议,不需要建立连接就能直接进行数据发送和接收,属于不可靠的、没有时序的通信,但是UDP协议的实时性比较好,通常用于视频直播相关领域。 server 端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 package main import ( "fmt" "net" ) func main() { // 建立 udp 服务器 listen, err := net.ListenUDP("udp", &net.UDPAddr{ IP: net.IPv4(0, 0, 0, 0), Port: 9090, }) if err != nil { fmt.Printf("listen failed error:%v\n", err) return } defer listen.Close() // 使用完关闭服务 for { // 接收数据 var data [1024]byte n, addr, err := listen.ReadFromUDP(data[:]) if err != nil { fmt.Printf("read data error:%v\n", err) return } fmt.Printf("addr:%v\t count:%v\t data:%v\n", addr, n, string(data[:n])) // 发送数据 _, err = listen.WriteToUDP(data[:n], addr) if err != nil { fmt.Printf("send data error:%v\n", err) return } } } client 端 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package main import ( "fmt" "net" ) func main() { // 建立服务 listen, err := net.DialUDP("udp", nil, &net.UDPAddr{ IP: net.IPv4(0, 0, 0, 0), Port: 9090, }) if err != nil { fmt.Printf("listen udp server error:%v\n", err) } defer listen.Close() // 发送数据 sendData := []byte("Hello server") _, err = listen.Write(sendData) // 发送数据 if err != nil { fmt.Println("发送数据失败,err:", err) return } // 接收数据 data := make([]byte, 4096) n, remoteAddr, err := listen.ReadFromUDP(data) // 接收数据 if err != nil { fmt.Println("接收数据失败,err:", err) return } fmt.Printf("recv:%v addr:%v count:%v\n", string(data[:n]), remoteAddr, n) } 参考文章 Go语言基础之网络编程 - 李文周的个人博客 简单理解Socket - 谦行 - 博客园 TCP/IP协议 - 百度百科 详解TCP连接的“三次握手”与“四次挥手”(下) 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页
Debug
入驻第1年
Linux Vim 命令手记
经常使用 Linux 的同学在编辑文本文件的时候一定知道 Vim 这一款神器,它代替 Linux 默认原装的 Vi 编辑器,它的快捷键可以使你在操控文件的时候如庖丁解牛般流畅,博主目前只会简单的命令,感觉不能满足开发需求,今天特地的学习了一下,并且针对于常用的命令做了整理及汇总: 开源项目 首先,Vim 编辑器是一个开源的项目,按照惯例,请给开发者一个 Star 奖励: https://github.com/vim/vim 常用命令示意图 常用命令参考 快捷键 操作说明 Ctrl + f 屏幕向下移动一页,类似 Page Down 按键 Ctrl + b 屏幕向上移动一页,类似 Page Up 按键 0 或 Home 键 移动到这一行最前面的字符处 $ 或 End 键 移动到这一行最后面的字符处 G 移动到这个文件的最后一行 gg 移动到这个文件的第一行,相当于 1G N[Enter] N 为数字。光标向下移动 N 行 /word 向下寻找一个名称为 word 的字符串 ?word 向上寻找一个名称为 word 的字符串 n 搭配查找 word 字符串使用,代表重复前一个查找的操作。例:如果前一个命令执行了 /word 命令去向下查找 word 这个字符串,当按下 n 后,会继续向下查找 word 这个字符串。 N 搭配查找 word 字符串使用,代表重复前一个查找的操作(反向)。 :n1,n2s/word1/word2/g 将此文本中的 word1字符串 替换为 word2 字符串 :1,$s/word1/word2/gc 将此文本中的 word1字符串 替换为 word2 字符串【给用户 confim提示】 x, X 在一行字符中,x为向后删除一个字符,X为向前删除一个字符 dd 删除光标所在那一行 ndd n为数字,删除光标所在向下n行 yy 复制光标所在那一行 nyy n为数字,复制光标所在向下n行 p, P p将已经复制的数据在光标下一行粘贴 P将已经复制的数据在光标上一行粘贴 u 复原前一个操作 Ctrl + r 重做上一个操作 . 重复上一个操作 模式切换 快捷键 操作说明 i, I 进入插入模式(Insert mode): i为目前光标所在处插入,I为在目前行所在的第一个非空格符处插入。 a, A 进入插入模式(Insert mode): a为目前光标的下一个字符处插入,A为在目前行所在的最后一个字符处开始插入。 o,O 进入插入模式(Insert mode): o为在目前光标所在下一行插入一个新行,O为在目前光标所在上一行插入一个新行。 r,R 进入替换模式(Replace mode):r 只会替换光标所在的那一个字符一次,R 会替换光标所在的文字,直到按下 [esc] 键。 [esc] 退出编辑模式 基础操作 快捷键 操作说明 快捷键 操作说明 :w 将编辑的文件写入磁盘文件中去。 :q! 强制退出编辑,且不保存操作。 :q 退出编辑,进入到命令行模式中去。 :wq 保存且退出编辑。 :wq! 强制保存且退出编辑。 Vim 环境修改。 :set nu 显示行号,设置后会在没有行前面前缀对应行号。 :set nonu 与:set nu相反,取消行号显示 键盘标识 关注微信公众号,第一时间获取最新内容,让我们一起变得更强!Debug客栈:订阅本站· 文章归档· 我的项目· 友情链接· 我的使用· 飞湾计划· 摄影展集· 我的主页

© 2026 好站网HaoZhan.wang 1.5 版权所有

苏ICP备19065220号-4    萌ICP备20269980号    茶ICP备2026050346号
本站数据    2026年报    版本历史    关于本站