bilibili学习

Go note

melody

"github.com/gorilla/websocket"

WebSocket解释:

WebSocket: 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。 在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。

**Ajax:**很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。

HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯。

​ ----- 菜鸟教程

// Upgrader specifies parameters for upgrading an HTTP connection to a
// WebSocket connection.
type Upgrader struct {
	// HandshakeTimeout specifies the duration for the handshake to complete.
	HandshakeTimeout time.Duration

	// ReadBufferSize and WriteBufferSize specify I/O buffer sizes in bytes. If a buffer
	// size is zero, then buffers allocated by the HTTP server are used. The
	// I/O buffer sizes do not limit the size of the messages that can be sent
	// or received.
	ReadBufferSize, WriteBufferSize int

	// WriteBufferPool is a pool of buffers for write operations. If the value
	// is not set, then write buffers are allocated to the connection for the
	// lifetime of the connection.
	//
	// A pool is most useful when the application has a modest volume of writes
	// across a large number of connections.
	//
	// Applications should use a single pool for each unique value of
	// WriteBufferSize.
	WriteBufferPool BufferPool

	// Subprotocols specifies the server's supported protocols in order of
	// preference. If this field is not nil, then the Upgrade method negotiates a
	// subprotocol by selecting the first match in this list with a protocol
	// requested by the client. If there's no match, then no protocol is
	// negotiated (the Sec-Websocket-Protocol header is not included in the
	// handshake response).
	Subprotocols []string

	// Error specifies the function for generating HTTP error responses. If Error
	// is nil, then http.Error is used to generate the HTTP response.
	Error func(w http.ResponseWriter, r *http.Request, status int, reason error)

	// CheckOrigin returns true if the request Origin header is acceptable. If
	// CheckOrigin is nil, then a safe default is used: return false if the
	// Origin request header is present and the origin host is not equal to
	// request Host header.
	//
	// A CheckOrigin function should carefully validate the request origin to
	// prevent cross-site request forgery.
	CheckOrigin func(r *http.Request) bool

	// EnableCompression specify if the server should attempt to negotiate per
	// message compression (RFC 7692). Setting this value to true does not
	// guarantee that compression will be supported. Currently only "no context
	// takeover" modes are supported.
	EnableCompression bool
}

// The Conn type represents a WebSocket connection.
type Conn struct {
	conn        net.Conn
	isServer    bool
	subprotocol string

	// Write fields
	mu            chan struct{} // used as mutex to protect write to conn
	writeBuf      []byte        // frame is constructed in this buffer.
	writePool     BufferPool
	writeBufSize  int
	writeDeadline time.Time
	writer        io.WriteCloser // the current writer returned to the application
	isWriting     bool           // for best-effort concurrent write detection

	writeErrMu sync.Mutex
	writeErr   error

	enableWriteCompression bool
	compressionLevel       int
	newCompressionWriter   func(io.WriteCloser, int) io.WriteCloser

	// Read fields
	reader  io.ReadCloser // the current reader returned to the application
	readErr error
	br      *bufio.Reader
	// bytes remaining in current frame.
	// set setReadRemaining to safely update this value and prevent overflow
	readRemaining int64
	readFinal     bool  // true the current message has more frames.
	readLength    int64 // Message size.
	readLimit     int64 // Maximum message size.
	readMaskPos   int
	readMaskKey   [4]byte
	handlePong    func(string) error
	handlePing    func(string) error
	handleClose   func(int, string) error
	readErrCount  int
	messageReader *messageReader // the current low-level reader

	readDecompress         bool // whether last read frame had RSV1 set
	newDecompressionReader func(io.Reader) io.ReadCloser
}

// Upgrade upgrades the HTTP server connection to the WebSocket protocol.
func (u *Upgrader) Upgrade(w http.ResponseWriter, r *http.Request, responseHeader http.Header){
    .....
}

// 开发
var upgrader = websocket.Upgrader{
		HandshakeTimeout: 5 * time.Second,
		ReadBufferSize:   1024,
		WriteBufferSize:  1024,
		CheckOrigin:      func(r *http.Request) bool { return true }
}
conn, err := upgrader.Upgrade(w, r, nil)//w http.ResponseWriter, r *http.Request


"gopkg.in/olahol/melody.v1"

解释:封装websocket和session,提供简便操作websocket

//   melody.go
// Melody implements a websocket manager.
type Melody struct {
	Config                   *Config
	Upgrader                 *websocket.Upgrader
	messageHandler           handleMessageFunc
	messageHandlerBinary     handleMessageFunc
	messageSentHandler       handleMessageFunc
	messageSentHandlerBinary handleMessageFunc
	errorHandler             handleErrorFunc
	closeHandler             handleCloseFunc
	connectHandler           handleSessionFunc
	disconnectHandler        handleSessionFunc
	pongHandler              handleSessionFunc
	hub                      *hub
}

// 别名
type handleMessageFunc func(*Session, []byte)  // **
type handleSessionFunc func(*Session)          // **
type handleErrorFunc func(*Session, error)
type handleCloseFunc func(*Session, int, string) error
type filterFunc func(*Session) bool

// New creates a new melody instance with default Upgrader and Config.
func New() *Melody {
	upgrader := &websocket.Upgrader{
		ReadBufferSize:  1024,
		WriteBufferSize: 1024,
		CheckOrigin:     func(r *http.Request) bool { return true },
	}

    //hub实例化并协程启动
	hub := newHub()
	go hub.run()    

	return &Melody{
		Config:                   newConfig(),
		Upgrader:                 upgrader,
		messageHandler:           func(*Session, []byte) {},
		messageHandlerBinary:     func(*Session, []byte) {},
		messageSentHandler:       func(*Session, []byte) {},
		messageSentHandlerBinary: func(*Session, []byte) {},
		errorHandler:             func(*Session, error) {},
		closeHandler:             nil,
		connectHandler:           func(*Session) {},
		disconnectHandler:        func(*Session) {},
		pongHandler:              func(*Session) {},
		hub:                      hub,
	}
}

// HandleRequest upgrades http requests to websocket connections and dispatches them to be handled by the melody instance.
// 升级 http --> websocket连接
func (m *Melody) HandleRequest(w http.ResponseWriter, r *http.Request) error {
	return m.HandleRequestWithKeys(w, r, nil)
}

// HandleRequestWithKeys does the same as HandleRequest but populates session.Keys with keys.
// 实现指定melody.session 建立 websocket连接
func (m *Melody) HandleRequestWithKeys(w http.ResponseWriter, r *http.Request, keys map[string]interface{}) error {
    //1. 判断melody对应的hub是否已经关闭
	if m.hub.closed() {
		return errors.New("melody instance is closed")
	}
    //2. 升级此次http请求
	conn, err := m.Upgrader.Upgrade(w, r, nil)
	if err != nil {
		return err
	}
    //3.创建session实例
	session := &Session{
		Request: r,
		Keys:    keys,
		conn:    conn,
		output:  make(chan *envelope, m.Config.MessageBufferSize),
		melody:  m,
		open:    true,
		rwmutex: &sync.RWMutex{},
	}
    //4. 此次会话session向hub注册
	m.hub.register <- session
	m.connectHandler(session)
    //5. 起协程 session.writePump() 实际是一个for{ select{}}结构阻塞式的从chan session.ouput中读导数据后向websocket.conn写出数据
	go session.writePump()
    // 6. 读websocket.conn中的数据 阻塞
	session.readPump()
    // 7. 此次session关闭,则相应关闭hub
	if !m.hub.closed() {
		m.hub.unregister <- session
	}
	session.close()
    //回调函数  可通过外部设置
	m.disconnectHandler(session)
	return nil
}

// Broadcast broadcasts a text message to all sessions.
// 推送数据
func (m *Melody) Broadcast(msg []byte) error {
	if m.hub.closed() {
		return errors.New("melody instance is closed")
	}

	message := &envelope{t: websocket.TextMessage, msg: msg}
	m.hub.broadcast <- message

	return nil
}


//  config.go
// Config melody configuration struct.
// 设置websocket中时间和大小
type Config struct {
	WriteWait         time.Duration // Milliseconds until write times out.
	PongWait          time.Duration // Timeout for waiting on pong.
	PingPeriod        time.Duration // Milliseconds between pings.
	MaxMessageSize    int64         // Maximum size in bytes of a message.
	MessageBufferSize int           // The max amount of messages that can be in a sessions buffer before it starts dropping them.
}

//   hub.go
// hub control session and message from websocket
//   ** 控制session 和 websocket中的message **
//  一个melody 对应有一个 hub.    
//  协程启动 go hub.run() 
type hub struct {
	sessions   map[*Session]bool
	broadcast  chan *envelope
	register   chan *Session
	unregister chan *Session
	exit       chan *envelope
	open       bool
	rwmutex    *sync.RWMutex
}

func (h *hub) run() {
loop:
	for {
		select {
		case s := <-h.register:
			h.rwmutex.Lock()
			h.sessions[s] = true
			h.rwmutex.Unlock()
		case s := <-h.unregister:
			if _, ok := h.sessions[s]; ok {
				h.rwmutex.Lock()
				delete(h.sessions, s)
				h.rwmutex.Unlock()
			}
		case m := <-h.broadcast:
			h.rwmutex.RLock()
			for s := range h.sessions {
				if m.filter != nil {
					if m.filter(s) {
						s.writeMessage(m)
					}
				} else {
					s.writeMessage(m)
				}
			}
			h.rwmutex.RUnlock()
		case m := <-h.exit:
			h.rwmutex.Lock()
			for s := range h.sessions {
				s.writeMessage(m)
				delete(h.sessions, s)
				s.Close()
			}
			h.open = false
			h.rwmutex.Unlock()
			break loop
		}
	}
}

//  session.go
// 注意: 此处的 session不是net/http概念中的, 是melody自定义的session结构体,他存在意义是对不同请求进行封装. 是实际向*websocket.Conn写数据的玩意. 而melody对实际数据用envelope进行封装..
// melody.Session wrapper around websocket connections.
type Session struct {
	Request *http.Request
	Keys    map[string]interface{}
	conn    *websocket.Conn
	output  chan *envelope
	melody  *Melody
	open    bool
	rwmutex *sync.RWMutex
}

//  envelope.go
// melody封装了chan *envelope
// 作用:传递websocket接收的message, 通知退出
type envelope struct {
	t      int             //计数
	msg    []byte         // message
	filter filterFunc     // 类似 拦截器, 对某些message做点什么事情..
}


NSQ

NSQ目前流行的分布式消息队列

1.介绍

go编写的开元实时分布式内存消息队列,性能强悍。优势:

  • NSQ提倡分布式和分散的拓扑,没有单点故障,支持容错和高可用性,提供可靠的消息交付保证
  • NSQ支持横向扩展,没有任何集中式代理
  • NSQ易配置部署,内置管理界面

2.应用场景

消息队列都适用

  • 异步处理:

    把非关键步骤异步化

    同步:用户注册(写数据库) -> 邮件/短信 -> 响应

    异步:用户注册(写数据库) --> 响应

​ --> 消息队列 : 邮件/短信

  • 应用解耦:

    通过将不同业务逻辑解耦,降低系统间的耦合,提高系统健壮性,后续其他业务也可订阅消息队列,提高灵活性

  • 流量削峰:

    秒杀,某一时间产生大量请求,使用消息队列能够为后台处理提供缓冲区,保证后台服务的稳定性

3.使用

3.1 nsq组件
  • nsqd

​ 守护进程,它接受,排队向客户端发送消息

​ 启动nsqd,指定

  • nsqlookupd

​ 维护所有nsqd状态,提供服务发现的守护进程,

  • nsqadmin

​ 实时监控集群状态,执行各种管理任务的web管理平台。启动nsqadmin,指定nsqdlookupd地址

nsqadmin.exe  -lookupd-tcp-address=127.0.0.1:4160

在这里插入图片描述

3.2启动
nsqlookupd.exe 

默认在本机的127.0.0.1:4160启动

nsqd.exe -broadcast-address=127.0.0.1 -lookupd-tcp-address=127.0.0.1:4160
nsqadmin.exe  -lookupd-tcp-address=127.0.0.1:4160

mysql

1.数据库

关系型数据库

2.知识点

2.1 SQL语句

DDL:操作数据库

DML:表的增删改查

DCL:用户及权限

2.2 存储引擎

MySQL支持插件式的存储引擎

常见存储引擎:MyISAM,InnoDB

myisam:

  1. 查询数据块
  2. 只支持表锁
  3. 不支持事务

innodb:

  1. 整体速度快
  2. 支持表锁和行锁
  3. 支持事务
2.3 事务

​ 多个sql操作当成一个

事务特点:

ACID:

  1. 原子性:要么全部完成,要么全部不完成

  2. 一致性:数据完成性没被破坏

    事务的执行不能破坏数据库数据的完整性和一致性,一个事务在执行之前和执行之后,数据库都必须处于一致性状态。

    如果数据库系统在运行过程中发生故障,有些事务尚未完成就被迫中断,这些未完成的事务对数据库所作的修改有一部分已写入物理数据库,这是数据库就处于一种不正确的状态,也就是不一致的状态

  3. 隔离性:事务之间相互隔离

    事务的隔离性是指在并发环境中,并发的事务时相互隔离的,一个事务的执行不能不被其他事务干扰。

    4个事务隔离级别:未授权读取,授权读取,可重复读取和串行化

    1、读未提交(Read Uncommited)

    ​ 该隔离级别允许脏读取,其隔离级别最低;比如事务A和事务B同时进行,事务A在整个执行阶段,会将某数据的值从1开始一直加到10,然后进行事务提交,此时,事务B能够看到这个数据项在事务A操作过程中的所有中间值(如1变成2,2变成3等),而对这一系列的中间值的读取就是未授权读取

    2、授权读取也称为已提交读(Read Commited)

    ​ 授权读取只允许获取已经提交的数据。比如事务A和事务B同时进行,事务A进行+1操作,此时,事务B无法看到这个数据项在事务A操作过程中的所有中间值,只能看到最终的10。另外,如果说有一个事务C,和事务A进行非常类似的操作,只是事务C是将数据项从10加到20,此时事务B也同样可以读取到20,即授权读取允许不可重复读取。

    3、可重复读(Repeatable Read)

    ​ 就是保证在事务处理过程中,多次读取同一个数据时,其值都和事务开始时刻是一致的,因此该事务级别禁止不可重复读取和脏读取,但是有可能出现幻影数据。所谓幻影数据,就是指同样的事务操作,在前后两个时间段内执行对同一个数据项的读取,可能出现不一致的结果。在上面的例子中,可重复读取隔离级别能够保证事务B在第一次事务操作过程中,始终对数据项读取到1,但是在下一次事务操作中,即使事务B(注意,事务名字虽然相同,但是指的是另一个事务操作)采用同样的查询方式,就可能读取到10或20;

    ​ 注:innodb的级别

    4、串行化

    ​ 是最严格的事务隔离级别,要求所有事务被串行执行,即事务只能一个接一个的进行处理,不能并发执行

  4. 持久性:事务操作的结果不会丢失

    一旦事务提交,那么它对数据库中的对应数据的状态的变更就会永久保存到数据库中。–即使发生系统崩溃或机器宕机等故障,只要数据库能够重新启动,那么一定能够将其恢复到事务成功结束的状态

2.4 索引

原理: B树和B+树

目的:减少IO操作

索引的类型

索引的命中

分库分表

SQL注入

SQL慢查询优化

MySQL主从

MySQL读写分离

3. Go操作MySQL

3.1 使用

database/sql

原生支持连接池,并发安全

这个标准库没有具体实现,只是列出了第三方库需实现的具体内容

"database/sql" // 提供了保证sql或类sql数据库的泛用接口, 没有具体实现,只是做了一个接口规范
//使用sql包必须注入一个数据库驱动


//sqlx的使用: 简化开发代码


3.2 预处理

普通sql语句执行;

  1. 客户端对sql语句进行占位符替换得到完成语句

  2. 客户端发送完整sql到服务端

  3. mysql服务端进行完整sql语句并将结果返回给客户端

预处理:

  1. 把sql分成两部分, 命令部分与数据部分
  2. 先把命令部分发mysql服务端, mysql server对sql预处理
  3. 把数据部分发给mysql server,mysql server 对sql语句占位替换
  4. mysql server 执行完成sql并将结果返回给客户端

why?

1. 优化mysql服务器重复执行sql的方法,提升mysql server性能, 提前让服务器编译,一次编译多次执行
2. 避免sql注入问题

go使用:

4.事务

go使用:

func (db, *DB) Begin() (*Tx, error) // 开始事务
func (tx, *Tx) Commit() error // 提交事务
func (tx, *Tx) Rollback() error // 回滚事务

5.sql注入

出现原因:sql查询语句拼接时出现

解决办法:预处理,让数据库先编译,再传数据

redis

kv数据库,

用处:

  1. cache缓存
  2. 简单队列
  3. 排行榜

开源内存数据库,Redis提供了多种不同类型的数据结构。

1.数据结构

  • 字符串 strings
  • 哈希 hashes

  • 列表 lists

  • 集合 sets

  • 带范围查询的排序集合 sorted sets

  • 位图 bitmaps

  • hyperloglogs

  • 带半径查询和刘的地理空间索引等数据结构

2.应用场景

  • 缓存系统,减轻mysql压力
  • 计数场景,比如微博,抖音中的关注数和粉丝数
  • 热门排行榜,需要排序的场景特别适合使用ZSET
  • 利用list实现队列

3. Redis和memcached

memcached值只支持简单的字符串,Redis支持更丰富的5中数据结构类型。redis性能强,支持RDB和AOF持久化。Redis支持master/slave模式(主从模式)。

4.Go操作Redis

go get -u github.com/go-redis/redis

Go base

1.字符串拼接处理

func plusConcat(n int, str string) string {
	s := ""
	for i := 0; i < n; i++ {
		s += str
	}
	return s
}
  1. fmt.Sprintf
func sprintfConcat(n int, str string) string {
	s := ""
	for i := 0; i < n; i++ {
		s = fmt.Sprintf("%s%s", s, str)
	}
	return s
}
  1. strings.Builder() -------优先选择
func builderConcat(n int, str string) string {
	var builder strings.Builder
	for i := 0; i < n; i++ {
		builder.WriteString(str)
	}
	return builder.String()
}
//改进
func builderConcat(n int, str string) string {
	var builder strings.Builder
	builder.Grow(n * len(str))   // 已知长度情况下
	for i := 0; i < n; i++ {
		builder.WriteString(str)
	}
	return builder.String()
}
  1. bytes.Buffer --------优先选择
func bufferConcat(n int, s string) string {
	buf := new(bytes.Buffer)
	for i := 0; i < n; i++ {
		buf.WriteString(s)
	}
	return buf.String()
}
  1. []byte -------优先选择
func byteConcat(n int, str string) string {
    buf := make([]byte, 0, n*len(str))  // 如果长度已知
    buf := make([]byte, 0)
	for i := 0; i < n; i++ {
		buf = append(buf, str...)
	}
	return string(buf)
}

2.切片

注 : for range遍历时是赋值一个临时变量

3.内置函数

内置函数介绍
close关闭channel
len求长度,string,array,slice,map,channel
new分配内存,主要针对值类型,int,struct,返回指针
make分配内存,主要针对引用类型,比如chan,map,slice
append追加元素到array,slice
panic,recover错误处理

panic、recover

panic、recover模式处理错误。panic在任何位置引发,但recover只在defer调用的函数中有效。

4.面向对象

结构体

结构体都是值类型,赋值时时拷贝

标识符:变量名,函数名,类型名,方法名

​ 大写表public,对外部包可见

在Go语言中,未进行初始化的变量都会被初始化为该类型的零值

//1.初始化
type Persion struct{
    name,age string
}
//1.1 
var p Persion 		// var p *Persion 语法糖
p.name="zhangsan"
p.age = "20"
//1.2 键值对
p1 := Persion{
    name: "zhangsan",
    age: "18",
}
//类似map
m := map[string]int{
    "stu1":100,
    "stu2":99,
}
//1.3 值列表
p2 := Persion{
 	"张三",
    "20",
}
//1.4
p := new(Persion)
p := &Persion{}
构造函数

new开头,创建结构体类型

返回一个该类型的变量

方法

作用于特定类型的函数。

在同一个包下得类型可以添加方法。

接受者Receiver

表示的是具体的类型。名称用类型首字母小写。类似this,self。

  • 值接受者: 拷贝
  • 指针接受者:地址传递

何时使用指针接受者:

  1. 需要修改接受者的值
  2. 接受者拷贝代价大
  3. 保证一致性,如果一个使用了指针接受者,其他方法都应使用指针接受者
任意类型添加方法

接受者的类型可以是任意的,并不一定是结构体.

type Myint int    //定义Myint类型

func (m Myint) hello() {
    fmt.Print("hello, I am int")
}

func main(){
    m:= Myint(100)  // 类似int8(100),强制转化为Myint类型
    m.hello()
}
结构体匿名字段

不常用,可以和嵌套结构体混合使用,方便编程

字段名称省略。缺点多

嵌套结构体
//
type Persion struct{
    name string
    addr address
    company    //匿名嵌套,好处是可以直接使用其自身字段
}
type address struct{
    province string
    city string
}
type company struct{
	cname string
    //city  string  
}
func main(){
    p :=Persion{
        name:"lisi",
        addr: {
            province:"shanxi",
            city:"taiyuan",
        },
        company:company{
            cname:"kaixin",
        }
    }
    fmt.Print(p.name,p.addr.city)
    fmt.Print(p.cname) // 可直接使用. 在自身结构体字段查找,若找不到,去匿名嵌套的结构体查找
}


继承
type animal struct{
    name string
}
type dog struct{
    feet uint8
    animal    //匿名字段   animal有的方法,dog也拥有了,间接实现  继承
}

func (a animal) move(){
    fmt.Print("gogogo..")
}

func (d dog) wang(){
    fmt.Print("%s: wangwangwang~",d.name) // 匿名字段animal
}

func main(){
    d ;= dog{
        animal : animal{name: "lisi"},
        feet: 4,
    }
    d.wang()
    d.move() // 
    
}

json&结构体

json --> 结构体:

json.Marshal() []byte

type animal struct{
    Name string `json:"name",db:"name",ini:"name"`
    Age  int	`json:"age"`   //字段名大写是因为需要在json包里处理
}

结构体 --> json:

json>Unmarshal([]byte, &p) // 传指针类型,是能在json中修改p的值

接口

接口也是一种特殊的类型,它规定变量有哪些方法

编程中,不关心传参类型,但要使用它的方法

type cat struct{}
type dog struct{}

func (d dog) speak(){
    fmt.print("wangwangwang")
}
func (c cat) speeak(){
    fmt.print("miaomiaomiao")
}
func call(x dog){
	//调用参数类型的方法
    x.speak()
}

func main(){
    var c cat
    var d dog
    //call(c)
    call(d)
}

//接口,规定了有哪些方法
type speaker interface{
    speak() //只要实现了speak方法的类型都是speaker类型,方法签名
}

type cat struct{} //只要实现了speak方法的类型都是speaker类型
type dog struct{} //只要实现了speak方法的类型都是speaker类型

func (d dog) speak(){
    fmt.print("wangwangwang")
}
func (c cat) speeak(){
    fmt.print("miaomiaomiao")
}
func call(x speaker){
	//调用参数类型的方法
    x.speak()
}

func main(){
    var c cat
    var d dog
    call(c)
    call(d)
}
接口定义
type 接口名 interface{
    方法名1(参数1,参数2,...)(返回值1,返回值2)
    方法名2(参数1,参数2,...)(返回值1,返回值2)
    ...
}
接口实现

变量,参数,返回值

//接口,规定了有哪些方法
type speaker interface{
    speak() //只要实现了speak方法的类型都是speaker类型,方法签名
    eat(string)
}

type dog struct{} //只要实现了speak方法的类型都是speaker类型

func (d dog) speak(){
    fmt.print("wangwangwang")
}

func (d dog) eat(){ //speaker eat(string)不是同一个
    ...
}
//表示dog并非实现speaker类型

func main(){
    var d dog
    var s speaker //定义speaker接口类型的变量s
    s = d  //error
    fmt.print(s)
    
}

接口保存两部分:值得类型 + 值本身

在这里插入图片描述

接受者
//使用 值接受者 和 指针接受者 的区别

5.文件

//读整个文件
func radAll(file string) {
	b, err :=ioutil.ReadFile(file)
	if err == nil{
		fmt.Println(string(b))
	}
}

//按行读文件
func buf_read(file string)  {
	fi,err := os.Open(file)
	if err!=nil && !errors.Is(err,io.EOF){
		fmt.Println("read file err. ",err)
		return
	}
	defer fi.Close()
	reader := bufio.NewReader(fi) //读文件的对象
	for  {
		line, err :=reader.ReadString('\n') //读到换行 停止
		if err == io.EOF {
			return
		}
		if err !=nil{
			fmt.Println("read line failed. ",err)
			return
		}
		fmt.Print(line)
	}
}

//按字节读文件
func read_byte(file string) {
	fi,err := os.Open(file)
	if err!=nil && !errors.Is(err,io.EOF){
		fmt.Println("read file err. ",err)
		return
	}
	defer fi.Close()
	var b [128]byte
	for  {
		n,err := fi.Read(b[:])
		if err!=nil{return}
		fmt.Println(b[:n])
		if n<128{
			return
		}
	}
}

func write(file string) {
	//openfile() 指定模式打开文件
	fi, err := os.OpenFile(file, os.O_APPEND|os.O_CREATE, 0644)
	if err != nil {
		return
	}
	defer fi.Close()
	fi.Write([]byte("hello world"))
	fi.WriteString("hello go world\n")

}

func write_buf(file string) {
	fi, err := os.OpenFile(file, os.O_CREATE|os.O_APPEND, 0644)
	if err != nil{
		return
	}
	writer := bufio.NewWriter(fi)
	writer.WriteString("hello go world\n") // 写入缓存区
	writer.Flush()                          //缓存区写入文件
	writer.Write([]byte("hello world"))
}

func write_all(file string)  {
	err := ioutil.WriteFile(file,[]byte("hello ioutil"),0644)
	if err!=nil{
		return
	}
}
copy
func copyFile(dst,src string) (written int64, err error)  {
	//读源文件
	s, err := os.Open(src)
	if err!=nil{
		return
	}
	defer s.Close()

	//写入新文件
	d,err := os.OpenFile(dst,os.O_WRONLY|os.O_APPEND,0644)
	if err!=nil{
		return
	}
	defer d.Close()
	return io.Copy(d,s)
}

6.网络编程

TCP
//server
func main() {
	addr, _ := net.ResolveTCPAddr("tcp", "0.0.0.0:21834")
	listener, err := net.ListenTCP("tcp", addr)
	if err != nil {
		fmt.Println("listen failed. err: ", err)
		return
	}
	fmt.Println("listen to %v", addr)
	for {
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("build conn failed. err: ", err)
			break
		}
		go connProc(conn)
	}
}
func connProc(conn net.Conn) {
	defer conn.Close()
	reader := bufio.NewReader(conn)

	//b := make([]byte, 1024)
	//n,_:=reader.Read(b[:])
	//fmt.Println(string(b[:n]))
	for {
		msg, err := Decode(reader)
		if err == io.EOF {
			return
		}
		if err != nil {
			fmt.Println("decode failed. err: ", err)
			return
		}
		fmt.Println("消息: ", msg)
	}

}

//client
func main() {
	conn, err := net.Dial("tcp", "127.0.0.1:21834")
	if err != nil {
		fmt.Println("dial failed. err: ", err)
		return
	}
	defer func() {
		if err := recover(); err != nil {
			conn.Close()
			fmt.Println("panic...")
		}
	}()
	reader := bufio.NewReader(os.Stdin)
	//for i := 0; i < 20; i++ {
	//	msg := "hello world! liubao "
	//	b, err := Encode(msg)
	//	if err != nil {
	//		return
	//	}
	//	conn.Write(b)
	//}
	go func() {
		for {
			msg, _ := reader.ReadString('\n') // 读到换行结束
			msg = strings.TrimSpace(msg)
			if msg == "exit" || msg == "" {
				return
			}
			msgb, err := Encode(msg)
			if err != nil {
				fmt.Println("encode err:", err)
				return
			}
			conn.Write(msgb)
			fmt.Println("msg: ", msg)
		}
	}()
	// 拦截系统信号量
	c := make(chan os.Signal, 1)
	signal.Notify(c)
	s := <-c
	fmt.Println("拦截: ", s)

}

TCP粘包

1.由Nagle算法造成的发送端的粘包:Nagle算法是一种改善网络传输效率的算法。简单来说就是当我们提交一段数据给TCP发送时,TCP并不立刻发送此段数据,而是等待一小段时间看看在等待期间是否还有要发送的数据,若有则会一次把这两段数据发送出去。
2.接收端接收不及时造成的接收端粘包:TCP会把接收到的数据存在自己的缓冲区中,然后通知应用层取数据。当应用层由于某些原因不能及时的把TCP的数据取出来,就会造成TCP缓冲区中存放了几段数据。

解决

出现”粘包”的关键在于接收方不确定将要传输的数据包的大小,因此我们可以对数据包进行封包和拆包的操作。

封包:封包就是给一段数据加上包头,这样一来数据包就分为包头和包体两部分内容了(过滤非法包时封包会加入”包尾”内容)。包头部分的长度是固定的,并且它存储了包体的长度,根据包头长度固定以及包头中含有包体长度的变量就能正确的拆分出一个完整的数据包。

我们可以自己定义一个协议,比如数据包的前4个字节为包头,里面存储的是发送的数据的长度。

func Encode(message string) ([]byte, error) {
	// 读取消息的长度转换成int32类型(4字节)
	var length = int32(len(message))
	var pkg = new(bytes.Buffer)
	// 把消息长度写入pkg(写入头)
	//小端方式写入  小端: 4字节存储的方式(低位数存在内存的低位)
	err := binary.Write(pkg, binary.LittleEndian, length)
	if err != nil {
		return nil, err
	}
	// 写入包体
	// pkg追加消息内容
	err = binary.Write(pkg, binary.LittleEndian, []byte(message))
	if err != nil {
		return nil, err
	}
	return pkg.Bytes(), nil
}

// 解码
func Decode(reader *bufio.Reader) (string, error) {
	// 读消息长度
	lengthByte, _ := reader.Peek(4) //返回当前读取的数据
	lengthBuff := bytes.NewBuffer(lengthByte)
	var length int32
	err := binary.Read(lengthBuff, binary.LittleEndian, &length) //从头部(pkg)读取  "消息长度"=length
	if err != nil {
		return "", err
	}
	// buffer返回缓冲中现有的可读的字节数
	if int32(reader.Buffered()) < length+4 { //length+4:表示消息+头数据 == 发来的消息实际长度
		return "", err
	}
	// 读取真正的数据
	pack := make([]byte, int(4+length)) // 创建字节切片  长度 = 消息实际长度
	_, err = reader.Read(pack)          // 把消息读出到这个字节切片
	if err != nil {
		return "", err
	}
	return string(pack[4:]), nil // 返回除头以外的,即实际消息字符串
}
UDP
//SERVER
func main() {
	udpConn, err := net.ListenUDP("udp", &net.UDPAddr{
		IP: net.IPv4(127,0,0,1),
		Port: 2021,
	})
	if err!=nil{
		fmt.Println(err)
		return
	}
	defer udpConn.Close()
	var b [1024]byte
	for{
		n, addr, err := udpConn.ReadFromUDP(b[:])
		if err!=nil{
			return
		}
		fmt.Println(string(b[:n]))
		_,err =udpConn.WriteToUDP([]byte(strings.ToUpper(string(b[:n]))),addr)
		if err!= nil{
			fmt.Println("write to udp failed.",err)
			return
		}
	}
}


//client
func main() {
	udpConn, err := net.DialUDP("udp",nil,&net.UDPAddr{
		IP: net.IPv4(127,0,0,1),
		Port: 2021,
	})
	if err!=nil{
		return
	}
	defer udpConn.Close()
	reader := bufio.NewReader(os.Stdin)
	var reply [1024]byte
	for{
		msg,_ := reader.ReadString('\n')
		udpConn.Write([]byte(msg))
		//收回复
		n,_,_:=udpConn.ReadFromUDP(reply[:])
		fmt.Println(string(reply[:n]))

	}

}
http

Go内置net/http包十分优秀

//server

func main(){
    http.HandleFunc("/speedtest", bandwidthtest) // 设置访问路由
    err=http.ListenAndServe(fmt.Sprintf("0.0.0.0:%s","8089"),nil)// 设置监听端口  
}
//http.ResponseWriter响应   http.Request 请求
func bandwidthtest(w http.ResponseWriter, r *http.Request) {
    b, _:= ioutil.ReadFile("./index.html")
    w.Write(b)
}


//client
// 一般请求
func main(){
    resp,err := http.Get("localhost:8089/speedtest")
    if err != nil{
        return 
    }
    //Response struct {
    //  Body io.ReadCloser
    //   ... 
    //}
    
    //从resp的body读取数据
    //var b [1024]byte
    //resp.Body.Read(b)
    //resp.Body.Close()
    
    //等价 ==>
    b, err := ioutil.ReadAll(resp.Body)
    defer resp.Body.Close()
}
//对url编码encode  -- GET
func main(){
    urlobj,err := url.Parse("localhost:8089/speedtest")//请求地址
    //请求参数
    data := url.Value{}
    data.Set("name","lisi")
    data.Set("age","28")
    querystr := data.Encode()  //url编码
    urlobj.RawQuery = querystr
    req,err := http.NewRequest("GET", urlobj.String(), nil)
}

//连接频繁
// 1. 复用client
// 2. DisableKeepAlives:   true, //建立短连接
// 3. t.MaxConnsPerHost = -1 这样设置后请求完毕后就会立即断开连接而不会放到连接池。

// 2和3区别: 如果设置DisableKeepAlives=true,则会请求的时候自动加上请求头"Connection", "close",这样在服务端响应完后就会立即关闭连接,否则连接将由客户端关闭。
//    if pc.t.DisableKeepAlives && !req.wantsClose() {
//        req.extraHeaders().Set("Connection", "close")
//    }

func clientRepeat(){
	client := &http.Client{
		Transport: &http.Transport{
            DisableKeepAlives:   false,
        },
	}
    //保持这个client长链接,复用
}


func bindAddress(address string) *http.Client {
	localAddr, err := net.ResolveIPAddr("ip", address)
	if err != nil {
		return nil
	}
	localTCPAddr := net.TCPAddr{
		IP: localAddr.IP, //绑定Ip
	}
	d := net.Dialer{
		LocalAddr: &localTCPAddr,
		Timeout:   30 * time.Second, //TCP建立连接最大时长
		KeepAlive: 30 * time.Second, //活动网络连接周期时间
	}
	t := &http.Transport{
		Proxy:               http.ProxyFromEnvironment,
		Dial:                d.Dial,
		DisableKeepAlives:   true, //2. 建立短连接
		TLSHandshakeTimeout: 10 * time.Second,
		TLSClientConfig:     &tls.Config{InsecureSkipVerify: true}, //证书检测
	} 
    t.MaxConnsPerHost = -1 //3.  这样设置后请求完毕后就会立即断开连接而不会放到连接池。
	client := &http.Client{
		Transport: t,
		Timeout:   time.Millisecond * HttpMaxTimeOut, //请求超时 超时限制包括: 连接时间、重定向和读取回复主体的时间
	}
	return client
}





7.Time

//时区问题
func main{
    now := time.Now()
    //字符串解析时间
    time.Parse("2006-01-02 15:04:05", "2022-01-02 15:04:05")
    
    
    //按照时区解析时间
    loc,err := time.LoadLocation("Asia/Shanghai")
    later, err := time.ParseInLocation("2006-01-02 15:04:05", "2022-01-02 15:04:05",loc)
    //求差
    td := later.Sub(now)
}

8.反射

  • 反射是指在程序运行期对程序本身进行访问和修改的能力,程序在编译时变量被转换为内存地址,变量名不会被编译器写入到可执行部分,在运行程序时程序无法获取自身的信息。
  • 支持反射的语言可以在程序编译期将变量的反射信息,如字段名称、类型信息、结构体信息等整合到可执行文件中,并给程序提供接口访问反射信息,这样就可以在程序运行期获取类型的反射信息,并且有能力修改它们。
  • Go语言提供了 reflect 包来访问程序的反射信息
reflect包
  • 任何类型由类型type该类型的值value组成。
  • reflect 包提供了 reflect.TypeOf 和 reflect.ValueOf 两个函数来获取任意对象的 Value 和 Type

type类型:

  1. 类型(type):go底层,如:int、string、byte等 reflect.TypeOf()
  2. 种类( kind):结构体。 reflect.TypeOf().Kind() / reflect.TypeOf().Name()

Go反射中,数组、切片、Map、指针等类型的变量,他们的 .Name()返回 nil

//获取
func reflectType(x interface{})  {
	v := reflect.TypeOf(x)
	fmt.Printf("type: %v\n ", v)
	fmt.Printf("type name:%v , type kind:%v\n ", v.Name(),v.Kind()) //类型的种类
}

func reflectValue(x interface{})  {
	v := reflect.ValueOf(x)
	k := v.Kind()  //值得种类
	switch k {
	case reflect.String:
		fmt.Println("type string. ", v.String())
	case reflect.Int64:
		fmt.Println("type string. ", v.Int())
	case reflect.Float32:
		fmt.Println("type string. ", v.Float())
	}
}

//设置
//*** go中函数的参数㐊值拷贝,若修改值,必须变量指针(引用) ***
func reflectSetValue(x interface{})  {
	v := reflect.ValueOf(x)
    if v.Elem().Kind() == reflect.Int{//Elem() 获取指针对应的值
		//v.SetInt(200)   //若x是变量副本,则panic
        v.Elem().SetInt(200)   //若x是变量副本,则panic
	}
	}
}

//IsNil() 常用于判断指针是否为空
// v持有的值是否为nil; v持有的值得分类必须是通道/函数/接口/映射/指针/切片之一;否则panic

//IsValid() 常用于返回值是否有效, 查找结构体是否存在某个字段
// v是否持有一个值

func reflectDemo(x interface{})  {
	var a *int
	fmt.Println("var a *int Isnil: ", reflect.ValueOf(a).IsNil())
	fmt.Println("var a *int Isnil: ", reflect.ValueOf(nil).IsValid())

	//实例化结构体
	b := struct{}{}
	//查找字段
	fmt.Println("字段: ",reflect.ValueOf(b).FieldByName("aaa").IsValid())
	fmt.Println("字段: ",reflect.ValueOf(b).MethodByName("aaa").IsValid())

	//map
	c := map[string]int{}
	fmt.Println(reflect.ValueOf(c).MapIndex(reflect.ValueOf("我想要的键")).IsValid())

}
结构体反射
type student struct {
	Name string `json:"name" hello:"nihao"`
	Age  int  `json:"age"`
}

func main() {
	stu1 := student{
		Name: "zhangsan",
		Age: 28,
	}
	s := reflect.TypeOf(stu1)
	fmt.Println(s.Name(),s.Kind())  // student struct

	//遍历字段
	for i:=0;i<s.NumField();i++{
		filed := s.Field(i)
		fmt.Printf("name:%v, index:%d, type:%v, jsonTag: %v, hello: %v\n",
			filed.Name,filed.Index,filed.Type,filed.Tag,filed.Tag.Get("hello"))
	}
	if filed,ok := s.FieldByName("score"); ok{
		fmt.Printf("name:%v, index:%d, type:%v, jsonTag: %v, hello: %v\n",
			filed.Name,filed.Index,filed.Type,filed.Tag,filed.Tag.Get("hello"))
	}
	if filed,ok := s.FieldByName("Age"); ok{
		fmt.Printf("name:%v, index:%d, type:%v, jsonTag: %v, hello: %v\n",
			filed.Name,filed.Index,filed.Type,filed.Tag,filed.Tag.Get("hello"))
	}

}

不滥用反射原因:

  1. 基于反射代码在运行期才会出现panic
  2. 难以理解
  3. 性能低下,慢一到两个数量级

9.并发

并发:在某一时间段内同时运行

并行:同一时刻都在运行

goroutine

协程: 用户态线程

结束时机:1. goroutine对应的函数结束了,gotoutine就结束了

​ 2. main函数结束,由main创建的goroutine结束

//优雅退出
var wg sync.WaitGroup
func main() {
	//g()
	for i := 0; i < 10; i++ {
		wg.Add(1)  // 计数器加一
		g1(i)
	}
	wg.Wait()   //阻塞主协程 , 直到计数器为0
}

func g1(i int)  {
	defer wg.Done()  //计数器减一
	time.Sleep(time.Microsecond * time.Duration(rand.Intn(300)))
	fmt.Println(i)

}


goroutine与线程

OS 线程(操作系统线程)一般由固定的栈内存(2MB),

一个goroutine的栈生命周期开始只占2KB,但不固定,可以增大和缩小,最大可达到1GB。可在go语言中一次创建十万左右的goroutine。

goroutine调度

GMP是Go语言运行时(runtime)层面的实现,是go语言自己实现的一套调度系统,区别于操作系统OS线程;

  • G : goroutine信息和与所在P绑定等信息
  • M(machine)是Go运行时(runtime)对操作系统内核线程的虚拟,M与内核线程一般是一一映射关系。一个goroutine最终是在M上执行的。
  • P 管理一组goroutine队列,P里面存储当前goroutine运行的上下文环境(函数指针,堆栈地址及地址边界),P会对自己管理的goroutine队列做调度(如:占cpu较长时间的goroutine暂停、运行后续的goroutine等等),当自己的队列消费完就去全局队里取,如果全局队列也消费完会去其他P的队列抢任务。

P与M一般一一对应。关系:P管理的一组G挂载在M上运行。当一个G长久阻塞在M上,runtime会新建一个M,阻塞G所在的P会吧其他的G挂载在新建的M上。当旧的G 阻塞完成或认为其已经死掉时回收旧的M。

P的个数时通过runtime.GOMAXPROCS设置(max=256)。Go1.5版本后默认为物理线程数。在并发量大是会增加P和M,但不会太多。

线程角度:go优势在OS线程是由OS 内核调度的,goroutine是由Go运行时调度器调度。采用m:n调度的技术(调度m个goroutine到n个OS线程)。goroutine调度实在用户态下完成,不涉及内核与用户之间频繁切换,包括内存的分配与释放,都是在用户态维护一块大的内存池,不直接调malloc函数(除非内存池改变);另一方面充分利用多核硬件资源,近似把若干goroutine均分在物理线程上,再加上本身goroutine超轻量,保证go调度性能。


Go语言中操作系统线程和goroutine的关系:

1.一个操作系统线程对应用户态多个goroutine

2.go程序可使用多个操作系统线程

3.goroutine和OS线程是多对多关系。如 m:n

goroutine泄露

work -pool: goroutine池


channel

函数与函数需要交换数据才能体现并发执行函数的意义。

  1. 共享内存进行数据交换,易发生数据竞争问题,为保证数据交换的正确性,必须使用互斥量对内存加锁,易造成性能问题。
  2. Go语言的并发模型是CSP(communication sequential processes),提倡通过通信共享内存而不是通过共享内存通信

goroutine是Go程序的并发执行体,channel是他们之间的连接,channel是可以让一个goroutine发送特定值到另一个goroutine的通信机制。

channel(通道)是一种特殊类型,像一个传送带或队列,总是先入先出(first in first out)规则,保证收发数据顺序。每个通道是一个具体类型的导管,即事先声明一个通道时需要指定元素类型。

使用

书写: <-

//发送
ch <- 10     // 把10 发送到ch中

//接受
x:= <- ch  // 从ch中接受值并赋值给变量x
<-ch       //从ch中接受值,忽略结果

//关闭: 只有通知接受方goroutine所有的数据都发送完毕后再关闭
//通道可以被垃圾回收的,但文件关闭是必须的,通道关闭不是必须的
close(ch)

关闭后的通道:

  1. 对一个关闭的通道发送值,panic
  2. 对一个关闭的通道进行接受会一直获取值直到通道为空
  3. 对一个关闭的并且没有值得通道执行接受操作会得到对应类型的零值
  4. 关闭一个已经关闭的通道会导致panic
有缓存区和无缓存取得通道

无缓存区的通道: 必须起协程先读,之后在写

有缓存区的通道: 可先写数据到通道

优雅的接受

fori:= range ch

注意事项
  1. 单向通道用途:用在函数的参数里。用来做绑定操作。
  2. 操作通道注意事项: (主语:变量)

在这里插入图片描述

​ 发送: 发送值到通道里

​ 接收:接收通道中的值

  1. 死锁:

​ 所有goroutine都在阻塞,包括主goroutine

4. 泄露:

​ 主程序正常运行,后台goroutine一直在阻塞,后台goroutine内存不能释放,造成泄露

worker pool(goroutine池)
//worker
func worker(id int, jobs <-chan int, res chan<- int){
    for j := range jobs{
        fmt.Printf("worker id: %d start job:%d\n",id, j)
        time.sleep(1)
        fmt.Printf("worker id: %d end job:%d\n",id, j)
        res <- j*2
    }
}
func main(){
    jobs := make(chan int, 100)
    res := make(chan int, 100)
    //3个工作的goroutine
    for i := 1; i<=3;i++{
        go worker(i,jobs,res)
    }
    //5个任务
    for j:=1;j<=5;j++{
    	jobs <-j
    }
    //关闭channel
    close(jobs)
    //输出结果
    for j:=1;j<=5;j++{
    	<- res
    }
    
}




select

多路复用

目标: 从多个通道中读取

for{
    data, ok ;= <- ch1
    data, ok := <- ch2
}

上述方法可以实现,但性能较差。会顺序从ch1ch2中读取

select可同时响应多个通道的操作。select类似switch语句,他有一些列case和默认default。每个case对应一个通道的通信(接受或发送)过程。

select一直等待某个case通信操作完成,执行对应case

select{
    case <- ch1:
    
    case data := <- ch2:
    
    case ch3 <- data:
    
    default:
    	默认操作
}
//多个case 同时满足条件,则随机选择一个case

//select{}没有case的会阻塞进程

练习: 协程和通道打印 1-10的偶数

//打印偶数
func main(){
    for i := 0; i <10; i++{
        select{
            case x := <- ch: // i = 1  3 ...
            	fmt.Print(x) //打印通道里的 0 2...
            case ch <- i:  // i = 0  2 ...
		}
    }
}

Go 锁
  1. sync.WaitGroup
// A WaitGroup must not be copied after first use.
// A WaitGroup waits for a collection of goroutines to finish.
var wg sync.WaitGroup
wg.Add(3) //添加goroutine数量
go func(){
    defer wg.Done() //goroutine数量减 1
    ...
}()
go func(){
    defer wg.Done()
    ...
}()
go func(){
    defer wg.Done()
    ...
}()
wg.Wait()  // 阻塞直到  goroutine数量为 0

  1. sync.Mutex 互斥锁

    var mtx *sync.Mutex //如果使用指针需要初始化
    var mtx sync.Mutex 
    mtx.Lock()
    mtx.Unlock()
                            
    
  1. sync.RWMutex 读写锁
// 读多写少
//读锁 : 一个goroutine获得,其他goroutine 仍可以获取读锁,但不能获得写锁
//写锁 :  一个goroutine获得,其他goroutine 不可以获取读锁或写锁



  1. sync.Once

在高并发场景下, 只执行一次,例如: 加载一次配置文件、关闭一次通道

只有一个sync.Once只有一个Do方法,签名如下:

// 如果函数有参数,需要搭配闭包
func (o *Once) Do(f func()) {}
  1. sync.Map
  • map不是并发安全的,go提供开箱即用的并发安全map; fatal error: concurrent map writes

  • 该map不需要初始化就可以使用

  • sync.map 方法;

var all2 sync.Map
all2.Store(iface.Name, iface.HardwareAddr.String()) //存
if ifname2,ok := all2.Load(slot.Mac);!ok{			//取
    fmt.Println(ifname2)
    break
}
//遍历
all.Range(func(key, value interface{}) bool {
    if value.(string) == mac{
        return true
    }
    return false
})

  1. atomic 原子操作

提供底层原子级内存使用,对于同步算法实现有用。除了特殊底层实现外,一般采用channel,sync包实现同步。

var x int64
atomic.AddInt64(&x, 10)

10 测试

类型格式作用
测试函数函数前缀Test测试程序的逻辑是否正确
基准测试函数前缀Benchmark测试函数性能
示例函数函数前缀Example提供实例文档

go test命令会遍历所有的*_test.go文件符合上述命名规则的函数,生成临时的main包调用相应的测试函数,报告测试结果,最后清理测试中临时生成的临时文件.

1. 测试函数

导入testing包,测试函数的签名如下:

func TestName(t *testing.T){ // Name是函数名,大写开头, 参数t 用于报告用于失败和附加的日志信息
	//... 	
}

func TestSetMultiPathSetting(t *testing.T) {

	//f1()
	//f2()
    
	//interfaces :=[]string{"WLAN 1","WLAN 2"}
	//interfaces :=[]string{"以太网","WLAN 1"}
	//post :=[]string{"WLAN 2","WLAN 1"}
	//post :=[]string{"WLAN 1","以太网"}
	//sort.Strings(post)
	//sort.Strings(interfaces)
	//f := f3(interfaces,post)
	//fmt.Println(f)

}


func f3(a, b []string) bool {
	if len(a) != len(b) {
		return false
	}

	if (a == nil) != (b == nil) {
		return false
	}
	sort.Strings(a)
	sort.Strings(b)
	for i, v := range a {
		if v != b[i] {
			return false
		}
	}
	return true
}


//不推荐
func f2()  {
	interfaces :=[]string{"以太网","WLAN 1","WLAN 2"}
	//interfaces :=[]string{"WLAN 1","WLAN 2"}
	post :=[]string{"WLAN 2","WLAN 1"}

	//m := []string
	//f := reflect.DeepEqual(post,interfaces)  x
	//fmt.Println(f)
	//sort.Strings(post)
	//sort.Strings(interfaces)
	f :=reflect.DeepEqual(post,interfaces)
	fmt.Println(f)
}


func f1()  {
	interfaces := map[string]int{
		"以太网":0,
		//"WLAN 1":0,
		"WLAN 2":0,
	}
	post :=map[string]int{
		"WLAN 2":0,
		"WLAN 1":0,
	}
	m := make(map[string]int, 10)
	if len(interfaces) != len(post){
		fmt.Println("update")
		return
	}else {
		for k,_ := range post{
			m[k] = 0
			for k1,_ := range interfaces{
				m[k1] = 0
			}
		}
	}
	if len(m) > len(interfaces){
		fmt.Println("update")
	}else{
		fmt.Println("no update")
	}
}

11.Go Module

go module是go1.11版本之后官方的版本管理工具,并从go1.13开始,go module是go默认的依赖管理工具

1. GO111MODULE

启用go module支持要开启GO111MODULE,通过它开启或关闭模块支持,有三个:off、on、auto,默认auto

  1. GO111MODULE=off,禁用模块支持,编译会从GOPATH和vendor文件夹查找包
  2. GO111MODULE=on,启用模块支持,编译会忽略gopath和vendor文件夹,根据go.mod下载依赖
  3. GO111MODULE=auto,当项目在$gopath/src外且项目根目录有go.mod文件时,开启模块支持

简言之,GO111MODULE=on,可在gopath外创建项目,且能管理项目依赖的第三方包信息。

使用go module管理依赖会在项目根目录下生成两个文件go.mod和go.sum

2.GOPROXY
export GOPROXY=https://goproxy.cn //mac
set GOPROXY=https://goproxy.cn   //windows
//默认 https://proxy.golang.org 墙外
3.GO mod命令
go mod download   下载依赖module到本地cache
go mod edit       编辑go.mod
go mod graph      打印模块依赖图
go mod init       初始化当前文件夹,创建go.mod文件
go mod tidy       增加缺少的module,删除无用的module
go mod vendor     将依赖复制到vendor
go mod verify     验证依赖
go mod why        解释为什么需要依赖
4.go.mod
//go.mod文件记录了项目所有依赖信息
- module 定义包名
- require 定义依赖包及版本
- indirect 间接引用
  • 依赖的版本

    go mod支持语义化版本号,如 go get foo@v1.2.3 或 go get foo@master (git分支或tag) 或 go get foo@a3456fdfasf23 (git提交哈希)

  • replace

    国内访问的golang.org/x的各个包需要翻墙,可以在go.mod中使用replace替换github对应的库

  • go get

    go get 命令可以下载依赖包,并指定下载的版本。

    1. go get -u升级到最新的次要版本或者修订版本(x.y.z : z是修订版本号,y是次要版本号)
    2. go get -u=patch 升级到最新的修订版本
    3. 运行go get package@version升级到指定的版本号version
    4. 下载所有依赖可使用 go mod download
  • 整理依赖

    代码中删除依赖并不会删除go.mod文件中的依赖库.

    使用go mod tidy可以更新go.mod依赖关系

  • go mod edit

    格式化

    手动修改go.mod文件,需要格式化该文件 go mod edit -fmt

    添加依赖项

    go mod edit -require=golang.org/x/text

    删除依赖项

    go mod edit -droprequire=golang.org/x/text

    帮助

    go help mod edit

  • 项目中使用

    • 需要对一个已经存在的项目启用 go module ,可按照以下步骤
    1. 项目目录执行go mod init ,生成一个go.mod文件
    2. 执行go get,查找当前项目的依赖,同时生成一个go.sum记录依赖库的版本和哈希值
    • 新项目
      1. go mod init
      2. 编辑go.mod文件require依赖项或执行go get自动发现,维护依赖

12. context

思考: 优雅退出goroutine

GO1.7加入新的标准库context,他定义了Context类型,用来简化单个请求的多个goroutine之间与请求域的数据,取消信号、截止时间等相关操作,这些操作涉及多个API调用。

对服务器传入的请求应该创建上下文,而对服务器传出的调用应该接受上下文。他们之间的函数调用链必须传递上下文。或使用withcancel、withdeadline、withtimeout、withvalue创建的派生上下文。当一个上下文被取消时,它派生的所有上下文也被取消。

Context接口
  • Deadline()需要返回当前context被取消的时间,也就是完成工作中的截止时间(deadline)

  • Done()返回一个channel,这个channel会在当前工作完成或上下文取消之后关闭,多次调用Done()会返回同一个channel

  • Err()返回当前context结束原因,只会在Done返回的channel被关闭时才会返回非空的值

    • context取消,返回canceled错误
    • context超时返回DealineExceeded错误
  • value()从context中返回键对应的值,对同一个上下文,多次调用value并传入相同的key会返回相同的结果 ,该方法仅用于传递跨API和进程间跟请求域的数据

Background()和TODO()

​ 内置函数,这两个函数分别返回一个实现了Context接口的background和todo,一般以这俩个内置的上下文对象作为最顶层的parent context,衍生出更多的子上下文对象。

​ background用于main函数,初始化,测试代码,作为根context

​ TODO,不知道应用场景时使用

background和TODO本质时emptyCtx结构体类型,是一个不可取消,没有设置截止时间,没有携带任何值得context

with函数

git

  1. git add . 添加文件到暂存区
  2. git commit 提交到本地仓库
  3. git push 推送到远程仓库(GitHub/gitee)

gitee

码云,信息门面。

  • 免密登录;

    码云是远程仓库,通过创建ssh公钥免密登录

    • 在%USERPROFILE%/.ssh 目录

    • ssh keygen -t rsa

    • 复制id_rsa.pub内容到gitee设置SSH公钥

  • 码云创建仓库

  • git clone https://地址

    • 克隆远程仓库到本地
  • IDE 集成

    • 新建项目
      1. git目录与开发目录相同
      2. 将git目录拷贝到开发目录下
    • 修改/新增文件
      1. 添加到暂存区
      2. commit提交到本地仓库
      3. push提交到远程仓库
    • 拉取文件

GitHub

git分支

列出所有本地分支

git branch

列出所有远程分支

git branch -r

新建分支

git branch [branch-name]

新建分支,并切换到该分支

git checkout -b [branch]

合并指定分支到当前分支

git merge [branch]

删除分支

git branch -d [branch-name]

删除远程分支

git push origin --delete [branch-name]

git branch -dr [remote/branch]


多个分支并行执行,会导致代码冲突,同时存在多个版本

mater主分支应该非常稳定,用来发布新版本,一般不允许在上面工作,工作一般在新建dev上,工作完成后,比如要发布,或者dev代码稳定后合并到master上。

Gin(go web)

微服务

Python note

Python base

mod edit

  • 项目中使用

    • 需要对一个已经存在的项目启用 go module ,可按照以下步骤
    1. 项目目录执行go mod init ,生成一个go.mod文件
    2. 执行go get,查找当前项目的依赖,同时生成一个go.sum记录依赖库的版本和哈希值
    • 新项目
      1. go mod init
      2. 编辑go.mod文件require依赖项或执行go get自动发现,维护依赖

12. context

思考: 优雅退出goroutine

GO1.7加入新的标准库context,他定义了Context类型,用来简化单个请求的多个goroutine之间与请求域的数据,取消信号、截止时间等相关操作,这些操作涉及多个API调用。

对服务器传入的请求应该创建上下文,而对服务器传出的调用应该接受上下文。他们之间的函数调用链必须传递上下文。或使用withcancel、withdeadline、withtimeout、withvalue创建的派生上下文。当一个上下文被取消时,它派生的所有上下文也被取消。

Context接口
  • Deadline()需要返回当前context被取消的时间,也就是完成工作中的截止时间(deadline)

  • Done()返回一个channel,这个channel会在当前工作完成或上下文取消之后关闭,多次调用Done()会返回同一个channel

  • Err()返回当前context结束原因,只会在Done返回的channel被关闭时才会返回非空的值

    • context取消,返回canceled错误
    • context超时返回DealineExceeded错误
  • value()从context中返回键对应的值,对同一个上下文,多次调用value并传入相同的key会返回相同的结果 ,该方法仅用于传递跨API和进程间跟请求域的数据

Background()和TODO()

​ 内置函数,这两个函数分别返回一个实现了Context接口的background和todo,一般以这俩个内置的上下文对象作为最顶层的parent context,衍生出更多的子上下文对象。

​ background用于main函数,初始化,测试代码,作为根context

​ TODO,不知道应用场景时使用

background和TODO本质时emptyCtx结构体类型,是一个不可取消,没有设置截止时间,没有携带任何值得context

with函数

git

  1. git add . 添加文件到暂存区
  2. git commit 提交到本地仓库
  3. git push 推送到远程仓库(GitHub/gitee)

gitee

码云,信息门面。

  • 免密登录;

    码云是远程仓库,通过创建ssh公钥免密登录

    • 在%USERPROFILE%/.ssh 目录

    • ssh keygen -t rsa

    • 复制id_rsa.pub内容到gitee设置SSH公钥

  • 码云创建仓库

  • git clone https://地址

    • 克隆远程仓库到本地
  • IDE 集成

    • 新建项目
      1. git目录与开发目录相同
      2. 将git目录拷贝到开发目录下
    • 修改/新增文件
      1. 添加到暂存区
      2. commit提交到本地仓库
      3. push提交到远程仓库
    • 拉取文件

GitHub

git分支

列出所有本地分支

git branch

列出所有远程分支

git branch -r

新建分支

git branch [branch-name]

新建分支,并切换到该分支

git checkout -b [branch]

合并指定分支到当前分支

git merge [branch]

删除分支

git branch -d [branch-name]

删除远程分支

git push origin --delete [branch-name]

git branch -dr [remote/branch]


多个分支并行执行,会导致代码冲突,同时存在多个版本

mater主分支应该非常稳定,用来发布新版本,一般不允许在上面工作,工作一般在新建dev上,工作完成后,比如要发布,或者dev代码稳定后合并到master上。

Gin(go web)

微服务

Python note

Python base

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值