Go基础语法| 青训营笔记

136 阅读10分钟

这是我参与「第三届青训营 -后端场」笔记创作活动的第1篇笔记。

1简介

1.1 Go语言特性

1.高性能、高并发    2.语法简单、学习曲线平缓  3.丰富的标准库

4.完善的工具链    5.静态链接     6.快速编译  7.跨平台  8.垃圾回收

1.2 使用Go的公司

      字节跳动、腾讯、美团、滴滴、百度、B站、谷歌、脸书

1.3 字节跳动选择Go的原因

      1.性能问题  2.C++局限  3.早期团队背景 4.部暑简单、学习成本低

5.内部 RPC和HTTP框架的推广

 

2入门

2.1 开发环境

      1.安装Golang   2.配置集成开发环境      3.基于云的开发环境

2.2 变量

      常见的变量类型:字符串、整型、浮点型、布尔型等。

字符串是内置类型,可以直接通过加号拼接,也能够直接用等于号去比较两个字符串。

变量的声明有两种方式,一种是通过var name sting=""这种方式来声明变量,声明变量的时候,一般会自动去推导变量的类型,有需要,你也可以显示写出变量类型。另一种声明变量的方式是使用变量冒号:=等于值。

常量的话就是把var改成const。go语言里面的常量没有确定的类型,会根据使用的上下文来自动确定类型。

2.3 条件判断

      1.if后面没有括号 2.后面必须接大括号

2.4 循环

      只有唯一的for循环

2.5 switch

      不需要加break

2.6 数组

      var a [5] int

      b := [5] int {1,2,3,4,5}

      var twoD [2][3] int

2.7 切片

      s = append(s,”d”)

2.8 map

      无序

2.9 range

      快速遍历slice和map

2.10 函数

      1.变量类型后置

      2.函数返回两个值,第一个是运算结果,第二个是错误信息

2.11 指针

      主要用途是对传入参数进行修改

2.12 结构体

      带类型的字段的集合

2.13 结构体方法

      1.带指针,可以对结构体进行修改   2.不带指针,仅仅做拷贝

2.14 错误处理

      如果出现错误,可以return nil和一个error

2.15 字符串操作

      1.contains判断一个字符串里面是否有包含另一个字符串

2.count字符串计数       3. index查找某个字符串的位置

4.join连接多个字符串   5.repeat重复多个字符串     6.replace替换字符串

2.16 字符串格式化

      1.%v打印任意类型的变量    2.%+v打印详细结果      3.%#v更详细

2.17 JSON处理

      1.JSON.marshaler序列化     2.JSON.unmarshaler反序列化

2.18 时间处理

      1.time.now()来获取当前时间,用time.date去构造一个带时区的时间。

2.用sub去对两个时间进行减法,得到一个时间段。用.UNIX来获取时间戳。

2.19 数字解析

      关于字符串和数字类型之间的转换都在STR conv这个包下,这个包是string convert这两个单词的缩写。用parselnt 或者parseFloat来解析一个字符串,用Atoi把一个十进制字符串转成数字,用itoA把数字转成字符串。如果输入不合法,那么这些函数都会返回error

2.20 进程信息

      1.用os.args来得到程序执行的时候的指定的命令行参数。

2.用os.getenv来读取环境变量。

 

3实战

3.1 猜谜游戏

      1.time.now.unix初始化随机种子

      2.math/rand包中的rand.Intn生成随机数

      3.bufio.NewReader把一个文件转换成一个reader变量,用reader.ReadString读取一行。

      优化:_, err := fmt.Scanf("%d", &guess)

      4.实现判断逻辑

      5.实现游戏循环

3.2 在线词典

      1.抓包:检查->Network->Preview

      2.代码生成:Headers->Copy as cURL->curlconverter.com

      3.生成request body:JSON序列化

      4.解析response body:JSON反序列化->oktools.net

      5.打印结果

      6.完善代码:把代码的主体改成一个query 函数,查询的单词作为参数传递进来。然后写一个简单main 函数,这个main 函数首先判断一下命令和参数的个数,如果它不是两个,那么我们就打印出错误信息,退出程序。否则就获取到用户输入的单词,然后执行query函数。

3.3 SOCKS5代理

3.3.1介绍

      1.虽然是代理协议,但它并不能用来翻墙,它的协议都是明文传输。

2.用途:比如企业内网为了确保安全性,有很严格的防火墙策略,但是带来的副作用就是访问某些资源会很麻烦。socks5相当于在防火墙开了个口子,让授权的用户可以通过单个端口去访问内部的所有资源。实际上很多翻墙软件,最终暴露的也是一个socks5协议的端口。

3.爬虫在爬取过程中很容易会遇到IP访问频率超过限制。这个时候很多人就会去网上找一些代理IP池,这些代理IP池里面的很多代理的协议就是socks5。

3.3.2原理

正常浏览器访问一个网站,如果不经过代理服务器的话,就是先和对方的网站建立TCP连接,然后三次握手,握手完之后发起HTTP请求,然后服务返回HTTP响应。如果设置代理服务器之后,流程会变得复杂一些。首先是浏览器和socks5代理建立TCP连接,代理再和真正的服务器建立TCP连接。这里可以分成四个阶段,握手阶段、认证阶段、请求阶段、relay 阶段。

第一个握手阶段,浏览器会向socks5代理发送清求,包的内容包括一个协议的版本号,还有支持的认证的种类,socks5服务器会选中一个认证方式,返回给浏览器,如果返回的是00的话就代表不需要认证,返回其他类型的话会开始认证流程,这里我们就不对认证流程进行概述了。

第三个阶段是请求阶段,认证通过之后浏览器会socks5服务器发起请求。主要信息包括版本号,请求的类型主要是 connection请求,就代表代理服务器要和某个域名或者某个IP地址某个端口建立TCP连接。代理服务器收到响应之后,会真正和后端服务器建立连接,然后返回一个响应。

第四个阶段是relay 阶段。此时浏览器会发送正常发送请求,然后代理服务器接收到请求之后,会直接把请求转换到真正的服务器上。然后如果真正的服务器以后返回前应的话,那么也会部请求转发到浏览器这边,然后实际上代理服务器并不关心流量的细节,可以是HTTP流量,也可以是其它TCP流量。

3.3.3 实现

      1.TCP echo server:

首先在main函数里面先用net.listen去监听一个端口,会返回一个server,然后在一个死循环里面,每次去accept一个请求,成功就会返回一个连接。接下来在一个process函数里面去处理这个连接。这前面会有个go关键字,这个代表启动一个goroutinue,开销会比子线程要小很多。

接下来是process函数的实现。首先第一步的话会先加一个defer connection.close(),代表在这个函数退出的时候要把这个连接关掉,否则会有资源的泄露。接下来用 bufio.NewReader来创建一个带缓冲的只读流,减少底层系统调用的次数,并且带缓冲的流会有更多的工具函数用来读取数据。简单地调用readbyte 函数来读取单个字节,再把这一个字节写进去连接。

      接下来实现协议的第一步,认证阶段。实现一个空的auth 函数,在process 函数里面调用。第一步浏览器会给代理服务器发送一个包,这个包有三个字段,第一个字段version也就是协议版本号,固定是5。第二个字段methods,认证的方法数目。第三个字段每个method的编码,0代表不需要认证,2代表用户名密码认证。先用readbytes把版本号读出来,如果版本号不是socks5的话返回报错,接下来再读取method size,也是一个字节。然后去make一个相应长度的一个slice,用io.ReadFull把它去填充进去。此时,代理服务器还需要返回一个response,返回包括两个字段,一个是version,一个是method,也就是我们选中的鉴传方式,当前只准备实现不需要鉴传的方式,也就是00。

      接下来实现请求阶段,读取携带URL或者IP地址+端口的包,然后把它打印出来。我们实现一个和auth 函数类似的connect 函数,同样在process里面去调用。再来实现connect 函数的代码。浏览器会发送一个包,包里面包含如下6个字段,version版本号,还是5;command代表请求的类型,我们只支持connection请求,也就是让代理服务建立新的TCP连接;RSV保留字段,不理会;atype目标地址类型,可能是IPV4、IPV 6或者域名下面是addr,这个地址的长度是根据 atype的类型而不同的;port端口号,两个字节。这四个字段总共四个字节,我们可以一次性把它读出来。我们定义一个长度为4的 buffer然后把它读满。读满之后,然后第0个.第1个、第3个、分别是version、cmd和type,version需要判断是socket 5,cmd需要判断是1。atype可能是ipv4,ipv6,或者是host。如果IPV4的话,我们再次读满这个buffer,因为这个buffer长度刚好也是4个字节,然后逐个字节打印成IP地址的格式保存到addr变量。如果是个host的话,需要先读它的长度,再make一个相应长度的buf填充它。再转换成字符串保存到addr变量。IPV 6用得比较少,我们就暂时先不支持。最后还有两个字节那个是port,我们读取它,然后按协议规定的大端字节序转换成数字。由于上面的buffer已经不会被其他变量使用了,我们可以直接复用之前的内存,建立一个临时的slice,长度是2用于读取,这样的话最多会只读两个字节回来。接下来把这个地址和端口打印出来用于调试。收到浏览器的这个请求包之后,需要返回一个包,这个包有很多字段,但其实大部分都不会使用。第一个是版本号还是socks5。第二个,就是返回的类型,这里是成功就返回0。第三个是保留字段填0。第四个atype 地址类型填1。第五个,第六个暂时用不到,都填成0。一共4+4 +2个字节,后面6个字节都是0填充。

      用net.dial建立一个TCP连接,加一个defer来关闭连接。建立浏览器和下游服务器的双向数据转发。标准库的 io.copy可以实现一个单向数据转发,双向转发的话,需要启动两个goroutinue。connect 函数会立刻返回,返回的时候连接就被关闭了。需要等待任意一个方向copy出错的时候,再返回connect函数。这里使用标准库里面的一个context机制,用context连 with cancel来创建一个context。在最后等待ctx.Done(),只要cancel 被调用, ctx.Done就会立刻返回。然后在上面的两个goroutinue里面调用一次cancel。

      在浏览器里面再测试一下,安装switchomega插件,然后里面新建一个情景模式,代理服务器选socks5,端口1080,保存并启用。此时应该还能够正常地访问网站,代理服务器这边会显示出浏览器版本的域名和端口。