第四届 Gopher China 大会正式启动,讲师火爆招募中……

回复

astaxie 发起了问题 • 1 人关注 • 0 个回复 • 117 次浏览 • 1 天前 • 来自相关话题

go实现西瓜视频花椒直播等平台智能答题

chain 发表了文章 • 0 个评论 • 218 次浏览 • 3 天前 • 来自相关话题

本文为转载,原文:go实现西瓜视频花椒直播等平台智能答题

查看全部

本文为转载,原文:go实现西瓜视频花椒直播等平台智能答题


Golang


本文源码


本文源码


介绍


最近出了很多答题平分奖金的直播,只要能够连续答对12道题,就能与所有答对的人一起平分奖池里的奖金,20万到500万不等。当这个时候,我才体会到“书到用时方恨少”这句至理名言。这时突然想到,咦!我们不是有无所不知的互联网吗,题目拿到百度中一搜不就完了。可是一看答题时间只有10秒,尽管我有着单身20多年的手速,也愣是做不到呀。再一想,我特么是程序猿呀,这种事还需要我亲自动手?
于是一通百度,找到了个大神的java智能答题的源码,这里把大神的源码地址贴出来供大家参考:https://github.com/lingfengsan/MillionHero


然而,我学了这么一大段时间的go语言,能不能用go来实现一下呢。
于是就动手尝试了一下,思路与前面提到的java的工具差不多。下面就来说道说道我是怎么实现的。


思路



  1. 手机与电脑连接,并打开直播页面

  2. 当页面出题时,通过adb截图并保存到电脑

  3. 通过百度AI文字识别,提取图片中的题目和选项的文字

  4. 使用百度搜索并,然后统计搜索得到结果数量

  5. 比较搜索到的结果数量并排序

  6. 否定的问题选择数量最少的选项,肯定的问题选数量最多的选项。


环境


硬件



  • windows电脑一台

  • 安卓手机一部

  • 安卓数据线一根


软件



  • golang 开发环境

  • adb 安卓调试驱动


其他



  • 百度AI开发者平台创建一个文字识别的应用


环境搭建


硬件就没有什么好说的了。这里主要说下软件。


golang开发环境


首先,肯定是要下载安装包啦,这里给个下载地址,自己根据情况选择版本下载:golang安装包 (i386表示x86,amd64表示x64)。


安装完Go之后,我们最好还是检查一些所有的环境变量是否正常。主要的环境变量有以下几个:



  • GOROOT:Go的安装目录

  • GOPATH:用于存放Go语言Package的目录,这个目录不能在Go的安装目录中

  • GOBIN:Go二进制文件存放目录,写成%GOROOT%\bin就好

  • PATH:需要将%GOBIN%加在PATH变量的最后,方便在命令行下运行Go


完成之后在cmd窗口输入:go version


go version


如图所示,表示我们已经安装配置成功。


然后就是IDE了,这个就更简单了。直接用记事本都可以,当然也可以用些轻量的编辑器,vscode, vim都是可以的。也可以用goland等。这些看自己的爱好。反正我是用的vscode。


adb安装


adb的全称为Android Debug Bridge 调试桥,是连接Android手机与PC端的桥梁,通过adb可以管理、操作模拟器和设备,如安装软件、查看设备软硬件参数、系统升级、运行shell命令等。
这里先给一个下载地址:adb下载地址 (有积分的大佬们从我这里下吧,我一分都没有了,想赚点分)
下载完成后安装好即可。然后把安装好的路径配置到环境变量中去,方便我们在cmd窗口下使用adb命令。配置好后,可以在cmd窗口下执行adb devices 命令:


adb devices


从图中可以看到,这里我们启动了adb,并且给了个设备列表,因为我没有连接安卓设备,所以没有东西显示。
这个时候,我们把安卓手机用数据线连接到电脑,并在手机上打开USB调试选项。设置->开发者选项->USB调试,不同的品牌的手机可能有差别,百度一下你就知道。
有时候可能做到这些还是列不出你的设备。这时候再需要做以下事情:



  1. 在计算机管理中设备管理中找到你的设备,然后右击->属性->详细信息->在详细信息页面的属性中找到硬件ID,再复制的硬件ID,我的手机是魅族,我的硬件ID是:2A45

  2. C:\Users\你的用户名\.android目录下找到adb_usb.ini文件,如果没有自己新建。然后把你刚刚复制的硬件ID写进去,由于这个ID是16进制的,所以前面加上0x,
    即:0x2A45

  3. 重启adb,停止Adb:adb kill-server,启动adb:adb start-server
    完成这些应该就可以了。如果还是不行,请自行百度。


至此,我们的环境算是完成了。


实验


实验之前,肯定是下载源码喽,当然还有少不了的依赖包。
这里我用了个baidu-ai-sdk的包。
可以通过以下命令完成安装:


go get github.com/chenqinghe/baidu-ai-go-sdk

然后通过git下载我的源码:


git clone https://github.com/Chain-Zhang/answer_ai.git

我们先看下main函数的内容


func main(){
for {
var cmd string
fmt.Printf("> ")
fmt.Scan(&cmd)
switch cmd{
case "1":
ai.Start()
case "2":
ai.ExeCommand("cmd", []string{"/c", "adb", "devices"})
case "exit":
os.Exit(1)
}
}
}

从代码中可以看到,在程序运行的时候会等待用户的输入。



  1. 当输入 1 时会进行截图答题的操作。

  2. 当输入 2 时会列出与电脑连接的设备

  3. 当输入 exit 时会退出程序。


下面我们在cmd窗口中进入我们代码的目录,执行以下命令来运行我们的程序:


go run main.go

然后输入2,看下是否有设备连接:
查看设备


然后手机打开直播,当主播出题时,输入1回车,这里实验所以手机直接打开一张图片,手机界面如下图:
手机界面


经过一系列的分析后,返回以下结果:
答题结果


根据 否定的问题选择数量最少的选项,肯定的问题选数量最多的选项 所以这一题选择: 2-c哩c哩舞。


源码


本文源码



转载请注明出处:
go实现西瓜视频花椒直播等平台智能答题

Golang解决XORM的时区问题

qiangmzsx 发表了文章 • 4 个评论 • 143 次浏览 • 3 天前 • 来自相关话题

如果你升级使用了较为新版xorm(如v0.6.3)和go-sql-driver(如v1.3)的go类库,那么你就可能会遇到时区问题。 如

查看全部
					

如果你升级使用了较为新版xorm(如v0.6.3)和go-sql-driver(如v1.3)的go类库,那么你就可能会遇到时区问题。 如


time.Parse("2006-01-02 15:04:05" ,"2018-01-15 12:11:12") // 2018-01-15T12:11:12+00:00

写入是数据库时候就会被改变为2018-01-15T20:11:12+00:00

上述的就是时区问题,因为我们使用的是东8时区,默认会被设置为0时区,解决方案很简单,只需要在main函数中或者main包中初始化时区:


time.LoadLocation("Asia/Shanghai")

数据库配置为


root:root@tcp(127.0.0.1:3306)/test?charset=utf8&interpolateParams=true

xorm的初始化修改为:


orm, err := initOrm(ds, maxIdleConn, maxOpenConn, debug)
if err != nil {
return nil, err
}
r.Value = orm
orm.DatabaseTZ = time.Local // 必须
orm.TZLocation = time.Local // 必须
orm.SetMaxIdleConns(maxIdleConn)
orm.SetMaxOpenConns(maxOpenConn)

字符串转换时间也需要改为


time.ParseInLocation("2006-01-02 15:04:05" ,"2018-01-15 12:11:12",time.Local)

此时写库时区问题就可以得到解决了,但是读库问题如下的的方式:


rss, err := this.Repo.Query(ctx, sqlStr, pos, now, os)
images := make([]*models.ImageConf, 0, len(rss))

for _, rs := range rss {
var tmpImage models.ImageConf
MapToStruct(rs, &tmpImage)
images = append(images, &tmpImage)
}

func MapToStruct(mapping map[string][]byte, j interface{}) {
elem := reflect.ValueOf(j).Elem()
for i := 0; i < elem.NumField(); i++ {
var key string
key = elem.Type().Field(i).Name
switch elem.Field(i).Interface().(type) {
case int, int8, int16, int32, int64:
x, _ := strconv.ParseInt(string(mapping[key]), 10, 64)
elem.Field(i).SetInt(x)
case string:
elem.Field(i).SetString(string(mapping[key]))
case float64:
x, _ := strconv.ParseFloat(string(mapping[key]), 64)
elem.Field(i).SetFloat(x)
case float32:
x, _ := strconv.ParseFloat(string(mapping[key]), 32)
elem.Field(i).SetFloat(x)
case time.Time:
timeStr := string(mapping[key])
timeDB, err := time.ParseInLocation("2006-01-02 15:04:05", timeStr, time.Local)
if err != nil {
timeDB, err = time.ParseInLocation("2006-01-02", timeStr, time.Local)
if err != nil {
timeDB, err = time.ParseInLocation("15:04:05", timeStr, time.Local)
} else {
timeDB = time.Date(0, 0, 0, 0, 0, 0, 1, time.Local)
}
}
elem.Field(i).Set(reflect.ValueOf(timeDB))
}
}
}

其中MapToStruct函数中的time.Time类型这儿有一个需要我们注意的,如果配置的数据库为


root:root@tcp(127.0.0.1:3306)/test?charset=utf8&interpolateParams=true&parseTime=true&loc=Local

多出了&parseTime=true&loc=Local此时timeStr := string(mapping[key])得到的将会是2006-01-02T15:04:05+08:00

那么你的转换格式应该为2006-01-02T15:04:05+08:00


总结一下:



  • 在项目中时区一定要在项目初始化时候就已经设置好

  • 字符串转换时间尽可能使用time.ParseInLocation

  • parseTime=true&loc=Local或者parseTime=true&loc=Asia%2FShanghaixorm解析时间类型为map[string][]byte有着影响

[Go] JSON 的编码和解码

SnailCoder 回复了问题 • 2 人关注 • 1 个回复 • 274 次浏览 • 3 天前 • 来自相关话题

golang for语句完全指南

sheepbao 发表了文章 • 4 个评论 • 154 次浏览 • 4 天前 • 来自相关话题

以下所有观点都是个人愚见,有不同建议或补充的的欢迎emialaboutme
查看全部

以下所有观点都是个人愚见,有不同建议或补充的的欢迎emialaboutme

原文章地址


关于for语句的疑问

for语句的规范

for语句的内部实现-array

问题解答


关于for语句的疑问


我们都知道在golang中,循环语句只有for这一个,在代码中写一个循环都一般都需要用到for(当然你用goto也是可以的),
虽然golang的for语句很方便,但不少初学者一样对for语句持有不少疑问,如:



  1. for语句一共有多少种表达式格式?

  2. for语句中临时变量是怎么回事?(为什么有时遍历赋值后,所有的值都等于最后一个元素)

  3. range后面支持的数据类型有哪些?

  4. range string类型为何得到的是rune类型?

  5. 遍历slice的时候增加或删除数据会怎么样?

  6. 遍历map的时候增加或删除数据会怎么样?


其实这里的很多疑问都可以看golang编程语言规范,
有兴趣的同学完全可以自己看,然后根据自己的理解来解答这些问题。


for语句的规范


for语句的功能用来指定重复执行的语句块,for语句中的表达式有三种:

官方的规范: ForStmt = "for" [ Condition | ForClause | RangeClause ] Block .



  • Condition = Expression .

  • ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .

  • RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .


单个条件判断


形式:


for a < b {
f(doThing)
}
// or 省略表达式,等价于true
for { // for true {
f(doThing)
}

这种格式,只有单个逻辑表达式, 逻辑表达式的值为true,则继续执行,否则停止循环。


for语句中两个分号


形式:


for i:=0; i < 10; i++ {
f(doThing)
}
// or
for i:=0; i < 10; {
i++
f(doThing)
}
// or
var i int
for ; i < 10; {
i++
f(doThing)
}

这种格式,语气被两个分号分割为3个表达式,第一个表示为初始化(只会在第一次条件表达式之计算一次),第二个表达式为条件判断表达式,
第三个表达式一般为自增或自减,但这个表达式可以任何符合语法的表达式。而且这三个表达式,
只有第二个表达式是必须有的,其他表达式可以为空。


for和range结合的语句


形式:


for k,v := range []int{1,2,3} {
f(doThing)
}
// or
for k := range []int{1,2,3} {
f(doThing)
}
// or
for range []int{1,2,3} {
f(doThing)
}

用range来迭代数据是最常用的一种for语句,range右边的表达式叫范围表达式,
范围表达式可以是数组,数组指针,slice,字符串,map和channel。因为要赋值,
所以左侧的操作数(也就是迭代变量)必须要可寻址的,或者是map下标的表达式。
如果迭代变量是一个channel,那么只允许一个迭代变量,除此之外迭代变量可以有一个或者两个。


范围表达式在开始循环之前只进行一次求值,只有一个例外:如果范围表达式是数组或指向数组的指针,
至多有一个迭代变量存在,只对范围表达式的长度进行求值;如果长度为常数,范围表达式本身将不被求值。


每迭代一次,左边的函数调用求值。对于每个迭代,如果相应的迭代变量存在,则迭代值如下所示生成:


Range expression                          1st value          2nd value

array or slice a [n]E, *[n]E, or []E index i int a[i] E
string s string type index i int see below rune
map m map[K]V key k K m[k] V
channel c chan E, <-chan E element e E


  1. 对于数组、数组指针或是分片值a来说,下标迭代值升序生成,从0开始。有一种特殊场景,只有一个迭代参数存在的情况下,
    range循环生成0到len(a)的迭代值,而不是索引到数组或是分片。对于一个nil分片,迭代的数量为0。

  2. 对于字符串类型,range子句迭代字符串中每一个Unicode代码点,从下标0开始。在连续迭代中,下标值会是下一个utf-8代码点的
    第一个字节的下标,而第二个值类型是rune,会是对应的代码点。如果迭代遇到了一个非法的Unicode序列,那么第二个值是0xFFFD,
    也就是Unicode的替换字符,然后下一次迭代只会前进一个字节。

  3. map中的迭代顺序是没有指定的,也不保证两次迭代是一样的。如果map元素在迭代过程中被删掉了,那么对应的迭代值不会再产生。
    如果map元素在迭代中插入了,则该元素可能在迭代过程中产生,也可能被跳过,但是每个元素的迭代值顶多出现一次。如果map是nil,那么迭代次数为0。

  4. 对于管道,迭代值就是下一个send到管道中的值,除非管道被关闭了。如果管道是nil,范围表达式永远阻塞。


迭代值会赋值给相应的迭代变量,就像是赋值语句。

迭代变量可以使用短变量声明(:=)。这种情况,它们的类型设置为相应迭代值的类型,它们的域是到for语句的结尾,它们在每一次迭代中复用。
如果迭代变量是在for语句外声明的,那么执行之后它们的值是最后一次迭代的值。


var testdata *struct {
a *[7]int
}
for i, _ := range testdata.a {
// testdata.a is never evaluated; len(testdata.a) is constant
// i ranges from 0 to 6
f(i)
}

var a [10]string
for i, s := range a {
// type of i is int
// type of s is string
// s == a[i]
g(i, s)
}

var key string
var val interface {} // value type of m is assignable to val
m := map[string]int{"mon":0, "tue":1, "wed":2, "thu":3, "fri":4, "sat":5, "sun":6}
for key, val = range m {
h(key, val)
}
// key == last map key encountered in iteration
// val == map[key]

var ch chan Work = producer()
for w := range ch {
doWork(w)
}

// empty a channel
for range ch {}

for语句的内部实现-array


golang的for语句,对于不同的格式会被编译器编译成不同的形式,如果要弄明白需要看
golang的编译器和相关数据结构的源码,
数据结构源码还好,但是编译器是用C++写的,本人C++是个弱鸡,这里只讲array内部实现


// The loop we generate:
// len_temp := len(range)
// range_temp := range
// for index_temp = 0; index_temp < len_temp; index_temp++ {
// value_temp = range_temp[index_temp]
// index = index_temp
// value = value_temp
// original body
// }

// 例如代码:
array := [2]int{1,2}
for k,v := range array {
f(k,v)
}

// 会被编译成:
len_temp := len(array)
range_temp := array
for index_temp = 0; index_temp < len_temp; index_temp++ {
value_temp = range_temp[index_temp]
k = index_temp
v = value_temp
f(k,v)
}

所以像遍历一个数组,最后生成的代码很像C语言中的遍历,而且有两个临时变量index_temp,value_temp,
在整个遍历中一直复用这两个变量。所以会导致开头问题2的问题(详细解答会在后边)。


问题解答




  1. for语句一共有多少种表达式格式?

    这个问题应该很简单了,上面的规范中就有答案了,一共有3种:


    Condition = Expression .
    ForClause = [ InitStmt ] ";" [ Condition ] ";" [ PostStmt ] .
    RangeClause = [ ExpressionList "=" | IdentifierList ":=" ] "range" Expression .



  2. for语句中临时变量是怎么回事?(为什么有时遍历赋值后,所有的值都等于最后一个元素)

    先看这个例子:


    var a = make([]*int, 3)
    for k, v := range []int{1, 2, 3} {
    a[k] = &v
    }
    for i := range a {
    fmt.Println(*a[i])
    }
    // result:
    // 3
    // 3
    // 3

    for语句的内部实现-array可以知道,即使是短声明的变量,在for循环中也是复用的,这里的v一直
    都是同一个零时变量,所以&v得到的地址一直都是相同的,如果不信,你可以打印该地址,且该地址最后存的变量等于最后一次循环得到的变量,
    所以结果都是3。




  3. range后面支持的数据类型有哪些?

    共5个,分别是数组,数组指针,slice,字符串,map和channel




  4. range string类型为何得到的是rune类型?

    这个问题在for规范中也有解答,对于字符串类型,在连续迭代中,下标值会是下一个utf-8代码点的第一个字节的下标,而第二个值类型是rune。
    如果迭代遇到了一个非法的Unicode序列,那么第二个值是0xFFFD,也就是Unicode的替换字符,然后下一次迭代只会前进一个字节。


    其实看完这句话,我没理解,当然这句话告诉我们了遍历string得到的第二个值类型是rune,但是为什么是rune类型,而不是string或者其他类型?
    后来在看了Rob Pike写的blogStrings, bytes, runes and characters in Go
    才明白点,首先需要知道runeint32的别名,且go语言中的字符串字面量始终保存有效的UTF-8序列。而UTF-8就是用4字节来表示Unicode字符集。
    所以go的设计者用rune表示单个字符的编码,则可以完成容纳所表示Unicode字符。举个例子:


    s := `汉语ab`
    fmt.Println("len of s:", len(s))
    for index, runeValue := range s {
    fmt.Printf("%#U starts at byte position %d\n", runeValue, index)
    }
    // result
    // len of s: 8
    // U+6C49 '汉' starts at byte position 0
    // U+8BED '语' starts at byte position 3
    // U+0061 'a' starts at byte position 6
    // U+0062 'b' starts at byte position 7

    根据结果得知,s的长度是为8字节,一个汉子占用了3个字节,一个英文字母占用一个字节,而程序go程序是怎么知道汉子占3个字节,而
    英文字母占用一个字节,就需要知道utf-8代码点的概念,这里就不深究了,知道go是根据utf-8代码点来知道该字符占了多少字节就ok了。




  5. 遍历slice的时候增加或删除数据会怎么样?

    for语句的内部实现-array可以知道,获取slice的长度只在循环外执行了一次,
    该长度决定了遍历的次数,不管在循环里你怎么改。但是对索引求值是在每次的迭代中求值的,如果更改了某个元素且
    该元素还未遍历到,那么最终遍历得到的值是更改后的。删除元素也是属于更改元素的一种情况。


    在slice中增加元素,会更改slice含有的元素,但不会更改遍历次数。


    a2 := []int{0, 1, 2, 3, 4}
    for i, v := range a2 {
    fmt.Println(i, v)
    if i == 0 {
    a2 = append(a2, 6)
    }
    }
    // result
    // 0 0
    // 1 1
    // 2 2
    // 3 3
    // 4 4

    在slice中删除元素,能删除该元素,但不会更改遍历次数。


    // 只删除该元素1,不更改slice长度
    a2 := []int{0, 1, 2, 3, 4}
    for i, v := range a2 {
    fmt.Println(i, v)
    if i == 0 {
    copy(a2[1:], a2[2:])
    }
    }
    // result
    // 0 0
    // 1 2
    // 2 3
    // 3 4
    // 4 4

    // 删除该元素1,并更改slice长度
    a2 := []int{0, 1, 2, 3, 4}
    for i, v := range a2 {
    fmt.Println(i, v)
    if i == 0 {
    copy(a2[1:], a2[2:])
    a2 = a2[:len(a2)-2] //将a2的len设置为3,但并不会影响临时slice-range_temp
    }
    }
    // result
    // 0 0
    // 1 2
    // 2 3
    // 3 4
    // 4 4



  6. 遍历map的时候增加或删除数据会怎么样?

    规范中也有答案,map元素在迭代过程中被删掉了,那么对应的迭代值不会再产生。
    如果map元素在迭代中插入了,则该元素可能在迭代过程中产生,也可能被跳过。


    在遍历中删除元素


    m := map[int]int{1: 1, 2: 2, 3: 3, 4: 4, 5: 5}
    del := false
    for k, v := range m {
    fmt.Println(k, v)
    if !del {
    delete(m, 2)
    del = true
    }
    }
    // result
    // 4 4
    // 5 5
    // 1 1
    // 3 3

    在遍历中增加元素,多执行几次看结果


    m := map[int]int{1: 1, 2: 2, 3: 3, 4: 4, 5: 5}
    add := false
    for k, v := range m {
    fmt.Println(k, v)
    if !add {
    m[6] = 6
    m[7] = 7
    add = true
    }
    }
    // result1
    // 1 1
    // 2 2
    // 3 3
    // 4 4
    // 5 5
    // 6 6

    // result2
    // 1 1
    // 2 2
    // 3 3
    // 4 4
    // 5 5
    // 6 6
    // 7 7

    在map遍历中删除元素,将会删除该元素,且影响遍历次数,在遍历中增加元素则会有不可控的现象出现,有时能遍历到新增的元素,
    有时不能。具体原因下次分析。




参考


https://golang.org/ref/spec#For_statements

https://github.com/golang/go/wiki/Range

https://blog.golang.org/strings

一个gRPC-go范例程序

changjixiong 发表了文章 • 0 个评论 • 227 次浏览 • 6 天前 • 来自相关话题

grpc-go的介绍:

The Go implementation of gRPC: A high performance, open source, general RPC framework that puts mobi... 			查看全部
					

grpc-go的介绍:


The Go implementation of gRPC: A high performance, open source, general RPC framework that puts mobile and HTTP/2 first.

github地址


https://github.com/grpc/grpc-go


注意看readme中的安装方式 go get -u google.golang.org/grpc,此处可能需要全局科学上网。


google提供的范例


https://github.com/grpc/grpc-go/tree/master/examples


写个测试程序试试,完整的代码见https://github.com/changjixion ... notes


创建子目录hello,在目录hello中创建一个hello.proto文件,对着exmaples中的helloworld依葫芦画瓢:


syntax = "proto3";

package hello;

service Hello {
rpc SayHello (HelloRequest) returns (HelloReply) {}
}

message HelloRequest {
int32 num = 1;
}

message HelloReply {
string message = 1;
}

在目录grpcnotes下执行protoc -I hello/ hello/hello.proto --go_out=plugins=grpc:hello


生成hello.pb.go文件


按同样的步骤在创建一个world。


创建子目录echo,在目录echo中创建一个echo.proto文件,对着exmaples/route_guide/routeguide/route_guide.proto文件抄一个stream模式的定义:


syntax = "proto3";

package echo;

service Echo {
rpc SayEcho (EchoRequest) returns (EchoReply) {}
rpc SayEchoS(stream EchoRequest) returns (stream EchoReply) {}
}

message EchoRequest {
int32 num = 1;
}

message EchoReply {
string message = 1;
}

在目录grpcnotes下执行protoc -I echo/ echo/echo.proto --go_out=plugins=grpc:echo


生成echo.pb.go文件。


按照exmaples/helloworld/greeter_server/main.go的例子抄一个,由于这里有3个protobuf对象,所以代码略有不同:


s := grpc.NewServer(
grpc.KeepaliveParams(keepalive.ServerParameters{}),
grpc.MaxConcurrentStreams(10000))
pb.RegisterHelloServer(s, &serverHello{})
pb2.RegisterWorldServer(s, &serverWorld{})
pb3.RegisterEchoServer(s, &serverEcho{})
// Register reflection service on gRPC server.
reflection.Register(s)

client参考exmaples/helloworld/greeter_client/main.go,增加了并发调用,参数风格参考ab测试


编译服务器程序命名为grpcserver,编译客户端程序命名为grpcclient。


运行 ./grpcserver


运行 ./grpcclient -c 1000 -n 100000


输出


550 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
400 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
500 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
50 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
900 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
150 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
300 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
200 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
650 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
250 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
750 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
600 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
800 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
350 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
0 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
850 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
950 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
450 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
100 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
700 sendokNum: 100 recvokNum 100 sendErrNum: 0 recvErrNum: 0 emptyNum: 0
cost: 3.96848528s

Gopher China 2018 大会来了,早鸟票开放

appleboy 回复了问题 • 9 人关注 • 6 个回复 • 711 次浏览 • 2018-01-11 09:04 • 来自相关话题

机器学习和算法

keke001 发表了文章 • 3 个评论 • 198 次浏览 • 2018-01-09 11:27 • 来自相关话题

Deeplearning Algorithms tutorial

github地址

最近... 查看全部

Deeplearning Algorithms tutorial


github地址


最近以来一直在学习机器学习和算法,然后自己就在不断总结和写笔记,记录下自己的学习AI与算法历程。
机器学习(Machine Learning, ML)是一门多领域交叉学科,涉及概率论、统计学、逼近论、凸分析、算法复杂度理论等多门学科。专门研究计算机怎样模拟或实现人类的学习行为,以获取新的知识或技能,重新组织已有的知识结构使之不断改善自身的性能。



  • 机器学习是计算机科学的一个子领域,在人工智能领域,机器学习逐渐发展成模式识别和计算科学理论的研究。

  • 机器学习:多领域交叉学科,涉及概率论统计学,逼近论,凸分析,算法复杂度理论等多门学科。

  • 机器学习的应用:语音识别,自动驾驶,语言翻译,计算机视觉,推荐系统,无人机,识别垃圾邮件,人脸识别,电商推荐系统。

  • 机器学习的基本概念:训练集,测试集,特征值,监督学习,非监督学习,分类,回归


目前国内在AI感知层面应用已经百花齐放,主要是无人驾驶、智能音箱、嵌入式。但在认知层面还是比较缺乏,所以新入行的AI应用团队可以放在认知层。如开头所述,认知层最重要的是算法,因此需要阅读Nature上最领先的算法公司DeepMind的几篇大作,如下:



  • 2016.01.Mastering the game of Go with deep neural networks and tree search

  • 2016.10.Hybrid computing using a neural network with dynamic external memory

  • 2017.10.Mastering the game of Go without human knowledge


机器学习步骤框架



  • 把数据拆分为训练集合测试集

  • 用训练集合训练集的特征向量来训练算法

  • 用学习来的算法运用在测试集上累评估算法(可能要设计到调整参数(parameter tuning) 用来验证集(validation set))


深度学习


深度学习:深度学习是基于机器学习延伸出来的一个新的领域,由以人大脑结构为启发的神经网络算法为起源加之模型结构深度的增加发展,并伴随大数据和计算能力的提高而产生的一系列新的算法。
深度学习的方向:被应用在图像处理与计算机视觉,自然语言处理以及语音识别等领域。



机器学习算法概览


从2016年起,机器学习有了新的突破和发展。但是,有效的机器学习是困难的,因为机器学习本身就是一个交叉学科,没有科学的方法及一定的积累很难入门。
从2017年10月19日,Nature上发表了新一代AlphaGo版本AlphaGo Zero的技术论文。指出一种仅基于强化学习的算法,AlphaGo Zero不使用人类的数据、指导或规则以外的领域知识成了自己的老师。DeepMind代表了目前人工智能领域最强的技术,其核心是两个字:算法。
很多人都想成为一个AI开发者,不仅是因为AI开发的薪资高,更主要是因为AI这几年的快速发展,但是因为AI本身的门槛就比较高,很多人可能就会比较徘徊,因而想把自己学习AI的过程写成本书,供大家参考和学习!



机器学习的基础



  • 机器学习需要的理论基础:数学,线性代数,数理统计,概率论,高等数学、凸优化理论,形式逻辑等


参考书籍


很有可能是 Github 上第一份完整的 LeetCode 算法题 Go 语言解答。100% 完成度,100% 代码覆盖率。

aQua 发表了文章 • 9 个评论 • 482 次浏览 • 2018-01-09 11:19 • 来自相关话题

半年前,学完 Go 语法,开始刷 LeetCode。到昨天晚上,终于刷完所有能用 Go 解答的算法题,并保证了 100% 的代码覆盖率。

所有的代码都尽量选用最优解,外加中文注释,并使用 Go style 编程。 希望可以帮助到想用 Go ... 查看全部

半年前,学完 Go 语法,开始刷 LeetCode。到昨天晚上,终于刷完所有能用 Go 解答的算法题,并保证了 100% 的代码覆盖率。


所有的代码都尽量选用最优解,外加中文注释,并使用 Go style 编程。
希望可以帮助到想用 Go 刷 LeetCode 的人。


欢迎大家开 issue 来 diss 我的代码。
更感谢大家可以来帮忙加 star。


项目地址:很有可能是 Github 上第一份完整的 LeetCode 算法题 Go 语言解答。

《Go语言高级编程》开源免费图书

chai2010 发表了文章 • 5 个评论 • 427 次浏览 • 2018-01-07 01:53 • 来自相关话题

https://github.com/chai2010/advanced-go-programming-book


仿着谢大 go web 也建了个开源图书 :D

Redis 集群搭建及使用Golang示例

pathbox 发表了文章 • 5 个评论 • 303 次浏览 • 2018-01-05 11:55 • 来自相关话题

Redis 在3.x版本之后,自身支持了集群模式。Redis的集群主要是 master-slave的形式。集群定义了 16384个hash slot。这些hash slot分布在所有master上。查看全部

Redis 在3.x版本之后,自身支持了集群模式。Redis的集群主要是 master-slave的形式。集群定义了
16384个hash slot。这些hash slot分布在所有master上。we simply take the CRC16 of the key modulo 16384 将key计算得到对应的hash slot的值,然后看这个hash slot在哪个redis服务上,这个key就会保存在对应的这个redis服务。和Elasticsearch、MongoDB不一样,redis通过这种方式进行sharding。不能不说这是一种简单,但对redis来说是有效的一种集群方式。能够最大限度的保留原有redis的属性。
[Document]


下面是部署过程的例子。


有三台服务器 A、B、C。外网ip分别为 A_IP,B_IP,C_IP。


三台服务器先卸载原有redis版本,安装最新的redis版本


$ wget http://download.redis.io/relea ... ar.gz
$ tar xzf redis-4.0.6.tar.gz
$ cd redis-4.0.6
$ make

cd /src
cp redis-server /usr/bin/
cp redis-cli /usr/bin/
cp redis-trib.rb /usr/bin/

mkdir redis-cluster
cd redis-cluster
mkdir 7000
mkdir 7100

进入7000,新建redis.conf 文件,编写配置文件


cd 7000
vim redis.conf

配置内容为


port 7000                      # 绑定的端口
bind A_IP # 绑定的IP,如果你要开放出去,需要使用外网IP
cluster-enabled yes # 是否启用集群模式
cluster-config-file nodes.conf # 节点配置文件
cluster-node-timeout 5000 # 超时设置
appendonly yes # appendonly 配置 持久化设置
cluster-require-full-coverage no

cluster-require-full-coverage默认是yes。如果某个redis挂了,没有对应的slave升级为master,这时候,整个redis集群不可用。


所以建议设为no, 这样某个redis挂了,只是影响这一部分的hash slot查询有问题,不影响集群的其他redis的读写。


同理完成 7100的redis.conf配置文件内容。将三台机器都安装上述进行安装配置。


在A、B、C机器上


cd 7000
redis-server redis.conf
cd 7001
redis-server redis.conf

到A机器上, 执行


redis-trib.rb create --replicas 1 A_IP:7000 B_IP:7000 \
C_IP:7000 A_IP:7100 B_IP:7100 C_IP:7100

之后会提示输入yes,执行完后。


redis-cli -p 7000 -h A_IP cluster nodes

查看集群状态,会得到类似这样的信息。


aedabb4bd830978905d68ab8d88a94a031b515fe A_IP:7000@17000 master - 0 1513840545000 2 connected 5461-10922
861ff3beac7a6a3023424a64446f1101f8193d9d A_IP:7100@17100 slave 3d9e37599f6547387503e165d21838556435bac4 0 1513840544011 4 connected
3d9e37599f6547387503e165d21838556435bac4 B_IP:7000@17000 myself,master - 0 1513840545000 1 connected 0-5460
3ef82f387086931dbdedbd24b379671b8bd03289 C_IP:7000@17000 master - 0 1513840544411 3 connected 10923-16383
27764a4a521e4e5bd8e2e108c472ef6ed51645cd B_IP:7100@17100 slave aedabb4bd830978905d68ab8d88a94a031b515fe 0 1513840543912 5 connected
7f6ef6f05968ee869359c0771d42d768243e4ca1 C_IP:7100@17100 slave 3ef82f387086931dbdedbd24b379671b8bd03289 0 1513840545414 6 connected

Bingo~ redis集群服务就跑起来了。 三台机器,每台机器一个master,一个slave


redis集群增加节点或Resharding操作是使用redis-trib脚本命令,这个脚本是Ruby写的。每个node有一个node ID,比如: aedabb4bd830978905d68ab8d88a94a031b515fe


Golang 代码示例


package redis

import (
"time"

"github.com/go-redis/redis"
)

// redis数据超时时间
var Timeout = 5 * time.Hour

var Client = GetClusterClient()

func GetClusterClient() *redis.ClusterClient {
var client *redis.ClusterClient
client = redis.NewClusterClient(&redis.ClusterOptions{
Addrs: []string{"A_IP:7000", "B_IP:7000", "C_IP:7000","A_IP:7100", "B_IP:7100", "C_IP:7100"},
})
err := client.Ping().Err()
if err == nil {
log.Info("Redis cluster OK")
} else {
log.Error("Redis cluster wrong")
}
return client
}

redis.Client.Set("word", "Hello World", redis.Timeout)
str, _ := rediscluster.Client.Get("word").Result()
fmt.Println(str) // => "Hello World"

文件描述符、打开文件表以及inode

changjixiong 发表了文章 • 0 个评论 • 195 次浏览 • 2018-01-04 22:02 • 来自相关话题

linux系统相关书籍中关于文件描述符及相关内容,通常会出现一张类似这样的图

查看全部

linux系统相关书籍中关于文件描述符及相关内容,通常会出现一张类似这样的图


(filegraph1)


或者这样的图


(filegraph2)


第一个图来自Michael Kerrisk的《Linux/UNIX系统编程手册》,第二个图来自《UNIX环境高级编程》(也就是APUE)。


文中对相关信息做了论述并且配上了上面这样的图,但是我相信很多人看完以后觉得好像懂了,那么请尝试想一想一下几个问题:


1.假如创建了文件a得到了文件描述符fa1,并且正在写入的过程中再次打开文件a得到了文件描述符fa2。这个时候通过fa2对文件重命名,会有什么结果。


2.fa2对文件重命名后,通过fa1获得的文件名是原来的文件名还是修改后的文件名。


3.fa1能否继续写入。


如果以上3个问题完全没有疑惑,说明已经对文件描述符及相关内容掌握的非常清楚,可以不用继续看下去了。如果还有疑问,那么请看下面这段代码


func main() {

fileOldName := "rotate.log"
fileRename := "rotate1.log"
file, _ := os.OpenFile(fileOldName, os.O_CREATE|os.O_WRONLY|os.O_APPEND, os.FileMode(0644))

fmt.Println("open file", fileOldName)
for i := 0; i < 5; i++ {
file.WriteString(fmt.Sprintf("%v line: %d\n", time.Now(), i))
}

var statOldFile syscall.Stat_t
if err := syscall.Stat(fileOldName, &statOldFile); err != nil {
panic(err)
}

fmt.Println(fileOldName, "statOldFile.Ino:", statOldFile.Ino)

err := os.Rename(fileOldName, fileRename)
if err != nil {
fmt.Println("file rename Error:", err)
}

fmt.Println("rename ", fileOldName, "->", fileRename)

var statRenamedFile syscall.Stat_t
if err := syscall.Stat(fileRename, &statRenamedFile); err != nil {
panic(err)
}

fmt.Println("fileRename", "statRenamedFile.Ino:", statRenamedFile.Ino)

fmt.Println("file.Name:", file.Name())

for i := 5; i < 10; i++ {
file.WriteString(fmt.Sprintf("%v line: %d\n", time.Now(), i))
}

fileOld, err := os.OpenFile(fileOldName, os.O_WRONLY|os.O_APPEND, os.FileMode(0644))

if nil != err {
fmt.Println(fileOldName, " open error:", err)
} else {
fmt.Println("fileOld.Name:", fileOld.Name())
}

}

运行后的输出为


open file rotate.log
rotate.log statOldFile.Ino: 28567907
rename rotate.log -> rotate1.log
fileRename statRenamedFile.Ino: 28567907
file.Name: rotate.log
rotate.log open error: open rotate.log: no such file or directory

rotate1.log文件中有10行记录。


上面的代码首先创建了一个名为rotate.log的文件,然后写入了5行记录,打印出文件的inodeID 28567907,然后将文件重命名为rotate1.log,再次打印文件的inodeID 28567907,继续写入5条记录,打印文件对象的名字,最后再次打开文件rotate.log,提示文件不存在。


现在来分析一下程序的逻辑:



  1. 创建并打开文件rotate.log其inodeID为28567907,file对象中包含了一个文件描述符姑且称为FA1,指向一个文件表项姑且称为FT1,FT1指向一个V节点项。此V节点项的文件就是inodeID为28567907的rotate.log文件,写入了5条记录。

  2. 利用rename将文件rotate.log重命名为rotate1.log。实际就是打开了一个文件描述符FA2,指向文件表FT2,FT2指向inodeID为28567907的V节点项,也就是前文的rotate.log,将V节点中这个文件的文件名属性修改为rotate1.log,文件还是那个文件,因此打印出来的inodeID还是那个inodeID。

  3. file对象再次写入5条记录,FA1没变,FT1没变,V节点中的inode没变,仅仅只是文件名属性变了,因此记录依然写入了原来那个文件,虽然文件名已经变了。

  4. 打印file对象的文件名属性,显示是修改前的名字,因为file对象的name属性在创建时已经赋值,即使V节点中的文件名已经修改,file对象中的name不会变。

  5. 再次打开rotate.log会发现已经打不开了,因为这个文件已经不存在了,毕竟V节点中这个文件现在已经叫rotate1.log了。

  6. rotate1.log中有10行记录,其中5行是改名前写入的,5条是改名后写入的。


通过这个例子,大概能明白文章开头的两个图了吧。


为啥文件名叫rotate.log?因为logrotate就是重命名后再次用原名创建新文件继续写的。


更多golang示例代码见 https://github.com/changjixiong/goNotes

gRPC如何进行文件上传

zsy619 回复了问题 • 4 人关注 • 3 个回复 • 318 次浏览 • 2018-01-04 13:45 • 来自相关话题

Go语言经典笔试题

huazhihao 发表了文章 • 0 个评论 • 408 次浏览 • 2018-01-02 00:40 • 来自相关话题