golang github死锁挑战二


https://github.com/GoesToEleven/GolangTraining/blob/master/22_go-routines/10_deadlock-challenges/03_deadlock-challenge/main.go

package main

import (
    "fmt"
)

func main() {

    c := make(chan int)

    go func() {
        for i := 0; i < 10; i++ {
            c <- i
        }
    }()

    fmt.Println(<-c)

}
//为什么只显示零?
//并且该怎么做才能打印所有0-9个数字?

解决方法一

停止携程 用for读取通道

package main

import (
    "fmt"
)

func main() {

    c := make(chan int)

    go func() {
        for i := 0; i < 10; i++ {
            c <- i
        }
        //关闭通道
        close(c)
    }()

    for {
        x, ok := <-c  //如果不关闭通道的话 这里会导致死锁,因为通道中没有值写入 这里一直一直一直会等待导致死锁
        if !ok { //判断通道是否关闭
            return
        }
        fmt.Println(x, ok)
    }

}

解决方法二

停止携程 用使用for-range读取通道

package main

import (
    "fmt"
)

func main() {

    c := make(chan int)

    go func() {
        for i := 0; i < 10; i++ {
            c <- i
        }
        //关闭通道
        close(c)
    }()

    for v := range c {
        fmt.Println(v)
    }
}

兴趣话题

下面代码为什么 会无限输出000000000000000000000000000.......?

package main

import (
    "fmt"
)

func main() {

    c := make(chan int)

    go func() {
        for i := 0; i < 10; i++ {
            c <- i
        }
        //关闭通道
        close(c)
    }()

    for {
        fmt.Println(<-c) //为什么会一直输出00000000000000?
    }
    // for v := range c {
    //     fmt.Println(v)
    // }
}

golang github死锁挑战一


https://github.com/GoesToEleven/GolangTraining/tree/master/22_go-routines/10_deadlock-challenges

package main

import (
    "fmt"
)

func main() {
    c := make(chan int)
    c <- 1
    fmt.Println(<-c)
}
//这会导致死锁。
//您能确定原因吗?
//而您将如何解决?

解决方法一:

通道加缓存

package main

import (
    "fmt"
)

func main() {
    c := make(chan int,1)
    c <- 1
    fmt.Println(<-c)
}

解决方法二:

加协程

package main

import (
    "fmt"
)

func main() {
    c := make(chan int)
    go func() {
        c <- 1
    }()
    fmt.Println(<-c)
}

golang websocket并发


WsConn *websocket.Conn 并发不安全 不能同时写
panic: concurrent write to websocket connection

package cws
 
import (
    "errors"
    "github.com/gorilla/websocket"
    "sync"
)
 
//封装websocket并发读写操作
 
type Connection struct {
    WsConn    *websocket.Conn
    InChan    chan []byte
    OutChan   chan models.BayDataS
    CloseChan chan byte
    Mutex     sync.Mutex
    IsClosed  bool
}
 
func InitConnection(wsConn *websocket.Conn) (conn *Connection, err error) {
    conn = &Connection{
        WsConn:    wsConn,
        InChan:    make(chan []byte, 1000),
        OutChan:   make(chan []byte, 1000),
        CloseChan: make(chan byte, 1),
    }
    //读协程
    go conn.ReadLoop()
    //写协程
    go conn.WriteLoop()
    return
}
 
func (conn *Connection) ReadMess() (data []byte, err error) {
    select {
    case data = <-conn.InChan:
    case <-conn.CloseChan:
        err = errors.New("connection is closed")
    }
    return
}
 
func (conn *Connection) WriteMes(data []byte) (err error) {
    select {
    case conn.OutChan <- data:
    case <-conn.CloseChan:
        err = errors.New("connection is closed")
    }
    return
}
 
func (conn *Connection) Close() {
    conn.WsConn.Close() //本身线程安全,可重入
    //加锁,只能执行一次
    conn.Mutex.Lock()
    if !conn.IsClosed {
        close(conn.CloseChan)
        conn.IsClosed = true
    }
}
 
//具体实现读消息
func (conn *Connection) ReadLoop() {
    var (
        data []byte
        err  error
    )
    for {
        if _, data, err = conn.WsConn.ReadMessage(); err != nil {
            goto ERR
        }
        select {
        case conn.InChan <- data:
        case <-conn.CloseChan:
            goto ERR
        }
    }
ERR:
    conn.Close()
}
 
//具体实现写消息
func (conn *Connection) WriteLoop() {
    var (
        data models.BayDataS
        err  error
    )
    for {
        select {
        case data = <-conn.OutChan:
        case <-conn.CloseChan:
            goto ERR
        }
        if err = conn.WsConn.WriteMessage(websocket.TextMessage, data); err != nil {
            goto ERR
        }
    }
ERR:
    conn.Close()
}

golang base64保存文件


golang:


/**
 * @description: base64 文件保存
 * @param 路径,文件名,内容
 * @return:
 */
func Base64ToFile(path, fileName, base64Str string) (err error) {
    err = os.MkdirAll(path, 0777) //创建目录
    if err != nil {
        return err
    }

    ddd, err := base64.StdEncoding.DecodeString(base64Str)
    if err != nil {
        return err
    }

    err = ioutil.WriteFile(path+fileName, ddd, 0666) //buffer输出文件中(不做处理,直接写到文件)
    if err != nil {
        return err
    }
    return nil
}

goalng Web中如何安全存储密码


普通方案

目前用的最多的密码存储方案是将明文密码做单向哈希后存储,单向哈希算法有一个特征:无法通过哈希后的摘要(digest)恢复原始数据,这也是“单向”二字的来源。常用的单向哈希算法包括SHA-256, SHA-1, MD5等。

Go语言对这三种加密算法的实现如下所示:

//import "crypto/sha256"
h := sha256.New()
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
fmt.Printf("% x", h.Sum(nil))

//import "crypto/sha1"
h := sha1.New()
io.WriteString(h, "His money is twice tainted: 'taint yours and 'taint mine.")
fmt.Printf("% x", h.Sum(nil))

//import "crypto/md5"
h := md5.New()
io.WriteString(h, "需要加密的密码")
fmt.Printf("%x", h.Sum(nil))

单向哈希有两个特性:

  • 同一个密码进行单向哈希,得到的总是唯一确定的摘要。
  • 计算速度快。随着技术进步,一秒钟能够完成数十亿次单向哈希计算。

结合上面两个特点,考虑到多数人所使用的密码为常见的组合,攻击者可以将所有密码的常见组合进行单向哈希,得到一个摘要组合, 然后与数据库中的摘要进行比对即可获得对应的密码。这个摘要组合也被称为rainbow table。

因此通过单向加密之后存储的数据,和明文存储没有多大区别。因此,一旦网站的数据库泄露,所有用户的密码本身就大白于天下。


进阶方案

通过上面介绍我们知道黑客可以用rainbow table来破解哈希后的密码,很大程度上是因为加密时使用的哈希算法是公开的。如果黑客不知道加密的哈希算法是什么,那他也就无从下手了。

一个直接的解决办法是,自己设计一个哈希算法。然而,一个好的哈希算法是很难设计的——既要避免碰撞,又不能有明显的规律,做到这两点要比想象中的要困难很多。因此实际应用中更多的是利用已有的哈希算法进行多次哈希。

但是单纯的多次哈希,依然阻挡不住黑客。两次 MD5、三次 MD5之类的方法,我们能想到,黑客自然也能想到。特别是对于一些开源代码,这样哈希更是相当于直接把算法告诉了黑客。

没有攻不破的盾,但也没有折不断的矛。现在安全性比较好的网站,都会用一种叫做“加盐”的方式来存储密码,也就是常说的 “salt”。他们通常的做法是,先将用户输入的密码进行一次MD5(或其它哈希算法)加密;将得到的 MD5 值前后加上一些只有管理员自己知道的随机串,再进行一次MD5加密。这个随机串中可以包括某些固定的串,也可以包括用户名(用来保证每个用户加密使用的密钥都不一样)。

//import "crypto/md5"
//假设用户名abc,密码123456
h := md5.New()
io.WriteString(h, "需要加密的密码")

//pwmd5等于e10adc3949ba59abbe56e057f20f883e
pwmd5 :=fmt.Sprintf("%x", h.Sum(nil))

//指定两个 salt: salt1 = @#$%   salt2 = ^&*()
salt1 := "@#$%"
salt2 := "^&*()"

//salt1+用户名+salt2+MD5拼接
io.WriteString(h, salt1)
io.WriteString(h, "abc")
io.WriteString(h, salt2)
io.WriteString(h, pwmd5)

last :=fmt.Sprintf("%x", h.Sum(nil))

在两个salt没有泄露的情况下,黑客如果拿到的是最后这个加密串,就几乎不可能推算出原始的密码是什么了。


专家方案

上面的进阶方案在几年前也许是足够安全的方案,因为攻击者没有足够的资源建立这么多的rainbow table。 但是,时至今日,因为并行计算能力的提升,这种攻击已经完全可行。

怎么解决这个问题呢?只要时间与资源允许,没有破译不了的密码,所以方案是:故意增加密码计算所需耗费的资源和时间,使得任何人都不可获得足够的资源建立所需的rainbow table

这类方案有一个特点,算法中都有个因子,用于指明计算密码摘要所需要的资源和时间,也就是计算强度。计算强度越大,攻击者建立rainbow table越困难,以至于不可继续。

推荐scrypt方案,scrypt是由著名的FreeBSD黑客Colin Percival为他的备份服务Tarsnap开发的。

目前Go语言里面支持的库 https://github.com/golang/crypto/tree/master/scrypt

dk := scrypt.Key([]byte("some password"), []byte(salt), 16384, 8, 1, 32)

通过上面的方法可以获取唯一的相应的密码值,这是目前为止最难破解的。


golang协程并发之error错误处理


案例

package main

import (
    "errors"
    "fmt"
)

func main() {

    go func() {
        for i := 0; i < 10; i++ {
            fmt.Println(i)
            if i == 5 {
                ReErr() //这个方法返回错误了怎么处理?
            }
        }
    }()
    for {

    }
}

func ReErr() error {
    return errors.New("出错了~")
}

输出结果:

0
1
2
3
4
5
6
7
8
9


sql server 无限递归查询树的下级、上级数据


表数据

select id,dept_nam,pid from dept

2020-05-28T06:34:56.png


查询树状结构某节点(75)的下级所有节点

with 
    dept_parent(id,dept_nam,pid) 
as
( 
  select 
    id,dept_nam,pid 
  from 
    dept 
  where 
    id = 75
  
  union all
  
  select 
    a.id,a.dept_nam,a.pid 
  from 
    dept a 
  inner join 
    dept_parent b
  on 
    a.pid=b.id  
)    
select * from dept_parent;

2020-05-28T06:37:38.png


查询树状结构某节点(77)的上级所有节点

with 
    dept_parent_s(id,dept_nam,pid) 
as
( 
  select 
    id,dept_nam,pid 
  from 
    dept 
  where 
    id = 77
  
  union all
  
  select 
    a.id,a.dept_nam,a.pid 
  from 
    dept a 
  inner join 
    dept_parent_s b
  on 
    a.id=b.pid
)    
select * from dept_parent_s

2020-05-28T06:39:13.png


golang中的多态



package main

import (
    "fmt"

)
//通知行为的接口
type notifier interface{
    notify()
}

//自定义的用户类型
type user struct{
    name string
    email string 
}
//notify是用指针接收者实现的方法
func (u *user ) notify(){
    fmt.Println("用户:",u)
}


//自定义的管理员类型
type admin struct{
    name string
    email string 
}
//notify是用指针接收者实现的方法
func (u *admin ) notify(){
    fmt.Println("管理员:",u)
}


func main(){
    user := user{"lp","344085057@qq.com"}
    sendNotification(&user)

    admin := admin{"admin","344085057@qq.com"}
    sendNotification(&admin)
}
//sendNotification 接受一个实现了notifier接口的值
func sendNotification(n notifier){
    n.notify()
}
运行结果

用户: &{lp 344085057@qq.com}
管理员: &{admin 344085057@qq.com}


golang接口中方法集的运行机制



package main

import(
    "fmt"
)
//通知行为的接口
type notifier interface{
    notify()
}
//自定义的用户类型
type user struct{
    name string
    email string 
}
//notify是用指针接收者实现的方法
func (u *user ) notify(){
    fmt.Println(u)
}

func main(){
    u:= user{"lp","344085057@qq.com"}
    sendNotification(u)
}
//sendNotification 接受一个实现了notifier接口的值
func sendNotification(n notifier){
    n.notify()
}
运行结果:

cannot use u (type user) as type notifier in argument to sendNotification:
user does not implement notifier (notify method has pointer receiver)

翻译

无法在sendNotification的参数中使用u(type user)类型:
user未实现sendNotification(notify方法具有指针接收器)

原因

//因为这里 只有指向user的指针才能够实现对应的接口。
sendNotification(u)

//正确的调用姿势
sendNotification(&u)

让我们来了解go语言规范里定义的方法集的规则:

从值的角度看这些规则:

Values           Methods Receivers
————————————————————————————
T                      (t T)
*T                    (t T) or (t *T)
描述:

T类型的值的方法集只包含值接收者申明的方法。
*T(T类型的指针) 类型的指针的方法集即包含值接收者申明的方法,也包含指针接收者声明的方法。

从接收者类型的角度来看这些规则:

 Methods Receivers       Values          
————————————————————————————
(t T)                                          T
(t T) or (t *T)                       *T
描述:

如果使用指针接收者来实现一个接口,那么只有指向那个类型的指针才能够实现对应的接口。
如果使用值接收者来实现一个接口,那么那个类型的值和指针都能够实现对应的接口


golang之并发同步sync.WaitGroup


本例子并行地访问URL,使用 WaitGroup 进行阻塞,直到所有的取回操作完成。

package main

import (
    "fmt"
    "net/http"
    "sync"
)

var wg sync.WaitGroup

func main() {
    var wg sync.WaitGroup
    var urls = []string{
        "https://www.52bd.net/",
        "https://www.baidu.com/",
        "https://go-zh.org/",
    }
    for _, url := range urls {
        // Increment the WaitGroup counter.
        // 递增 WaitGroup 计数器。
        wg.Add(1)
        // Launch a goroutine to fetch the URL.
        // 启动一个Go程来取回URL。
        go func(url string) {
            defer wg.Done()

            // Decrement the counter when the goroutine completes.
            // Fetch the URL.
            // 请求URL
            resp, _ := http.Get(url)
            fmt.Println(resp.Status)//输出请求状态

        }(url)
    }
    // Wait for all HTTP fetches to complete.
    // 等待所有的HTTP取回操作完成。
    wg.Wait()
}

func (wg *WaitGroup) Add(delta int)

Add 添加 delta,对于 WaitGroup 的 counter 来说,它可能为负数。 若 counter 变为零,在 Wait() 被释放后所有Go程就会阻塞。 若 counter 变为负数,Add 就会引发Panic。

注意,当 counter 为零时,用正整数的 delta 调用它必须发生在调用 Wait 之前。 用负整数的 delta 调用它,或在 counter 大于零时开始用正整数的 delta 调用它, 那么它可以在任何时候发生。 一般来说,这意味着对 Add 的调用应当在该语句创建Go程,或等待其它事件之前执行。 具体见 WaitGroup 的示例。

func (wg *WaitGroup) Done()

Done 递减 WaitGroup 的 counter。

func (wg *WaitGroup) Wait()

Wait 阻塞 WaitGroup 直到其 counter 为零。