shen100

这家伙很懒,什么个性签名都没有留下

  • 话题
  • 回复
  • 参与的投票
  • 收藏

他的话题

Golang动手写一个Http Proxy

转载于: http://yangxikun.github.io/http/2017/09/16/http-proxy.html

本文主要使用Golang实现一个可用但不够标准,支持basic authentication的http代理服务。
为何说不够标准,在HTTP/1.1 RFC中,有些关于代理实现标准的条目在本文中不考虑。

Http Proxy是如何代理我们的请求

Http 请求的代理如下图,Http Proxy只需要将接收到的请求转发给服务器,然后把服务器的响应,转发给客户端即可。

Https 请求的代理如下图,客户端首先需要发送一个Http CONNECT请求到Http Proxy,Http Proxy建立一条TCP连接到指定的服务器,然后响应200告诉客户端连接建立完成,之后客户端就可以与服务器进行SSL握手和传输加密的Http数据了。

为何需要CONNECT请求? 因为Http Proxy不是真正的服务器,没有www.foo.com 的证书,不可能以www.foo.com 的身份与客户端完成SSL握手从而建立Https连接。 所以需要通过CONNECT请求告诉Http Proxy,让Http Proxy与服务器先建立好TCP连接,之后客户端就可以将SSL握手消息发送给Http Proxy,再由Http Proxy转发给服务器,完成SSL握手,并开始传输加密的Http数据。

Basic Authentication

为了保护Http Proxy不被未授权的客户端使用,可以要求客户端带上认证信息。这里以Basic Authentication为例。

客户端在与Http Proxy建立连接时,Http请求头中需要带上:

Proxy-Authorization: Basic YWxhZGRpbjpvcGVuc2VzYW1l

如果服务端验证通过,则正常建立连接,否则响应:

HTTP/1.1 407 Proxy Authentication Required\r\nProxy-Authenticate: Basic realm="*"

所需要开发的功能模块

  1. 连接处理
  2. 从客户端请求中获取服务器连接信息
  3. 基本认证
  4. 请求转发

连接处理

需要开发一个TCP服务器,因为HTTP服务器没法实现Https请求的代理。
Server的定义:

type Server struct {
	listener   net.Listener
	addr       string
	credential string
}

通过Start方法启动服务,为每个客户端连接创建goroutine为其服务:

// Start a proxy server
func (s *Server) Start() {
	var err error
	s.listener, err = net.Listen("tcp", s.addr)
	if err != nil {
		servLogger.Fatal(err)
	}

    if s.credential != "" {
        servLogger.Infof("use %s for auth\n", s.credential)
    }
	servLogger.Infof("proxy listen in %s, waiting for connection...\n", s.addr)

	for {
		conn, err := s.listener.Accept()
		if err != nil {
			servLogger.Error(err)
			continue
		}
		go s.newConn(conn).serve()
	}
}

从客户端请求中获取服务器连接信息

对于http请求头的解析,参考了golang内置的http server。
getTunnelInfo用于获取:

  1. 请求头
  2. 服务器地址
  3. 认证信息
  4. 是否https请求

// getClientInfo parse client request header to get some information:
func (c *conn) getTunnelInfo() (rawReqHeader bytes.Buffer, host, credential string, isHttps bool, err error) {
	tp := textproto.NewReader(c.brc)

	// First line: GET /index.html HTTP/1.0
	var requestLine string
	if requestLine, err = tp.ReadLine(); err != nil {
		return
	}

	method, requestURI, _, ok := parseRequestLine(requestLine)
	if !ok {
		err = &BadRequestError{"malformed HTTP request"}
		return
	}

	// https request
	if method == "CONNECT" {
		isHttps = true
		requestURI = "http://" + requestURI
	}

	// get remote host
	uriInfo, err := url.ParseRequestURI(requestURI)
	if err != nil {
		return
	}

	// Subsequent lines: Key: value.
	mimeHeader, err := tp.ReadMIMEHeader()
	if err != nil {
		return
	}

	credential = mimeHeader.Get("Proxy-Authorization")

	if uriInfo.Host == "" {
		host = mimeHeader.Get("Host")
	} else {
		if strings.Index(uriInfo.Host, ":") == -1 {
			host = uriInfo.Host + ":80"
		} else {
			host = uriInfo.Host
		}
	}

	// rebuild http request header
	rawReqHeader.WriteString(requestLine + "\r\n")
	for k, vs := range mimeHeader {
		for _, v := range vs {
			rawReqHeader.WriteString(fmt.Sprintf("%s: %s\r\n", k, v))
		}
	}
	rawReqHeader.WriteString("\r\n")
	return
}

基本认证

// validateCredentials parse "Basic basic-credentials" and validate it
func (s *Server) validateCredential(basicCredential string) bool {
	c := strings.Split(basicCredential, " ")
	if len(c) == 2 && strings.EqualFold(c[0], "Basic") && c[1] == s.credential {
		return true
	}
	return false
}

请求转发

serve方法会进行Basic Authentication验证,对于http请求的代理,会把请求头转发给服务器,对于https请求的代理,则会响应200给客户端。

// serve tunnel the client connection to remote host
func (c *conn) serve() {
    defer c.rwc.Close()
	rawHttpRequestHeader, remote, credential, isHttps, err := c.getTunnelInfo()
	if err != nil {
		connLogger.Error(err)
		return
	}

	if c.auth(credential) == false {
		connLogger.Error("Auth fail: " + credential)
		return
	}

	connLogger.Info("connecting to " + remote)
	remoteConn, err := net.Dial("tcp", remote)
	if err != nil {
		connLogger.Error(err)
		return
	}

	if isHttps {
		// if https, should sent 200 to client
		_, err = c.rwc.Write([]byte("HTTP/1.1 200 Connection established\r\n\r\n"))
		if err != nil {
			glog.Errorln(err)
			return
		}
	} else {
		// if not https, should sent the request header to remote
		_, err = rawHttpRequestHeader.WriteTo(remoteConn)
		if err != nil {
			connLogger.Error(err)
			return
		}
	}

	// build bidirectional-streams
	connLogger.Info("begin tunnel", c.rwc.RemoteAddr(), "<->", remote)
	c.tunnel(remoteConn)
    connLogger.Info("stop tunnel", c.rwc.RemoteAddr(), "<->", remote)
}

完整代码可查看:https://github.com/yangxikun/gsproxy

阅读全文

发送邮件

申请域名邮箱

golang123使用的是QQ域名邮箱

修改配置

打开config.json文件,修改其中的配置

{
  "go": {
    "MailUser"  : "test@example.com", /*域名邮箱账号*/
    "MailPass"  : "12345678",            /*域名邮箱密码*/
    "MailHost"  : "smtp.qq.com",         /*smtp邮箱域名*/
    "MailPort"   : 465,                       /*smtp邮箱端口*/
    "MailFrom"  : "abc",                    /*邮件来源*/
      ...
  }
}

发送邮件

import "github.com/shen100/golang123/controller/mail"

email   := "abc@test.com"  //给abc@test.com邮箱发邮件
title      := "邮件标题"
content := "这是邮件内容"
mail.SendMail(email, title, content)

阅读全文

Go语言 | Go 1.9 新特性 Type Alias详解

北京时间2017.08.25,Go1.9正式版发布了。Go1.9经历了2个beta,好几个月,终于定了,发布了正式版本。Go 1.9包含了很多改变,比如类型别名Type Alias,安全并发Map,并行编译等,都是很大的改变,今天这篇文章主要介绍类型别名 Type Alias。

安装go 1.9

很多众所周知的原因,大家可能无法下载最新的go 1.9 sdk,如果你没有梯子,可以到我自建的这个镜像网站下载,有很多常用的开发软件,其中就包含最新的go 1.9。镜像地址:http://mirrors.flysnow.org/

作用

type alias这个特性的主要目的是用于已经定义的类型,在package之间的移动时的兼容。比如我们有一个导出的类型flysnow.org/lib/T1 ,现在要迁移到另外一个package中, 比如flysnow.org/lib2/T1中。

没有type alias的时候我们这么做,就会导致其他第三方引用旧的package路径的代码,都要统一修改,不然无法使用。

有了type alias就不一样了,类型T1的实现我们可以迁移到lib2下,同时我们在原来的lib下定义一个lib2下T1的别名,这样第三方的引用就可以不用修改,也可以正常使用,只需要兼容一段时间,再彻底的去掉旧的package里的类型兼容,这样就可以渐进式的重构我们的代码,而不是一刀切。

//package:flysnow.org/lib
type T1=lib2.T1

type alias vs defintion

我们基于一个类型创建一个新类型,称之为defintion;基于一个类型创建一个别名,称之为alias,这就是他们最大的区别。

type MyInt1 int
type MyInt2 = int

第一行代码是基于基本类型int创建了新类型MyInt1,第二行是创建的一个int的类型别名MyInt2,注意类型别名的定义是=。

var i int =0
var i1 MyInt1 = i //error
var i2 MyInt2 = i
fmt.Println(i1,i2)

仔细看这个示例,第二行把一个int类型的变量i,赋值给MyInt1类型的变量i1会被提示编译错误:类型无法转换。但是第三行把int类型的变量i,赋值给MyInt2类型的变量i2就可以,不会提示错误。

从这个例子也可以看出来,这两种定义方式的不同,因为Go是强类型语言,所以类型之间的转换必须强制转换,因为int和MyInt1是不同的类型,所以这里会报编译错误。

但是因为MyInt2只是int的一个别名,本质上还是一个int类型,所以可以直接赋值,不会有问题。

类型方法

每个类型都可以通过接受者的方式,添加属于它自己的方法,我们看下通过type alias的类型是否可以,以及拥有哪些方法。

type MyInt1 int
type MyInt2 = int

func (i MyInt1) m1(){
    fmt.Println("MyInt1.m1")
}
func (i MyInt2) m2(){
    fmt.Println("MyInt2.m2")
}
func main() {
    var i1 MyInt1    
    var i2 MyInt2
    i1.m1()
    i2.m2()
}

以上示例代码看着是没有任何问题,但是我们编译的时候会提示:

i2.m2 undefined (type int has no field or method m2)
cannot define new methods on non-local type int

这里面有2个错误,一个是提示类型int没有m2这个方法,所以我们不能调用,因为MyInt2本质上就是int。

第二个错误是我们不能为int类型添加新方法,什么意思呢?因为int是一个非本地类型,所以我们不能为其增加方法。既然这样,那我们自定义个struct类型试试。

type User struct {

}
type MyUser1 User
type MyUser2 = User

func (i MyUser1) m1(){
    fmt.Println("MyUser1.m1")
}
func (i MyUser2) m2(){
    fmt.Println("MyUser2.m2")
}
//Blog:www.flysnow.org
//Wechat:flysnow_org
func main() {
    var i1 MyUser1    
    var i2 MyUser2
    i1.m1()
    i2.m2()
}

换成struct,正常运行。所以本地定义的类型的别名,还是可以为其添加方法的。现在我们接着上面的例子,看一个有趣的现象,我在main函数里增加如下代码:

var i User
i.m2()

然后运行,发现,可以正常运行。是不是很奇怪,我们并没有为类型User 定义方法啊,怎么可以调用呢?这就得益于type alias,MyUser2完全等价于User,所以为MyUser2定义方法,等于就为User定义了方法,反之,亦然。

但是对于新定义的类型MyUser1就不行了,因为它完全是个新类型,所以User的方法,MyUser是没有的。这里不再举例,大家自己可以试试。

还有一点需要注意,因为MyUser2完全等价于User,所以User已经有的方法,MyUser2不能再声明,反之亦然,如果定义了会有如下提示:

./main.go:37:6: User.m1 redeclared in this block
    previous declaration at ./main.go:31:6

其实就是重复声明的意思,不能再次重复声明了。

接口实现

上面的小结我们可以发现,User和MyUser2是等价的,并且其中一个新增了方法,另外一个也会有。那么基于此推导出,一个实现了某个接口,另外一个也会实现。现在验证一下:

type I interface {
    m2()
}
type User struct {

}
type MyUser2 = User

func (i User) m(){
    fmt.Println("User.m")
}
func (i MyUser2) m2(){
    fmt.Println("MyUser2.m2")
}
func main() {
    var u User    
    var u2 MyUser2    
    var i1 I =u    
    var i2 I =u2

    fmt.Println(i1,i2)
}

定义了一个接口I,从代码上看,只有MyUser2实现了它,但是我们代码的演示中,发现User也实现了接口I,所以这就验证了我们的推到是正确的,返回来如果User实现了某个接口,那么它的type alias也同样会实现这个接口。

以上讲了很多示例都是类型struct的别名,我们看下接口interface的type alias是否也是等价的。

type I interface {
    m()
}
type MyI1 I
type MyI2 = I

type MyInt int
func (i MyInt) m(){
    fmt.Println("MyInt.m")
}

定义了一个接口I,MyI1是基于I的新类型;MyI2是I的类型别名;MyInt实现了接口I。下面进行测试。

//Blog:www.flysnow.org
//Wechat:flysnow_org
func main() {
    //赋值实现类型MyInt
    var i I = MyInt(0)    
    var i1 MyI1 = MyInt(0)    
    var i2 MyI2 = MyInt(0)    
    
    //接口间相互赋值
    i = i1
    i = i2
    i1 = i2
    i1 = i
    i2 = i
    i2 = i1
}

以上代码运行是正常的,这个是前面讲的具体类型(struct,int等)的type alias不一样,只要实现了接口,就可以相互赋值,管你是新定义的接口MyI1,还是接口的别名MyI2。

类型的嵌套

我们都知道type alias的两个类型是等价的,但是他们在类型嵌套时有些不一样。

//Blog:www.flysnow.org
//Wechat:flysnow_org
func main() {
    my:=MyStruct{}
    my.T2.m1()
}
type T1 struct {

}
func (t T1) m1(){
    fmt.Println("T1.m1")
}
type T2 = T1
type MyStruct struct {
    T2
}

示例中T2是T1的别名,但是我们把T2嵌套在MyStruct中,在调用的时候只能通过T2这个名称调用,而不能通过T1,会提示没这个字段的。反过来也一样。

这是因为T1,T2是两个名称,虽然他们等价,但他们是具有两个不同名字的等价类型,所以在类型嵌套的时候,就是两个字段。

当然我们可以把T1,T2同时嵌入到MyStrut中,进行分别调用。

//Blog:www.flysnow.org
//Wechat:flysnow_org
func main() {
    my:=MyStruct{}
    my.T2.m1()
    my.T1.m1()
}
type MyStruct struct {
    T2
    T1
}

以上也是可以正常运行的,证明这是具有两个不同名字的,同种类型的字段。

下面我们做个有趣的实验,把main方法的代码改为如下:

//Blog:www.flysnow.org
//Wechat:flysnow_org
func main() {
    my:=MyStruct{}
    my.m1()
}

猜猜是不是可以正常编译运行呢?答应可能出乎意料,是不能正常编译的,提示如下:

./main.go:25:4: ambiguous selector my.m1

其实想想很简单,不知道该调用哪个,太模糊了,匹配不了,不然该用T1的m1,还是T2的m1。这种结果不限于方法,字段也也一样;也不限于type alias,type defintion也是一样的,只要有重复的方法、字段,就会有这种提示,因为不知道该选择哪个。

类型循环

type alias的声明,一定要留意类型循环,不要产生了循环,一旦产生,就会编译不通过,那么什么是类型循环呢。假如type T2 = T1,那么T1绝对不能直接、或者间接的引用到T2,一旦有,就会类型循环。

type T2 = *T2
type T2 = MyStruct
type MyStruct struct {
    T1
    T2
}

以上两种定义都是类型循环,我们自己在使用的过程中,要避免这种定义的出现。

byte and rune

这两个类型一个是int8的别名,一个是int32的别名,在Go 1.9之前,他们是这么定义的。

type byte bytet
ype rune rune

现在Go 1.9有了type alias这个新特性后,他们的定义就变成如下了:

type byte = uint8
type rune = int32

恩,非常很省事和简洁。

导出未导出的类型

type alias还有一个功能,可以导出一个未被导出的类型。

package lib

//Blog:www.flysnow.org
//Wechat:flysnow_org
type user struct {
    name string
    Email string
}
func (u user) getName() string {
    return u.name
}
func (u user) GetEmail() string {
    return u.Email
}
//把这个user导出为User
type User = user

user本身是一个未导出的类型,不能被其他package访问,但是我们可以通过type User = user,定义一个User,这样这个User就可以被其他package访问了,可以使用user类型导出的字段和方法,示例中是Email字段和GetEmail方法,另外未被导出name字段和getName方法是不能被其他package使用的。

小结

type alias的定义,本质上是一样的类型,只是起了一个别名,源类型怎么用,别名类型也怎么用,保留源类型的所有方法、字段等。

阅读全文

Go语言环境搭建详解

作者: 飞雪无情
转载于: https://mp.weixin.qq.com/s/VgA6OZ7NZFMg7AOsjLLZ-Q

最近写了很多Go语言的原创文章,其中Go语言实战系列30篇,近15W字,还有最近更新的Go经典库系列,不过通过大家的咨询来看,还是想要一些入门的知识,这一篇文章写于2017年初,这里再更新一下,发给大家。

有读者来信(微信公众号消息)说能不能写一篇关于Go语言环境的配置搭建,这样对于想学Go语言的可以快速的配置起来一个环境。这个的确是我忽略了,按照我写书的逻辑,也是先有环境搭建,才能有语言功能介绍,这个直接把Go语言的开发环境搭建等配置跳过去实在不应该,所以这篇特意针对Go语言的开发环境搭建、配置、编辑器选型、不同平台程序生成等做了详细的介绍。

下载

要搭建Go语言开发环境,我们第一步要下载go的开发工具包,目前最新稳定版本是v1.9,Go1.9增加了一些新特性,我这里有一篇讲Go语言 | Go 1.9 新特性 Type Alias详解 的,大家可以参考。Go为我们所熟知的所有平台架构提供了开发工具包,比如我们熟知的Linux、Mac和Windows,其他的还有FreeBSD等。

我们可以根据自己的机器操作系统选择相应的开发工具包,比如你的是Windows 64位的,就选择windows-amd64的工具包;是Linux 32位的就选择linux-386的工具包。可以自己查看下自己的操作系统,然后选择,Mac的现在都是64位的,直接选择就可以了。

开发工具包又分为安装版和压缩版。安装版是Mac和Windows特有的,他们的名字类似于:

  • go1.9.darwin-amd64.pkg
  • go1.9.windows-386.msi
  • go1.9.windows-amd64.msi

安装版,顾名思义,双击打开会出现安装向导,让你选择安装的路径,帮你设置好环境变量等信息,比较省事方便一些。

压缩版的就是一个压缩文件,可以解压得到里面的内容,他们的名字类似于:

  • go1.9.darwin-amd64.tar.gz
  • go1.9.linux-386.tar.gz
  • go1.9.linux-amd64.tar.gz
  • go1.9.windows-386.zip
  • go1.9.windows-amd64.zip

压缩版我们下载后需要解压,然后自己移动到要存放的路径下,并且配置环境变量等信息,相比安装版来说,比较复杂一些,手动配置的比较多。

根据自己的操作系统选择后,就可以下载开发工具包了,Go语言的官方下载地址是 https://golang.org/dl/ 可以打开选择版本下载,如果该页面打不开,或者打开了下载不了,可以使用镜像网站 http://mirrors.flysnow.org/ ,打开后搜索或者找到Golang,选择相应的版本下载,这个镜像网站会同步更新官方版本,基本上都是最新版,可以放心使用。

Linux下安装

我们以Ubuntu 64位为例进行演示,CentOS等其他Linux发行版大同小异。

下载go1.9.linux-amd64.tar.gz后,进行解压,你可以采用自带的解压软件解压,如果没有可以在终端行使用tar命令行工具解压,我们这里选择的安装目录是/usr/local/go,可以使用如下命令:

tar -C /usr/local -xzf go1.9.linux-amd64.tar.gz

如果提示没有权限,在最前面加上sudo以root用户的身份运行。运行后,在/usr/local/下就可以看到go目录了。如果是自己用软件解压的,可以拷贝到/usr/local/go下,但是要保证你的go文件夹下是bin、src、doc等目录,不要go文件夹下又是一个go文件夹,这样就双重嵌套了。

然后就要配置环境变量了,Linux下又两个文件可以配置,其中/etc/profile是针对所有用户都有效的;$HOME/.profile是针对当前用户有效的,可以根据自己的情况选择。

针对所有用户的需要重启电脑才可以生效;针对当前用户的,在终端里使用source命令加载这个$HOME/.profile即可生效。

source ~/.profile

使用文本编辑器比如VIM编辑他们中的任意一个文件,在文件的末尾添加如下配置保存即可:

export GOROOT=/usr/local/go
export PATH=$PATH:$GOROOT/bin

其中GOROOT环境变量表示我们GO的安装目录,这样其他软件比如我们使用的Go开发IDE就可以自动的找到我们的Go安装目录,达到自动配置Go SDK的目的。

第二句配置是把/usr/local/go/bin这个目录加入到环境变量PATH里,这样我可以在终端里直接输入go等常用命令使用了。而不用再加上/usr/local/go/bin这一串绝对路径,更简洁方便。

以上配置好之后,我们打开终端,属于如下命令,就可以看到go的版本等信息了。

➜  ~ go version
go version go1.9 linux/amd64

这就说明我们已经安装go成功了,如果提示go这个命令找不到,说明我们配置还不对,主要在PATH这个环境变量,仔细检查,直到可以正常输出为止。

Mac下安装

Mac分为压缩版和安装版,他们都是64位的。压缩版和Linux的大同小异,因为Mac和Linux都是基于Unix,终端这一块基本上是相同的。

压缩版解压后,就可以和Linux一样放到一个目录下,这里也以/usr/local/go/为例。在配置环境变量的时候,针对所有用户和Linux是一样的,都是/etc/profile这个文件;针对当前用户,Mac下是$HOME/.bash_profile,其他配置都一样,包括编辑sudo权限和生效方式,最后在终端里测试:

➜  ~ go version
go version go1.9 darwin/amd64

Mac安装版下载后双击可以看到安装界面,按照提示一步步选择操作即可。安装版默认安装目录是/usr/local/go,并且也会自动的把/usr/local/go/bin目录加入到PATH环境变量中,重新打开一个终端,就可以使用go version进行测试了,更快捷方便一些。

Windows下安装

Windows也有压缩版和安装版,又分为32和64位以供选择,不过目前大家都是64位,选择这个更好一些。

Window的压缩版是一个ZIP压缩包,下载后使用winrar等软件就可以解压,解压后要选择一个存放目录,比如c:\Go下,这个c:\Go就是Go的安装目录了,他里面有bin、src、doc等目录。

然后就是环境变量的配置,Window也和Linux一样分为针对所有用户的系统变量,和针对当前用户的用户变量设置,可以自行选择,比如系统变量,针对所有用户都有效。

以Window 7为例,右击我的电脑->属性会打开系统控制面板,然后在左侧找到高级系统设置点击打开,会在弹出的界面最下方看到环境变量按钮,点击它,就可以看到环境变量配置界面了。上半部分是用户变量配置,下半部分是系统变量配置。

我们在系统变量里点击新建,变量名输入GOROOT,变量值是我们刚刚安装的go路径c:\Go,这样就配置好了GO目录的安装路径了。

然后修改PATH系统变量,在变量值里添加%GOROOT%\bin路径,和其他PATH变量以;(分号,Linux下是冒号)分割即可。这样我们就可以在CMD里直接输入go命令使用了。

打开我们的终端,输入go version测试下,好了的话就可以看到输出的信息了。

Window的安装版相比来说就比较简单一些,双击就可以按照提示一步步安装,默认安装路径是c:\Go,并且会配置好PATH环境变量,可以直接打开CMD终端使用。

设置工作目录

工作目录就是我们用来存放开发的源代码的地方,对应的也是Go里的GOPATH这个环境变量。这个环境变量指定之后,我们编译源代码等生成的文件都会放到这个目录下,GOPATH环境变量的配置参考上面的安装Go,配置到/etc/profile或者Windows下的系统变量里。

这个工作目录我们可以根据自己的设置指定,比如我的Mac在$HOME/code/go下,Window的可以放到d:\code\go下等。该目录下有3个子目录,他们分别是:

├── bin
├── pkg
└── src
  • bin文件夹存放go install命名生成的可执行文件,可以把$GOPATH/bin路径加入到PATH环境变量里,就和我们上面配置的$GOROOT/bin一样,这样就可以直接在终端里使用我们go开发生成的程序了。
  • pkg文件夹是存在go编译生成的文件。
  • src存放的是我们的go源代码,不同工程项目的代码以包名区分。

go项目工程结构

配置好工作目录后,就可以编码开发了,在这之前,我们看下go的通用项目结构,这里的结构主要是源代码相应地资源文件存放目录结构。

我们知道源代码都是存放在GOPATH的src目录下,那么多个项目的时候,怎么区分呢?答案是通过包,使用包来组织我们的项目目录结构。有过java开发的都知道,使用包进行组织代码,包以网站域名开头就不会有重复,比如我的个人网站是flysnow.org,我就可以以flysnow.org的名字创建一个文件夹,我自己的go项目都放在这个文件夹里,这样就不会和其他人的项目冲突,包名也是唯一的。

如果没有个人域名,现在流行的做法是使用你个人的github.com,因为每个人的是唯一的,所以也不会有重复。

src
├── flysnow.org
├── github.com
├── golang.org
├── gopkg.in
├── qiniupkg.com
└── sourcegraph.com

如上,src目录下跟着一个个域名命名的文件夹。再以github.com文件夹为例,它里面又是以github用户名命名的文件夹,用于存储属于这个github用户编写的go源代码。

src/github.com/spf13
├── afero
├── cast
├── cobra
├── fsync
├── hugo
├── jwalterweatherman
├── nitro
├── pflag
└── viper

那么我们如何引用一个包呢,也就是go里面的import。其实非常简单,通过包路径,包路径就是从src目录开始,逐级文件夹的名字用/连起来就是我们需要的包名,比如:

import (    
    "github.com/spf13/hugo/commands"
)

Hello World

都准备好了,让我们创建一个hello项目,测试一下。我的项目的路径为src/flysnow.org/hello/, 在hello目录下创建文件main.go。

package main

import (
    "fmt"
)

func main() {
    fmt.Println("Hello World")
}

Go版Hello World非常简单。在src/flysnow.org/hello/目录下运行go run main.go命令就可以看到打印的输出Hello World,下面解释下这段代码。

  1. package 是一个关键字,定义一个包,和Java里的package一样,也是模块化的关键。
  2. main包是一个特殊的包名,它表示当前是一个可执行程序,而不是一个库。
  3. import 也是一个关键字,表示要引入的包,和Java的import关键字一样,引入后才可以使用它。
  4. fmt是一个包名,这里表示要引入fmt这个包,这样我们就可以使用它的函数了。
  5. main函数是主函数,表示程序执行的入口,Java也有同名函数,但是多了一个String[]类型的参数。
  6. Println是fmt包里的函数,和Java里的system.out.println作用类似,这里输出一段文字。

整段代码非常简洁,关键字、函数、包等和Java非常相似,不过注意,go是不需要以;(分号)结尾的。

安装程序

安装的意思,就是生成可执行的程序,以供我们使用,为此go为我们提供了很方便的install命令,可以快速的把我们的程序安装到$GOAPTH/bin目录下。

go install flysnow.org/hello

打开终端,运行上面的命令即可,install后跟全路径的包名。 然后我们在终端里运行hello就看到打印的Hello World了。

➜  ~ hello
Hell World

跨平台编译

以前运行和安装,都是默认根据我们当前的机器生成的可执行文件,比如你的是Linux 64位,就会生成Linux 64位下的可执行文件,比如我的Mac,可以使用go env查看编译环境,以下截取重要的部分。

➜  ~ go env
GOARCH="amd64"
GOEXE=""
GOHOSTARCH="amd64"
GOHOSTOS="darwin"
GOOS="darwin"
GOROOT="/usr/local/go"
GOTOOLDIR="/usr/local/go/pkg/tool/darwin_amd64"

注意里面两个重要的环境变量GOOSGOARCH,其中GOOS指的是目标操作系统,它的可用值为:

  • darwin
  • freebsd
  • linux
  • windows
  • android
  • dragonfly
  • netbsd
  • openbsd
  • plan9
  • solaris

一共支持10种操作系统。

GOARCH指的是目标处理器的架构,目前支持的有:

  • arm
  • arm64
  • 386
  • amd64
  • ppc64
  • ppc64le
  • mips64
  • mips64le
  • s390x

一共支持9种处理器的架构,GOOSGOARCH组合起来,支持生成的可执行程序种类很多,具体组合参考https://golang.org/doc/install/source#environment 。如果我们要生成不同平台架构的可执行程序,只要改变这两个环境变量就可以了,比如要生成linux 64位的程序,命令如下:

GOOS=linux GOARCH=amd64 go build flysnow.org/hello

前面两个赋值,是更改环境变量,这样的好处是只针对本次运行有效,不会更改我们默认的配置。

获取远程包

go提供了一个获取远程包的工具go get,他需要一个完整的包名作为参数,只要这个完整的包名是可访问的,就可以被获取到,比如我们获取一个CLI的开源库:

go get -v github.com/spf13/cobra/cobra

就可以下载这个库到我们$GOPATH/src目录下了,这样我们就可以像导入其他包一样import了。

特别提醒,go get的本质是使用源代码控制工具下载这些库的源代码,比如git,hg等,所以在使用之前必须确保安装了这些源代码版本控制工具。

如果我们使用的远程包有更新,我们可以使用如下命令进行更新,多了一个-u标识。

go get -u -v github.com/spf13/cobra/cobra

获取gitlab私有库包

如果是私有的git库怎么获取呢?比如在公司使用gitlab搭建的git仓库,设置的都是private权限的。这种情况下我们可以配置下git,就可以了,在此之前你公司使用的gitlab必须要在7.8之上。然后要把我们http协议获取的方式换成ssh,假设你要获取http://git.flysnow.org ,对应的ssh地址为git@git.flysnow.org,那么要在终端执行如下命令。

git config --global url."git@git.flysnow.org:".insteadOf "http://git.flysnow.org/"

这段配置的意思就是,当我们使用http://git.flysnow.org/获取git库代码的时候,实际上使用的是git@git.flysnow.org这个url地址获取的,也就是http到ssh协议的转换,是自动的,他其实就是在我们的~/.gitconfig配置文件中,增加了如下配置:

[url "git@git.flysnow.org:"]
    insteadOf = http://git.flysnow.org/

现在我们就可以使用go get直接获取了,比如:

go get -v -insecure git.flysnow.org/hello

仔细看,多了一个-insecure标识,因为我们使用的是http协议, 是不安全的。当然如果你自己搭建的gitlab支持https协议,就不用加-insecure了,同时把上面的url insteadOf换成https的就可以了。

Go编辑器推荐

Go采用的是UTF-8的文本文件存放源代码,所以原则上你可以使用任何一款文本编辑器,这里推荐几款比较流行的。

对于新手来说,我推荐功能强大的IDE,功能强大,使用方便,比如jetbrains idea+golang插件,上手容易,而且它家的IDE都一样,会一个都会了,包括菜单、快捷键等。

值得高兴的是jetbrains针对Go这门语言推出了专用IDE gogland,也足以证明go的流行以及jetbrains的重视。goglang地址为 https://www.jetbrains.com/go/ ,可以前往下载使用。

其次可以推荐微软的VS Code以及Sublime Text,这两款编辑器插件强大,快捷键方便,都对Go支持的很好,也拥有大量的粉丝。

最后推荐老牌的VIM,这个不用多介绍,大家都知道。

编辑器只是为了提高开发效率,大家哪个顺手用哪个,不存在谁更NB。

阅读全文

修改手机HOSTS的四种方式

一、直接修改手机的HOSTS文件

直接以root用户修改手机的HOSTS文件

二、电脑开热点

如果电脑是windows系统,可以通过电脑开热点,手机连接热点,修改电脑HOSTS文件。即通过修改电脑HOSTS文件的方式来间接到达修改手机HOSTS的目的。

三、通过抓包工具设代理

电脑安装抓包工具fiddler或Charles,手机设代理,这时,电脑中的HOSTS对手机而言也是生效的,但仅仅限于HTTP/HTTPS,websocket就不好使了。

四、电脑搭建DNS服务器

安装dnsmasq,用电脑搭个DNS服务器,手机设置DNS为电脑的IP。

停止
sudo launchctl stop homebrew.mxcl.dnsmasq

启动
sudo launchctl start homebrew.mxcl.dnsmasq

清除DNS缓存
sudo killall -HUP mDNSResponder

阅读全文

安卓WebSocket WSS/SSL对证书受信任

WebSocket库下载地址

Java-WebSocket

Demo代码

package com.test;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

import java.io.IOException;
import java.net.URI;
import java.net.URISyntaxException;
import java.security.KeyManagementException;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;

import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;

import org.java_websocket.WebSocketImpl;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake;

import java.security.SecureRandom;
import javax.security.cert.X509Certificate;


class WebSocketChatClient extends WebSocketClient {

    public WebSocketChatClient( URI serverUri ) {
        super( serverUri );
    }

    @Override
    public void onOpen( ServerHandshake handshakedata ) {
        System.out.println( "Connected" );
        this.send("{\"age\": 22}");
    }

    @Override
    public void onMessage( String message ) {
        System.out.println( "got: " + message );

    }

    @Override
    public void onClose( int code, String reason, boolean remote ) {
        System.out.println( "Disconnected" + code + reason + remote);
        System.exit( 0 );
    }

    @Override
    public void onError( Exception ex ) {
        ex.printStackTrace();
    }
}

public class MainActivity extends AppCompatActivity {

    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        new Thread(new Runnable() {
            @Override
            public void run() {
                WebSocketImpl.DEBUG = true;

                WebSocketChatClient chatclient = null;
                try {
                    chatclient = new WebSocketChatClient( new URI( "wss://test.com/socket" ) );
                } catch (URISyntaxException e) {
                    e.printStackTrace();
                }

                SSLContext sslContext = null;
                try {
                    sslContext = SSLContext.getInstance( "TLS" );
                } catch (NoSuchAlgorithmException e) {
                    e.printStackTrace();
                }
                try {
                    sslContext.init(null, new TrustManager[] {
                            new X509TrustManager() {

                                public void checkClientTrusted(X509Certificate[] certs, String authType) {
                                    System.out.println("checkClientTrusted1");
                                }

                                @Override
                                public void checkClientTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
                                        throws CertificateException {
                                    System.out.println("checkClientTrusted2");
                                }

                                public void checkServerTrusted(X509Certificate[] certs,
                                                               String authType) {
                                    System.out.println("checkServerTrusted1");
                                }

                                @Override
                                public void checkServerTrusted(java.security.cert.X509Certificate[] arg0, String arg1)
                                        throws CertificateException {
                                    System.out.println("checkServerTrusted2");
                                }
                                @Override
                                public java.security.cert.X509Certificate[] getAcceptedIssuers() {
                                    return null;
                                }
                            }
                    }, new SecureRandom());
                } catch (KeyManagementException e) {
                    e.printStackTrace();
                }
                SSLSocketFactory factory = sslContext.getSocketFactory();

                try {
                    chatclient.setSocket( factory.createSocket() );
                } catch (IOException e) {
                    e.printStackTrace();
                }
                chatclient.connect();
            }
        }).start();
    }
}

阅读全文

搭建React Native安卓环境遇到的问题及解决方案

Android Studio自带的模拟器的HOSTS文件不能修改?

按照网上搜索的方法,将/system改为可读写的,输入以下命令

adb root
adb remount
adb shell
root@xxxxxx:/ #mount -o remount, rw /system

最后输入mount后,发现/system还是只读的,见下面的 /system ext4 ro,即read only

/dev/block/sda6 /system ext4 ro,noatime,data=ordered 0 0

最后换成用genymotion装模拟器,输入以下命令后,hosts文件能修改了

adb root
adb remount
adb shell
root@xxxxxx:/ #mount -o remount, rw /system

Genymotion安装的模拟器不能上网?

折腾了一天,模拟器有时候能上网,有时候不能,最后发现,原来是没有手动连接WIFI,如下图,不只是On 这个按钮要开启,而且WiredSSID要点击后连接,之前一直是仅仅开启了On这个按钮

React Native初始化项目速度很慢?

在进行React Native初始化时,需要执行如下命令:

react-native init AwesomeProject

但是按下Enter键后,要很长时间才能初始化完成,或者直接就失败了。

在初始化的过程中,node-gyp需要进行编译,node-gyp 编译时候需要 NodeJs 源码来提供头文件,所以它会先尝试下载 NodeJs 源码。我们可以先把 NodeJs源码下载到本地,然后提取给 node-gyp。

执行如下脚本:

bash node-gyp.sh

执行脚本前要保证已经安装了nodejs, wget,如果没安装的话,请先安装。

node-gyp.sh的内容如下:

NODE_VERSION=`node -v | cut -d'v' -f 2`

echo ${NODE_VERSION}

# 下载源码包(使用镜像)
wget http://npm.taobao.org/mirrors/node/v$NODE_VERSION/node-v$NODE_VERSION.tar.gz

# 删除现有内容不完整的目录
rm -rf ~/.node-gyp
mkdir ~/.node-gyp

# 解压缩并重命名到正确格式
tar zxf node-v$NODE_VERSION.tar.gz -C ~/.node-gyp
mv ~/.node-gyp/node-v$NODE_VERSION ~/.node-gyp/$NODE_VERSION

# 创建一个标记文件
printf "9\n">~/.node-gyp/$NODE_VERSION/installVersion

npm官方的源不稳定,我们可以使用国内淘宝的源

npm config set registry=http://registry.npm.taobao.org/

配置这些之后,再去初始化项目

react-native init AwesomeProject

阅读全文

Node也许不是构建大型服务的最佳选择——Node之父Ryan Dahl访谈录

导读:本文是对 Node.js 之父 Ryan Dahl 的访谈。Ryan Dahl 谈到了创造 Node 的过程和现在做的一些很有意义并有挑战的事情。

Ryan Dahl 是 Google Brain 的软件工程师,Node.js 的创始人。目前他正专注于深度学习研究项目,目前的关注重点主要是图像转换。他为几个广为使用的开源项目做出了巨大贡献,其中包括 HTTP Parser, libuv。

Pramod:你好,这里是 Ryan Dahl,他让我们领会使用同步 IO 是不正确的姿势,并教会我们如何使用纯异步编程模型来构建软件,Ryan 也是 Node 的创始人。 很高兴 Ryan 接受我们的采访。

Ryan:你好! 很高兴来这里。

Pramod: 您作为 Node 的创造者而知名,可以告诉我们你之前的技术经历吗?

Ryan:当然可以。我在圣地亚哥长大,今年 36 岁,当我六岁的时候,妈妈拿到了一台苹果 2C,所以我很早就接触电脑了。随着互联网的出现,我的时代就来了。我去了圣地亚哥的社区学院,然后去了 UCSD,在那里我学习数学。然后我去了罗切斯特大学的数学研究生院。在那里,我研究代数拓扑,这是一个非常抽象的主题。毕业后,我开始攻读博士学位,随后我意识到自己并不想成为数学科学家,于是我就退学去了南美,找到一份做网站的工作。这就是我编程生涯的开始,在一个滑雪板公司做 Ruby on Rails 程序开发

Pramod:退出数学博士攻读去南美做 Web 开发?这个经历听起来非常有意思!

Ryan:是的。我的意思是你习惯于处理非常抽象的问题,而做网站是一个非常具体的过程。但是我真的试图把它变成一个美丽的数学理论,就像我在研究生学习过程中一样。我真的很喜欢 Ruby,你可以在 Ruby 中更清楚地表达你的想法。我认为 Rails 真的很有趣。它给了一个新的结构,虽然可能不是原创的,我认为 Rails 普及了 MVC 结构。 Ruby 能够清楚的表达我的想法和 MVC 这两件事结合在一起,对我来说真的很有趣。

Pramod:构建 Web 应用程序是非常有意思的事情。而 Ruby 是一个完美的工具。接下来,您继续在德国担任自由职业的 Web 开发人员。您工作的其中一个项目是 Node,我想那时候接下来六到八个月内都在忙这个?

Ryan:对。离开南美以后,我和女朋友一起搬到了德国,因为她是德国人,不得不回到大学继续学业。我开始去参加那里的 Ruby 会议,人们经常会讨论 Rails。其中有个人叫 Chris Neukirchen。他开发了一个名为 Rack 的项目,这是 Web 服务器的简化抽象。它是把一个 Web 服务器变成单一函数接口,你发起一个请求,然后得到一个响应。结合我在 Nginx Module 上做的一些工作,这让我想起过去。在 Nginx 中一切都是异步的。因此当你为其构建模块时,必须非常小心地避免阻塞。我认为 Chris Neukirchen 的脚手架加上 Nginx 的非阻塞 IO,使我开始思考如何将这两个问题结合起来。

Pramod:现在你有结合 Rack 和 Nginx 的想法了。 你是怎么说服自己,花半年时间做出可以在服务器端运行的 JavaScript,这可能会很大程度提高性能?

Ryan:那么这两个简单的 Web 服务器接口就是 Rack,而这个异步部分是 Nginx,我一直在思考这一问题。 Chrome 于 2008 年 12 月发布。随之而来的是 V8 JavaScript 解释器。这是一个很好的运行时。所以,当 V8 出来的时候,我尝试了一下,它看起来真的很有意思。 JavaScript 实际上是单线程的,每个操作都是非阻塞的。人们使用 AJAX 请求和内容时,就已经在执行非阻塞请求了。我想:我认为 JavaScript 加异步 IO 加上一些 HTTP 服务器实际上是一件很酷的事情。而且我对这个想法感到非常兴奋,在接下来的四年里,我一直坚持不懈。

Pramod:是的 JavaScript 加异步 I / O 工作得非常好。我相信开发商正在期待看到一个这样做的框架。在这段时间里,你曾经咨询过别人吗?

Ryan:基本上只是我。有些有编程经验的人给了建议。后来,我最后搬到了旧金山,在 Joyent 工作,并遇到了很多很棒的专业人士。很多人的思想汇聚而成日后的的 Node。

Pramod:自从你在 2009 年左右创建了 Node 之后,我知道 Ryan 这个名字就已经很久了。

Ryan:我觉得至少对我自己来说,在我的生活中,没有比这更伟大的时刻了,真的有时间坐下来好好地工作。我认为 Node 是一个等着发生的想法,如果我没有做,别人会有。但碰巧的是,我失业了有一些空闲时间,可以连续工作几个月。

Pramod:太好了这太妙了。你做得很好, Node 是建立在“纯异步”编程模型的基础上。这个想法如何产生的?

Ryan:是的,我认为这是一个非常有趣的问题。现在已经好几年了,而且自 2012 年或 2013 年以来,我还没为 Node 工作过。当然, Node 已经是一个很大的项目了。所以当它刚出现的时候,我试图说服人们以非阻塞的方式完成一切工作,我们将解决很多编程难题。也许我们可以完全忘记线程,只使用进程和序列化通信。但是在单个进程中,我们可以通过完全异步处理许多请求。当时我非常坚持这个想法,但是在过去的几年里,我认为这可能不是最终目的。特别是当 Go 出来的时候。当我第一次开始听到 Go 的时候,那是 2012 年左右, Go 有一个非常漂亮的运行时,它有 goroutine, goroutine 是真的很容易使用抽象。程序员以为在使用阻塞 IO,而实际上是在执行非阻塞 IO

而 Go 向用户提供的接口是阻塞的,我认为这是一个更好的编程模型。如果阻塞调用接口,你可以在很多情况下思考你在做什么。如果你有一连串的行动,可以说:做 A,等待回应,也许返回错误。做事 B,等待回应,或者出错。在 Node 中,这更困难,因为你必须跳转到另一个函数调用。

Pramod:是的,我喜欢 Go 的编程模式。使用 goroutines 是非常容易和有趣的。事实上,我们正在工作中使用 Go 构建分布式应用程序

Ryan:是的,我认为这是某种类型的应用程序,如果你正在构建服务器端程序,我无法想象使用 Go 以外的任何系统。我认为 Node 的非阻塞范式非常适用于没有线程的 JavaScript。而且我认为,回调有很多问题,您必须跳入许多匿名函数才能完成工作。对使用 async 关键字,异步功能的现阶段 JavaScript 来说,这个问题已经缓解很多了。因此一些较新版本的 JavaScript 使得完成工作更容易。我认为 Node 不是构建庞大服务器网络的最佳系统,我一定会用 Go 去做,这基本上是我离开 Node 的原因。实际上, Node 不是最好的服务器端系统

我认为让 Node 真正发光的是客户端。例如,在建立网站时做一些脚本,捆绑客户端的 JavaScript。你可以在客户端和服务器端使用同样的语言。对一些比较小的服务器功能来说, Node 可能是正确的选择。但是如果你正在构建一个大规模分布式的 DNS 服务器,我不会选择 Node。

Pramod:这应该是全世界所有开发人员的好消息。选择正确的应用工具非常重要。 我们对 Node 没有任何偏见。您在 JsConf 2009 Berlin 中介绍了 Node.js。你惊讶于它突然收到的成功和牵引力吗?

Ryan:是的。我四年来基本上处于惊喜状态。因为它增长非常快,人们非常喜欢它。

Pramod:你加入 Joyant 之后,为 Node 工作,你搬到了 SF 吗?经验怎么样?开发者很喜欢 Node,你是 Node 的中心。

Ryan:这是一生只有一次的经历,我感觉到我是这一切的中心。有一次,我去了日本,人们要求和我拍照,我觉得有点奇怪。在网上,每当我评论一些东西,我会得到很多人回应。所以,我发现我不得不非常仔细地选择我的言论,以及我如何表达自己,因为似乎人们真的在听。而我也不喜欢这样。我是一个程序员,我仅仅想写代码而已,有时分享我的意见,大家不必太在意。

Pramod:我记得你是在 29 或者 30 年纪时候就创造 Node,而 Node 造成如此巨大的影响

Ryan:是的。我在那时候还是一个新手。

Pramod:好的 Ryan,当时有很多服务器端的 JavaScript 项目。 Node 不是唯一的。你对 Node 的成功归因于什么?

Ryan:对。当时有几个人都在尝试让 JS 运行在服务器端。我完全忘记了他们是什么。问题在于他们都使用阻塞 I / O,然而 JS 根本没有线程。因此,如果您正在使用阻塞 I / O,您根本无法提供请求。如果一次只能提供一件事情,那么 Node 永远不会成功的。我希望使得 HTTP 服务器运行的更加良好。而且我把这些事情做得很好,让人们可以很快的建立网站。老实说,构建 Web 服务器并不是简单的事情。我认为重要的是,当你发布一个软件框架,或者任何种类的软件,需要让人们可以坐下来立即使用。这是 Node 所做的事情之一,就是人们可以立即下载并使用 Web 服务器。

Pramod:如果人们可以轻松下载,安装和使用它,会有很大的不同。此外,人们都知道 JavaScript,他们可以随时开始编码。

Ryan:是的。我们认为在语言之间切换是很容易的事情。我的意思是,即使你会用另一种语言,切换语言也是非常困难的。还有很多人非常熟悉 JavaScript。并且给他们这些工具,以便能够在其他情况下使用它激发人们。你突然能够做到比以前能够做的更多的事情。

Pramod:是的在 2012 年的 Node 已经有一个巨大的开发者基础。你为什么离开,把 Node 的控制权转转交给 Joyent 的 Isaac Schlueter?

Ryan:我认为有两个原因让我做出如此决定。我已经为 Node 工作了四年。已经达到了我想要的地步。我从来不想让 Node 成为一个非常大的 API。我想让它成为一种小而紧凑的核心,人们可以在其上构建模块。还有几个关键的特性,在早期添加了扩展模块,我们已经得到了所有的网络库, HTTP, UDP, TCP,我们可以访问所有的文件系统。然后把它移植到 Windows,而且我们想使用 Windows 抽象来进行异步 IO,即是用 Windows 的 IOCP 接口。这需要重写核心库,其结果是编写了 libuv 库。在那个时候,所有这一切都已经完成了,我们已经在 Windows 上发布了。虽然接下来有很多 BUG 需要修复,但已经有不少人参与其中。而我想去做别的事情,加上 Go 已经出来的事实,我看到 Node 并非服务器端的终极解决方案。当我发表博客时,也不想成为关注的焦点。

Pramod:确实有些人不喜欢成为众人注目的焦点。当您开始在 Node 上工作时,您绝对有一些目标。今天的 Node 是否达到你的期望呢?

Ryan: Node 被这几十万的用户广泛使用,我觉得这绝对超出了我的预期。

Pramod:Ryan 在你与 Node 的精彩旅程之后,你决定从事什么工作?

Ryan:在 Node 之后,我搬到了纽约,花了一些时间来完成自己的项目。当时 Instagram 已经出来,我也忍不住想做同样的事情。我有一个社交网络项目,还有有一个 C++ 的构建系统项目,甚至有另一个 HTML 的构建系统项目。我有一堆项目,其中没有一个是在我心目中淘汰的。我会在某个时候回到这个项目。我这样做了一段时间。然后我我开始听到卷积网络,图像分类,这让我对机器学习真的很感兴趣。

Pramod:你也是 Google Brain 的 Residency 计划的一部分,里面的经历怎么样?

Ryan:是的。我刚刚在山景城度过了一年。回过头来, TensorFlow 已经在两年前发布了。与此同时,他们宣布了 Google Brain Residency 计划,邀请了 20 人来到 Google Brain。我认为这个有想法的并不一定是真正懂机器学习的人,而是在数学和程序设计方面有一些背景,并且对机器学习感兴趣,喜欢这些新想法。因为机器学习正在快速变化,而且还有大量的工作已经完成,现在缩小的神经网络成为机器学习中最有用的算法,加上 TensorFlow,将产生一些有趣的想法。

我花了一年的时间,编写了基本模型,并写了关于这些模型的论文。我主要从事图像转换问题。如果你有一些输入图像,你想得到一些输出图像。例如,您可以将黑白照片作为输入,尝试将照片的颜色预测为输出。这个问题最酷的地方是有无限的训练数据。你可以拍任何彩色照片,然后把它的饱和度调低,就是你的输入图像。机器学习的一个问题就是需要大量的数据,而使用这些方式,这个问题就不是问题了。最近也有很多关于生成模型的工作,也就是输出图像的模型。它们已经表现出了学习自然图像多样性的能力,能够真正理解什么是真实图像,什么不是真实图像,什么看起来像真实图像。我的想法是把这个最近的工作应用在生成模型中,并采取这个无限的训练数据,看看是否可以做一些图像转换问题。因此,我做了一些关于超分辨率的工作(转换低分辨率图像到高分辨率)。这也是一个图像到图像的转换问题。

Pramod:我已经看到, TensorFlow 是许多机器学习问题的一个很好的平台。图像分类和转换,我相信这很有趣。你是否继续专注于 ML?

Ryan:对。我还是在谷歌,作为一名软件工程师,处理同样的问题。研究生成模型,并试图帮助研究人员构建下一代系统和下一代模型。

Pramod:生成模型与之前的 JavaScript, Node 或 Web 开发工作有很大的不同?

Ryan:我想是这样的。我想我有相当不错的数学知识基础。我不想成为一个只做 JavaScript 的人,我也不想只成为一个机器学习研究者。对我来说探索什么是可能的非常有趣。令人兴奋的是建立一些以前从未做过的新事物,它可能在某种程度上造福人类。

Pramod:很高兴知道机器学习需要一个很好的数学背景,在最近的博客关于 Optimistic Nihilism 的话题,你说我们有可能有一天会模仿大脑,并建立一个像人类一样理解和思考的机器,我们距离这个目标还有多远?

Ryan:我必须对一些预言有点小心。我们无法接近人类智慧。我们正在使用的机器学习系统非常简单,它基本还不工作。事实上,我有一篇博客文章,其中列举了开发这些模型的所有困难。我认为那些不在这个领域工作的人有这样的想法:你可以接受这个模型并通过它得到一些数据。但事实并非如此。这些东西都是非常挑剔的,不是很好理解的,它需要很长时间的精心调整和实验才能得到结果。

我认为最近确实出现了一些有希望的技术,也就是说卷积网络似乎起作用了。事实上,这些东西是建立在一个模型的基础上的,这种神经网络模型不是真正的大脑,但是它是由大脑激发出来的,这是非常诱人的。我们有 GPU,我们展示了如何在这些 GPU 上进行训练,并在一定程度上分配加载在 GPU 上的训练。因此,我认为建立更大、更智能的系统的基础正在发生。就我个人而言,我是一个无神论者,我相信除了化学物质和大脑中的神经元外,我脑子里再也没有别的东西了。我们的意识是编码,但是我们还不知如何在神经元之间的相互作用。我不知道哪一天,我们有足够的研究和工作在这个领域可以效仿那种行为。这太远了,无法预测会有多长时间。

Pramod:你想在未来 20 年看到什么新科技?

Ryan:我对机器学习和它带来的可能性感到非常兴奋。我认为这种技术有很多应用。基本上,任何一个可以帮助你的系统,都将从这项技术中获益匪浅。有不可数的工业流程可以从这种事情中受益。有很多很多系统可以从简单的机器学习系统中受益。而且我认为我们将越来越多地看到这些系统应用于不同的领域。所以我认为这会大大影响科技界。

Pramod:是机器学习是非常令人兴奋的。当我在山景城看到自动驾驶车时,我感到非常兴奋。谢谢 Ryan,为我们带来了漂亮的 Node,感谢您的参与访谈。也祝你未来的项目成功。

Ryan:是的,它非常酷,感谢邀请我出席访谈。

Pramod:谢谢。我真的很喜欢和 Ryan 聊天,一个非常谦逊及厉害的人物,在非常年轻时候就在技术上取得了很大的成就,这样一个鼓舞人心的故事。

英文原文: https://www.mappingthejourney.com/single-post/2017/08/31/episode-8-interview-with-ryan-dahl-creator-of-nodejs/
转载地址: https://mp.weixin.qq.com/s/GjJHWv84kkEINZny00D_4Q

作者 Pramod HS,由 Jesse 翻译

阅读全文

golang123配置支持emoji🙂

配置MySQL支持utf8mb4

打开my.conf文件,按照如下的配置就可以支持utf8mb4的字符集了。(注意: MySQL的版本要5.5.3以上)

[client]
default-character-set = utf8mb4

[mysqld]
collation-server = utf8mb4_general_ci
character-set-server = utf8mb4

在修改完my.conf配置之后,重启mysql,检查字符集是否已经更改,除了character_set_filesystemcharacter_set_system外,其他的字符集都变成utf8mb4类型。

mysql> show variables like 'char%';
+----------------------------------+------------------------------------+
| Variable_name              | Value                            |
+----------------------------------+------------------------------------+
| character_set_client        | utf8mb4                        |
| character_set_connection | utf8mb4                        |
| character_set_database   | utf8mb4                        |
| character_set_filesystem  | binary                           |
| character_set_results      | utf8mb4                         |
| character_set_server       | utf8mb4                         |
| character_set_system      | utf8                              |
| character_sets_dir          | /usr/share/mysql/charsets/ |
+-----------------------------------+------------------------------------+

配置gorm支持utf8mb4

打开golang123/config.json文件,在database字段中的配置找到Charset, 改为utf8mb4

{
	"database": {
		"Dialect"            : "mysql",
		"Database"        : "golang123",
		"User"	             : "",
		"Password"        : "",
		"Charset"           : "utf8mb4",
		"Host"               : "127.0.0.1",/*数据库ip*/
		"Port"                : 3306,       /*数据库端口*/
		"MaxIdleConns"   : 5,           /*空闲时最大的连接数*/
		"MaxOpenConns" : 10          /*最大的连接数*/
	}
	...
}

阅读全文

Linux下Redis的安装和配置

Redis的安装

  1. 下载redis压缩包, 地址: http://download.redis.io/releases/redis-4.0.1.tar.gz

  2. 将压缩包上传到Linux服务器的某个目录,然后解压

    tar xzvf redis-4.0.1.tar.gz
    
  3. 进入解压后的目录, 对Redis解压后的文件进行编译

    cd redis-4.0.1
    make
    
  4. 执行redis的测试

    make test
    

    测试通过后, 界面如下:

  5. 进入redis-4.0.1/src目录, 安装redis

    cd src
    make install
    
  6. 安装完成

make test常见问题

  1. You need tcl 8.5 or newer in order to run the Redis test
    安装tcl

    yum install tcl
    
  2. Executing test client: NOREPLICAS Not enough good slaves to write
    这种情况下,可以修改redis-4.0.1/tests/integration/replication-2.tcl文件,将after 1000改为after 10000以延长等待时间。

  3. Executing test client: I/O error reading reply
    这种情况下,可以修改redis-4.0.1/tests/unit/memefficiency.tcl文件, 将expected_min_efficiency中的值改小一点。

运行

打开redis-4.0.1/redis.conf文件, 找到daemonize,改为daemonize yes, 即后台daemon方式运行
输入以下命令运行

redis-server /path/to/redis.conf

配置参数

参数 说明
daemonize 是否以后台daemon方式运行
pidfile pid文件位置
port 监听的端口号
timeout 请求超时时间
loglevel log信息级别
logfile log文件位置
databases 开启数据库的数量
rdbcompression 是否使用压缩
dbfilename 数据快照文件名(只是文件名)
dir 数据快照的保存目录(仅目录)
appendonly 是否开启appendonlylog,开启的话每次写操作会记一条log,这会提高数据抗
风险能力,但影响效率
appendfsync appendonlylog如何同步到磁盘。三个选项,分别是每次写都强制调用fsync、
每秒启用一次fsync、不调用fsync等待系统自己同步

阅读全文

React Native与原生模块交互之iOS篇

为什么需要使用原生模块?

有时候App需要访问平台API,但React Native可能还没有相应的模块封装;或者你需要复用Objective-C、Swift或C++代码,而不是用JavaScript重新实现一遍;又或者你需要实现某些高性能、多线程的代码,譬如图片处理、数据库、或者各种高级扩展等等。

JavaScript与Objective-C交互

Objective-C代码

MyNativeModule.h

#import <React/RCTBridgeModule.h>
#import <React/RCTEventEmitter.h>

// 在React Native中,如果实现一个原生模块
// 需要实现"RCTBridgeModule"协议,其中RCT就是ReaCT的缩写
@interface MyNativeModule : RCTEventEmitter <RCTBridgeModule>

@end

MyNativeModule.m

#import "MyNativeModule.h"

@implementation MyNativeModule

// 这个宏可以添加一个参数用来指定在JavaScript中访问这个模块的名字
// 如果不指定,默认就会使用这个Objective-C类的名字
RCT_EXPORT_MODULE();

// RCT_EXPORT_METHOD()宏来实现要给JavaScript导出的方法
RCT_EXPORT_METHOD(sayHello:(NSString *)name event:(RCTResponseSenderBlock)callback)
{
    NSString *result = [NSString stringWithFormat:@"%@, %@", @"Hello", name];
    // 通过回调函数给JavaScript返回结果
    // 第一个参数是错误对象,第二个参数是数组
    callback(@[[NSNull null], result]);
  
    [self onStart:[NSString stringWithFormat:@"%@, %@", name, @"data1 from onStart"]];
    [self onEnd:[NSString stringWithFormat:@"%@, %@", name, @"data2 from onEnd"]];
}

- (NSArray<NSString *> *)supportedEvents
{
    return @[@"onStart", @"onEnd"];
}

- (void)onStart:(NSString *)msg
{
    // Objective-C也可以直接给JavaScript发送事件
    [self sendEventWithName:@"onStart" body:msg];
}

- (void)onEnd:(NSString *)msg
{
    // Objective-C也可以直接给JavaScript发送事件
    [self sendEventWithName:@"onEnd" body:msg];
}

@end

JavaScript代码

import React, { Component } from 'react';
import {
    AppRegistry,
    StyleSheet,
    Text,
    View,
    NativeModules,
    NativeEventEmitter
} from 'react-native';

export default class golang123ReactNative extends Component {
    constructor() {
        super();
        this.onNativeEvent = this.onNativeEvent.bind(this);
        this.state = {
            helloMsg: '',
            eventMsg: ''
        };
    }
    componentDidMount() {
        let MyNativeModule = NativeModules.MyNativeModule;

         //创建自定义事件接口  
        let myNativeEvt = new NativeEventEmitter(MyNativeModule);
        //对应了原生端的事件名称
        this.listener = myNativeEvt.addListener('onStart', this.onNativeEvent); 
        this.listener = myNativeEvt.addListener('onEnd', this.onNativeEvent);
        // 调用原生端的方法sayHello,传字符串参数"golang123"
        MyNativeModule.sayHello("golang123", (err, result) => {
            if (!err) {
                this.setState({
                    helloMsg: result
                });
            }
        });
    }

    componentWillUnmount() {  
        this.listener && this.listener.remove();
        this.listener = null;  
    }  

    onNativeEvent(msg) {
        this.setState({
            eventMsg: this.state.eventMsg + '\n'+ msg
        });    
    } 

    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.text}>{this.state.helloMsg}</Text>
                <Text style={styles.text}>{this.state.eventMsg}</Text>
            </View>
        );
    }
}

const styles = StyleSheet.create({
    container: {
        flex: 1,
        justifyContent: 'center',
        alignItems: 'center',
        backgroundColor: '#F5FCFF',
    },
    text: {
        fontSize: 20,
        paddingBottom: 12,
        textAlign: 'center',
        margin: 10
    }
});

AppRegistry.registerComponent('golang123ReactNative', () => golang123ReactNative);

阅读全文

为什么你的DevOps会失败?

DevOps的目标非常明确:使应用软件高效迭代,可靠,质量更好。这个目标非常理想,几乎所有人都不会对此产生异议。

许多人都说,他们已经开始了DevOps的实践,正遵循一些常见的框架,比如“CALMS”。然而,能得到非常满意结果的并不多,我们在与200多名DevOps专业人士交谈后,做了以下的数据统计,希望你能从中得出一些结论:

  • 68%的人表示,DevOps中所需的多种工具之间缺乏连接性;
  • 52%的人表示,他们的大部分测试仍然是手动的,速度缓慢;
  • 38%的人表示,他们混合了传统和现代应用,使得环境变得非常混乱,这给应用部署策略和工具链等方面制造了很多麻烦;
  • 27%的人仍然在努力消除孤立的团队,追求所预期的协作;
  • 23%的人对自助服务基础设施的访问仍然受限;
  • 以及一些其他的问题:找不到正确的DevOps思路,难以管理多种服务和环境的复杂性,缺乏预算和紧迫性,以及执行领导层的支持有限……

通过这些数据统计,我们能得出一些更深入的结论来:

挑战1:DevOps工具链中缺少连接

许多DevOps工具会用于自动执行不同的任务,如CI,基础设施配置,测试,部署,配置管理,发布管理等,虽然这些组织开始采用DevOps,但他们往往不能一起工作。

举一个典型的例子,某团队使用Capistrano进行部署,当需要部署新版本的应用程序时,或者当应用配置需更改时,研发人员仍然会通过JIRA tickets 与Test and Ops团队进行通信。

运行Capistrano脚本所需的所有信息都可以在JIRA tickets 中使用,在运行之前,研发人员手动将其复制到脚本中,这个过程通常需要几个小时,需要仔细管理。而所需的配置其实被手动传输两次:先输入到JIRA,再将其复制到Capistrano。 这是一个简单的例子,但这个问题存在于整个工具链中。当DevOps工具链中的工具无法协作并且依赖于手动的时候,持续交付将变得非常困难。

挑战2:缺乏测试自动化

尽管所有的焦点都集中在TDD上,但大多数组织仍然在与自动化测试进行斗争。如果测试是手动的,那么几乎不可能执行整个测试套件,这将成为持续交付的障碍。团队试图通过运行一组核心的测试来处理这一问题,并定期运行完整的测试套件。但这意味着在你的软件交付工作流程中可能忽视很多bug,而且查找和修复的成本要高得多。

测试自动化是DevOps采用过程的重要组成部分,因此需要成为首要任务。

挑战3:布朗菲尔德环境

典型的IT组合在本质上是跨越了数十年的技术、云平台供应商、实验室、数据中心的私有云和公共云。创建跨越这些方面的工作流程是非常有挑战性的,因为相当一部分工具只能使用在特定的架构和技术上。这导致了工具链的蔓延,因为每个团队都希望使用最符合自己需求的工具链。

Docker的兴起鼓励许多组织开发基于微服务架构进行。这也增加了DevOps自动化的复杂性,因为应用程序现在需要数百个异构微服务的部署管道。

挑战4:文化问题

开发人员开发了稳定优质的软件,然后由运维部门部署和运维。尽管所有这些团队都希望能够携手共同合作,但他们往往会有利益冲突。

开发人员可以快速迭代,QA团队确保没有软件错误,两个团队通常由SecOps和基础设施运维部门协调,他们被激励以确保生产不会中断。但成本中心的压力越来越大,这导致了一种反对变革的文化,因为变革引发了风险,破坏了事物的稳定,这意味着需要更多的资金和资源来控制影响。

开发人员也受到开发问题的困扰,大部分时间都花在之前的内容维护上,而不是创新新事物。大多数组织试图让所有团队参与SDLC的所有阶段,但这种方法仍然依赖于手动协作。 自动化是开发和运维合作的最佳方式。但是正如我们刚刚分析的那样,这种不太健康的自动化本身会降低你的速度并引入风险和错误。

我们需要更多关于DevOps理解和思考

一套完整的DevOps框架会包括文化、自动化、测试和共享。DevOps运动的雏形其实是一种文化运动,即使在今天,大多数的实现仍集中在文化上。

虽然文化是任何DevOps架构的重要组成部分,但想改变一个组织的文化是最困难的事情,因为文化会在时间的沉淀中形成。Ops团队不讨厌改变,他们试图快速改变生产过程,但他们更需要运维的稳定性。 把它们和开发人员一起放在一起,可能有助于使工作环境变得更加友好,但它并没有解决根本原因,Dev团队和Ops团队还有很长的路要走。

转载于: http://blog.csdn.net/ghostcloud2016/article/details/77684001

阅读全文

“好吃的”奥利奥 Android 8.0 正式发布:更快、更强大、更安全

2017 年 8 月 21 日,随着日全食的到来,此前一直猜测是 OREO(奥利奥)还是 Orellete(加泰罗尼亚的点心)的 Android 8.0 最终拉开帷幕,Google 正式采取了“OREO”的甜品来命名。基于此,Google 最新的手机操作系统和 Android Nougat 的下一版本不仅由此而得名,而且 Google 还将最新的源代码推送至 Android 开源项目上(AOSP)。

整场 Android 8.0 的发布会从开始至结束只有短短的 55 秒,可谓是全球最短的发布会了。官方在直播中只公布了 Android 新版本叫 OREO , 然后就没有其他了,至于 Android 8.0什么时候到来?按照惯例,Google 旗下的自有品牌手机肯定是近水楼台先得月,Google 表示,正式版 Android 8.0 将很快推送给 Pixel 和 Nexus 设备,首批可以升到“OREO”的设备还包括 Pixel、Pixel XL、Pixel C、 Nexus 6P、Nexus 5X 以及 Nexus Player。而如果手持上述设备的用户加入了 Android Beta Program 并运行着最新的预览版系统,将会很快收到更新。

此外,谷歌还承诺,包括 Essential、General Mobile、HMD Global Home、Nokia、华为、HTC、Kyocera、LG、摩托罗拉、三星、夏普和索尼等硬件制造商预计在“今年之内”升级设备至 Android 8.0 Oreo。

那么 Android 8.0 与以往新增了哪些功能呢?接下来,我们一起来看一下:

  • 后台执行限制。每次在后台运行时,应用都会消耗一部分有限的设备资源,例如 RAM。 这可能会影响用户体验,如果用户正在使用占用大量资源的应用(例如玩游戏或观看视频),影响尤为明显。为了提升用户体验,Android 8.0 对应用在后台运行时可以执行的操作施加了限制。应用主要在两个方面受到限制:后台服务限制和广播限制。
  • 重要通知。以往我们会收到大量的通知,但很多通知相当于垃圾信息,Android 8.0 中新增了“重要通知”这一功能,用户可通过该功能进行筛选设置。
  • Dot。在 Android 7.1.1 中,已经有 Notification Dot 这一功能,可以长按桌面上的图标(判断 App 是否具有这样的设计),然后可以看到最新的通知及建立捷径。

  • 自动填充框架。用户可以通过在设备中使用自动填充来节省填写表单的时间。引入自动填充框架后,Android 可以更轻松地填充表单。自动填充框架管理应用程序和自动填充服务之间的通信。只要你在 Google 账号中记录了你的用户及密码,当你在电话或手机甚至其他装置中,都不需要再次输入用户名及密码,只需要你进行指纹确认或手机锁确认。
  • 画中画模式。Android 8.0 允许以画中画 (PIP) 模式启动操作组件。PIP 是一种特殊的多窗口模式,最常用于视频播放。目前,PIP 模式可用于 Android TV,而 Android 8.0则让该功能可进一步用于其他 Android 设备。这样的好处是在一款设备上,播放影片的同时不影响其他工作。
  • 用户界面。新版 Android 中沒有重大的视觉变化。事实上,最引人注意的视觉差异是通知栏中快速设置区域的色彩变得明亮,它现在是浅灰色,不是深灰色。除了这种颜色变化,快速设置面板还有一些细微的重新排列,使设置,用户切换和编辑快捷键都由原本放置于顶部转为底部,主要原因估计是为了迁就未来推出的 18:9 屏幕手机 , 使用时更容易按到。
  • XML字体资源。Android 8.0 推出一项新功能,即 XML 中的字体,允许用户使用字体作为资源。这意味着,不再需要以资产的形式捆绑字体。字体在 R 文件中编译,并且作为一种资源,可自动用于系统。然后,用户可以利用一种新的资源类型 font 来访问这些字体。在运行 API 版本 14 及更高版本的设备中,支持库 26 对此功能提供完全支持。
  • 可下载字体。Android 8.0 和 Android 支持库 26 允许您从提供程序应用请求字体,而无需将字体绑定到 APK 中或让 APK 下载字体。此功能可减小 APK 大小,提高应用安装成功率,使多个应用可以共享同一种字体。
  • 表情符号兼容性。Android 8.0 在 Emoji 5.0 中添加了一些新的表情符号 , 而且重新设计了图案,放弃了过去的布局造型。

Google 还改进了蓝牙音频,并新增了 Google Play Protect 机制,定期扫描所以的应用保证设备的安全。尽量减少在后台从应用程序中过度使用电池,另外操作系统优化会带来更快的启动时间(Pixel 上的两倍)和更流畅的 App。

关于 Android 8.0 更多的功能和 API 可参考:https://developer.android.com/about/versions/o/android-8.0.html

阅读全文

程序员编程生涯中会犯的7个错误


作为软件开发人员生活和职业指导,我需要和很多程序员交流,帮助他们提升职业生涯,加速成长。时间久了,我发现很多程序员总是犯着相同的错误,前仆后继,却毫不自知。下面就是程序员在他们的软件开发生涯中最常犯的7个错误。

1.没有明确的目标

心中没有终点目标,那就只会随波逐流。如果你想在软件开发的职业生涯上获得成功,那么你需要有一个明确的目标。仅仅只是对遥远的未来有一个模糊的想法是不够的。相反,你应该有坚实的目标——在某个时间段内的首要目的——明确定义的目标。

我认识许多程序员和所谓的专业人士庸庸碌碌地在同一个岗位上干了几十年,是的,你没听错,就是几十年!这是一场悲剧,但如果没有目标,这就是你人生的默认选择。请引以为戒,否则下一个悲剧就会是你。那么,我们能做些什么呢?

从今天开始,从现在开始,花一些时间,好好想想你的编程生涯,并决定自己的近期目标。我的意思是,明确当前的首要目标。一旦达到这个目标之后,再制定一个新的目标,但是现在,请好好想想,你的编程生涯需要实现什么目的?你可以记下来,放到每天都能看到的位置,来提醒自己不断地朝着目标前进。

2.不投资于非技术和“软技能”

我认识很多程序员其实真的很擅长于写代码。我也认识很多程序员在算法上确实远远优于我。他们理解和思考复杂架构的水平,是我所望尘莫及的。但你猜怎么着?在我的软件开发职业生涯中,我超越了他们,不仅包括职位职务,还有工资,工作效率,性能等等。

我说出来不是为了炫耀,只是想要说明软技能对我们的编程生涯有多重要,而不仅仅是那些大多数程序员重点关注的技术技能。作为一个软件开发人员,你肯定知道,你的工作并不仅仅是编写代码。还有其他许多必要的重点技能。我们得时常与人打交道,所以人际交往能力是必须的。紧张的时间期限,快速的变化则需要稳定的心理,能够全神贯注,并懂得自我激励。

在一个不断变化的环境中,在一个充斥了各种繁多和意外的环境中,我们要学会如何优先安排,并尽可能地富有成效。此外我们也不能忽略健康以及经济因素,如果忽略它们的话也同样会导致失败甚至是毁灭。相关方面的内容还有很多,我就不一一赘述了,感兴趣的话,可以阅读《Soft Skills: The Software Developer’s Manual》 做深入的了解。

总而言之,不管你做的是哪方面的工作,软技能几乎总是比硬技能、技术技能更重要——所以一定要好好学习这方面的知识。

3.不参与社区

我做的其中一件让我的编程生涯受益无穷的事就是,参与社区。这不但让我有了归属感,不再感觉孤单,还能帮助我提高技能,敢于设定更高的目标。所以,我强烈建议你加入到编程社区中。

众人拾柴火焰高,参与社区,是一种积极的成长方式。

如果你发现自己的软件开发职业生涯停滞不前,那么加入社区吧,里面的一些志同道合之人会为你提供助你克服困难,冲出困境的种种建议。成为社区的一份子,还可以让你获得关注,增加知名度,这将会大大有利于你的事业发展。

那么,怎么加入社区呢?这很简单。世界各地都有这一类的团体,你可以简单地加入一个并参加聚会。比如说,你可以加入一年一次的,免费的,当地的Code Camp活动,那时许多软件开发人员会聚集到一起分享他们的工作心得。并且通常任何人都可以报名发表他们想要谈论的话题。如果你不喜欢这种聚会方式,也可以加入虚拟社区。对于初学者来说,不妨加入社区。社区里面提供技术开发交流,也有很多资讯和信息,非常不错。你也可以写博客,这也是参与社区的一种方式。话说,就是博客让我在社区众多程序员中脱颖而出的。

4.不专业

如果你曾经看过我写的博客,或者读过我的书,你就会发现我几乎每次都会提到这个话题,因为它真的非常重要。专业化。为自己选定一个方向,然后专心致志地朝着这个方向发展。但这并不意味着你无需具备广泛的知识基础——我非常热衷于通晓多门编程语言——我的意思是,你应该选择某个区域,然后孜孜不倦一心一意于挖掘更深层次的内容。

成为某种形式的专业人士是非常重要的,尤其是职业生涯的早期。专业人士的需求高,所以他们拿到的薪资也高,并且通常而言,他们还能够更快地塑造起威望来。另外,如果你的老板知道你在软件开发领域和技术上面钻研得很深,肯定会对此非常开心。你应该成为小池塘中的大鱼,而不是大池塘中的小鱼。

或许最终你会因为个头太大而不再适合这个池塘——那个时候你可以大胆潜入到更深的水域——但是,以一个专业人士的身份开启你的软件开发生涯,可以在这一行中为你自己树立个人品牌和声誉。(关于这一点,下面我会详细说明。)

最后,不要担心自己专业化了之后会被对号入座——这种事很少发生。并且,你也不需要真的研究得太深。话说,这么多年,我也没碰到有谁是太过于专业化的。

5.不投资于个人品牌

生活中的许多事情来来去去,犹如过往云烟。你可能会换工作,又或许甚至要换配偶;突然一夜暴富,也可能穷困潦倒;可能身体很健康,但也有可能会发胖——但无论生活中发生什么,有一样东西会永远与你同在……

你的名字。所以,既然你的名字将贯穿你的一生,为什么不在这上面花些精力呢?你的名字,或者说你的个人品牌,是非常宝贵的财富,也是许多软件开发人员没有意识到的财富。你的名字,或者说你的个人品牌,是你找工作、升职、挖掘潜在客户、甚至是自己创业的强大工具。哪怕你的名字从字面上看并不与众不同,但只要你有良好的知名度和声誉,那么有时候搞定诸多麻烦只是举手之劳而已。

我认识很多的软件开发人员因为已经具备了坚实的个人品牌,所以再也不必担心就业问题。因为无论发生什么事,他们都有把握找到另一份工作,因为他们的声誉众所周知。我们都听说过推销产品和服务,但你可曾想过推销自己?想在软件开发行业打造个人品牌,我的建议是写博客,选择一个特定的领域或专业,然后做到让你的名字如雷贯耳就行了。最好办法之一就是写一些对其他人有用的内容。

就拿博客举例。我写的博客可以在互联网上构建了我的品牌和声誉。如果你觉得这篇文章,甚至是我的网站有价值,那么你可能会分享。也可能会为此页添加书签,或者订阅相关邮件,这样你就不会错过任何好的资讯。这只是打造个人品牌的方式之一。你还可以创建YouTube视频,发表自己的播客,写文章写书,在活动中发言。但这并不意味着你必须做上述所有这些事情,这只是我认为不错的一些点子而已。

6.不搞点业余项目

我们手头应该总是有个业余项目在做。业余项目有很多你可能不知道的有益之处。

首先,业余项目是改善技能的有效方式。并且,这远远比你朝九晚五的工作能更快地提升你的成长速度。开发业余项目也是学习新技能新技术的好方法,有助于你寻找新工作。常常有很多程序员抱怨说现在千篇一律的工作没法让他们学习新技术,使得他们跟不上市场的脚步。听到这样的话,我总是劝他们不妨试着用心仪的新技术去开发业余项目,这绝对是个学习相关技能的好办法。而且,业余项目还可以让你赚点外快。可能你一开始不会想着用业余项目赚钱,但是业余项目的确是能让你获取额外的收入。

我大概在4年前开始开发Android和iOS app作为我的业余项目,并且至今它们依然在为我创造财富。我也认识不少软件开发人员最终将业余项目当作了他们的全职工作。开发业余项目其实很有趣。当你工作累了厌了,写一会自己喜欢的业余项目能很好地消除疲劳和压力。并且业余项目也是一个很好的出路,也许哪一天让你赚了大钱呢。

7.没有自我教育的规划

每次我面试软件开发人员时,问的第一个问题往往是关于他们自我教育和自我完善的规划。有没有去做点什么以便让自己成长得更为优秀呢?我经常会问他们用什么措施来跟上总是在不断变化的领域。我经常会问他们最近读了什么书,以及哪些是他们认为值得推荐给所有软件开发人员阅读的好书。我想从他们的答案中知道他们是否有一个用于自我教育,用于不断成长的确切规划。我之所以这么做是因为我知道一个致力于不断自我完善的人不仅会成就自己,也能带动周围的人一起朝着成功前行。

然而,很可惜的是,很多程序员都没有任何形式的自我教育规划。如果你还没有用于学习和提升自我技能的规划,那么是时候为自己制定一个了。想听听我推荐的一个简单规划吗?保证每个月阅读一本技术或职业发展类的书籍。一年下来你就能累计阅读12本。我个人的话,每天至少投入45分钟到阅读上。

请记住,千里之行始于足下。哪怕一天30分钟,持续一两年之后,就能给你带来巨大的改变。行动吧,骚年。希望这篇文章列举的这7个错误能警示各位,但是,如果你不采取任何行动,那么即使是灵丹妙药,也不会有一丝作用。所以,阅读完了之后,不要抛之脑后,请从今天就开始行动。先将定为至少改正自己已知的一个错误。

欢迎留下评论以及分享你的成果。

真正的勇士,敢于直面自己的不足之处,然后积极改正它们。

英文原文: https://simpleprogrammer.com/2015/05/18/7-mistakes-youre-making-in-your-programming-career/
译文链接: http://www.codeceo.com/article/7-mistakes-programmer-do.html
翻译作者: 码农网 – 小峰

阅读全文

为什么无服务器更适用于移动开发

原文: Alexander Stigsen    译者: lloog

译者注:作者通过介绍Realm移动平台,引出无服务器开发模式的优点,解释无服务器架构为什么适合移动开发。

当我们将旧版服务器堆栈取消时,构建移动应用程序将变得无限简单

当我们谈到构建移动应用时,我们真正的意思是构建与服务器技术交互的移动应用。这就意味着要与一个被设计为与以太网电缆连接的桌面计算机的世界进行交互。尽管世界已经超越了大屏幕和有线连接,但移动开发者还是不得不接受无休止的妥协,以获得他们想要的体验。

要交付有用的服务器端代码,您需要大量新颖的、特定领域的技能。当开发人员构建一个应用程序并将其连接到服务器时,数据并不会神奇地开始流入有用的列和行。在发出第一个请求之前,您必须部署和管理这些服务器。而devops让这种可能性变得更加容易,但它们占用很多时间。

接下来,你的服务器必须从请求中获取的任何格式(可能是JSON)数据进行序列化,然后必须将其存储在通常理解为SQL的数据库中,然后必须对该数据执行业务逻辑。 它将以服务器端语言完成所有这些操作,当然这与用于编写移动应用程序的Swift或Android Java不同。

简化服务器

现在有一个更好的方式,一种称为无服务器开发的新兴模式 ,在Realm,我们一直努力把这种模式带给移动开发人员。无服务架构旨在抽象出所有服务器端开发需要的基础设施和框架,从而,开发者只需要将注意力集中于:编写能够满足需求的,以及随时响应数据更改的代码。 服务器还在,但是所有的工作都已经消失了。

这是Realm移动平台的背后理念。因为Realm对象服务器是与Realm移动数据库一起工作的对象同步和事件处理服务器。它只要对数据模型或控制器进行最小的更改,便可以在设备之间无缝自动地将数据保持同步。

由于Realm对象服务器自动处理设备之间的数据同步,您可以直接进入Realm仪表板,创建一个新的Realm函数,然后开始编写JavaScript,以响应客户端应用程序生成的变化数据。

这与正常的服务器端开发有什么不同呢?作为一名移动开发人员,您即使没有掌握服务器端开发知识,也可以有效地开展工作。不需要考虑如何让服务器运行,也不需要考虑如何将数据传输到服务器,你不需要做devops,也不需要学习如何处理Postgres和Redis以及其他复杂的应用程序所需要的服务器端技术。

开发者不必学习一种全新的语言和框架,比如Django或Rails,只需要写一些JavaScript,而平台则负责我们所有使用框架的管道。该平台不需要处理中间件和URL路由,而是按照预期的格式获取所需的数据。您只需直接处理传入的数据,而不是构建基于rest的端点并将请求指向它们。

我过去编写Django应用程序时,常常将数据发送到新视图,结果需要用到四到五个文件中的几十行代码。 相比之下,无服务器的Realm函数中的JavaScript代码只包含重要的部分,你按下运行按钮后便立即开始运行。

专注于应用程序

你最终也会写很多不那么移动的代码。与其在你的移动应用程序中编写网络和序列化代码,你所要做的就是你所创建的模型和数据。因为这个平台可以处理同步,所以你可以专注于应用代码,这将会让你的应用变得很好,而不是为了让你的应用程序工作而需要的代码。您可以从以前编写的那些用于与rest式的API进行交互、占用您剩余的时间的脆弱的代码中解脱出来。

使用无服务器架构,您不再需要专门的devops和服务器团队。您不再需要知道服务器端框架, 只需要了解一点点JavaScript就足够了。而且,您甚至不需要编写与服务器通信所需的所有代码,因为该平台的设计初衷是为了避免此类工作。

Realm移动平台是一个将移动用例放在首位的无服务器平台。数据同步是优秀移动应用程序的基础(无论是显示你的Uber驾驶员的位置还是Facebook上的家人最新的图片)。服务器端编码也是必需的,但不是编写所有在移动应用程序之间连接和共享数据的样板代码。你可以专注于现在服务器上编写的代码,而无需学习超过javascript外的东西。

移动应用程序应该尽可能做到快速移动。像实时协作、双向数据同步、端点计算和“脱机优先”这样的特性通常都是昂贵且难以构建的。通过采用无服务器、移动优先的方式,开发人员可以拥有利用Realm功能来构建下一代的能力,而这只是我们用来构建的应用搭建舞台的一小部分资源。现在,我们可以开始工作,建设未来。

阅读全文

黑客是这样写JavaScript的

原文: garethheyes    译者: Tianyi_Ting    校核:myownghost

注 XSS攻击即Cross Site Scripting,通常在网页链接地址URL中注入JS代码来达到攻击手段,很多大厂都中过招,如:Twitter,新浪微博,示例代码:http://www.demo.cn/=<script>alert(document.cookie)</script>其实此代码并不能在所有浏览器上执行,但仅需要一部分浏览器(如IE6)可用,即可达到攻击效果。目前很多网站都有自动过滤XSS代码的功能,此文即介绍了一些如何屏蔽XSS过滤器的手段,其实我们可以发现,大多数在前端执行的XSS过滤都是不安全的,这对于我们在防范XSS攻击时有一定的借鉴意义。

我喜欢以一种意想不到的方式使用JavaScript,写出一些看起来奇怪但其实很管用的代码,这些代码常常能够执行一些出人意料功能。这听起来似 乎有些微不足道,但是基于这点发现足以总结出一些非常有用的编程技巧。下面写到的每一个小技巧都可以屏蔽掉XSS过滤器,这也是我写这些代码的初衷。然 而,学习这样的JavaScript代码可以明显加强你对语言本身的掌握,帮助你更好地处理输入,并且提高Web应用程序的安全性。

下面就看看这些令人惊异的JavaScript代码吧!

正则表达式替换可执行代码

当用到带有replace的正则表达式时,第二个参数支持函数赋值。在Opera中,可以利用这个参量执行代码。例如,下面这个代码片段:

'XSS'.replace(/XSS/g, alert)

这个执行的结果将会等价于:alert(‘XSS’); 产生这种现象的原因是正则表达式的匹配项被被当成一个参数,传递到了alert函数。一般情况下,在匹配文本上你会用一个函数调用另一段代码,像这样:

'somestring'.replace(/some/, function($1) {
    //do something with some
})

但是,正如在第一个例子中所看到的,我们执行了一个本地alert调用,而不是用户自定义函数,并且参数由正则表达式传递到了本地调用。这是个很酷的技巧,可以屏蔽掉一些XSS过滤器。例如,先写一个字符串,再跟一个“卯点”,接着就可以调用任何你想调用的函数啦。

为了看一看这个在XSS环境中是怎么使用的,想象一下:我们在字符串中有段未过滤的攻击代码,可能是JavaScript事件或者是script标签,即这个字符串中出现了一个注入。首先,我们注入一个有效的函数alert(1),接着我们突破这个引号的限制,最后再写我们的正则表达式。

.replace(/.+/,eval)//

注意我在这里用了eval函数执行我想执行的任何代码,并且为了使攻击代码传递给eval,正则表达式必须匹配所有项。

如果我把所有的代码放在一起,展示这个页的输出,这样的话就会更容易理解这个过程:

页输出:

<script>somevariableUnfiltered="YOUR INPUT"</script>

上面的代码在分析脚本中很常见,你上网搜索的所有字符串都被一些广告公司储存在这样的分析脚本中。你可能没有注意到这些脚本,但是如果 你观察一个 Web页面的源,你会发现这是经常出现的。另外,论坛也是一个经常会用到这些脚本的地方。“YOUR INPUT”是你所控制的字符串。如果输入没有被正确过滤时,这也将被称为基于DOM的XSS注入。(注:DOM,将 HTML 文档表达为树结构,通常指HTML结构)

输入:

alert(1)".replace(/.+/,eval)//

输出结果:

<script>somevariableUnfiltered="alert(1)".replace(/.+/,eval)//"</script>

注意这里”//”用于清除后面引用的单行注释。

Unicode 转义

尽管在对Unicode字符转义时,用圆括号是不太可能的,但是我们可以对正在被调用的函数名进行转义。例如:

\u0061\u006c\u0065\u0072\u0074(1)

这句代码调用了alert(1); \u表明这是个转义字符,并且在\u0061后面的十六进制数是“a”。

另外,常规字符可以和转义字符混合或匹配使用,下面的例子就展示了这一点:

\u0061lert(1)

你也可以将它们包含在字符串中,甚至用eval对它们求值。Unicode转义和常规的16进制或8进制转义有些不同,因为Unicode转义可以包含在一个字符串中,或者是引用函数、变量或对象中。

下面的例子展示了如何使用被求值并且被分成两部分的Unicode转义。

eval('\\u'+'0061'+'lert(1)')

通过避免像命名为alert这样的常规函数,我们就可以愚弄XSS过滤器注入我们的代码。这个例子就是用来绕过PHPIDS(一个开源的IDS系 统),最终导致规则变得更健壮。如果为了分析可能运行的恶意代码,你需要在解码JavaScript时,需要考虑过滤尽可能多的编码方法。就像在这个例子中看到的,这不是个容易的工作。

JavaScript解析器引擎

JavaScript是一个非常动态的语言。可以执行很大量的代码。这些代码第一眼看起来似乎不能执行,然而一旦理解了解析器工作的原理,你就能够逐渐理解它背后的逻辑。

JavaScript在函数执行之前是不知道函数结果的,并且很明显它必须通过调用函数返回变量的类型。这点很有趣,举个例子:如果返回函数不能返回代码块的一个有效值,就会在函数执行之后出现语法错误。

说的到底是什么意思呢?好吧!代码总比空谈更有说服力,看下面的例子:

+alert(1)--

alert函数执行后,返回一个未定义的量,然而已经有些太晚了,语法错误立刻就会出现,这是因为自减操作符的操作数应该是一个数字。

下面是一些不会产生错误的例子:

+alert(1)
1/alert(1)
alert(1)>>>/abc/

你可能认为上面的例子没有什么意义,但是实际上它们深刻体现了JavaScript的工作过程。一旦你理解了这些细节,JavaScript这个大 家伙就变得清晰,了解代码的执行方式可以帮助你理解解析器是怎么工作的。我觉得这类例子在追踪语法错误,检测基于DOM的XSS攻击和检测XSS过滤器的 时候很有用。

Throw,Delete还有什么?

你可以用想不到的方式进行删除操作,这会产生一些很古怪的语法。让我们看看将throw, delete, not和typeof操作符组合在一起会发生什么?

throw delete~typeof~alert(1)

你可能认为这句代码不能运行,但是使用函数调用delete却是可以的,仍旧能够执行:

delete alert(1)

这儿有一些更多的例子:

delete~[a=alert]/delete a(1)
delete [a=alert],delete a(1)

第 一眼看过去,你会认为这样的代码有语法错误,但是当你仔细分析后,你觉得会有几分道理。解析器先发现一个数组内部的变量赋值,执行赋值操作后删除 数组。同样地,删除操作是在一个函数(注* [a=alert])调用之后,因为删除操作需要在知道函数执行结果的情况下,才能删除返回的对象,即使返回的是NULL。

同时,这些代码可以用来屏蔽XSS过滤器,因为它们经常会尝试着匹配有效的语法,不希望代码太晦涩。当你的应用程序进行数据验证的时候,你应该考虑这样的例子。

声明全局对象

在屏蔽XSS过滤器的特定实例中,攻击代码经常隐藏在一个类似英语文本中的变量中。聪明的系统如PHPIDS,可以使用语法分析去比较判断访问请求是否是恶意攻击,所以这是测试这些系统很有用的方法。

仅使用全局对象或函数时,能够产生类似英文的代码块。事实上,在sla.ckers安全论坛上,我们可以玩个小游戏,用JavaScript形式产生类似英语的句子。为了了解这是怎么一回事,请看下面的例子:

stop, open, print && alert(1)

我自己杜撰了个名字,叫作Javascriptlish, 因为它可以产生一些看起来很不可思议的代码:

javascript : /is/^{ a : ' weird ' }[' & wonderful ']/" language "
the_fun: ['never '] + stop['s']

我们使用正则表达式/is/跟上一个操作符^,接着创造一个对象{ a : ‘weird’}(拥有a属性和赋值weird)。在我们刚刚创造的对象中,寻找’ & wonderful ‘属性,这个属性接着被一串字符分开。

接下来我们用一个命名为the_fun 的标识和一个带有never的数组,用一个命名为stop的全局函数检查s… 的属性,所有这些都是正确的语法。

Getters/Setters函数

当火狐增加 custom syntax for setters后, 屏蔽了一些不使用圆括弧的有趣XSS注入。Opera还不支持自定义语法—从安全角度来说,这是个优点,但对JavaScript黑客来说却不是个好 消息。然而Opera支持标准的defineSetter语法。这使我们能够通过赋值以达到调用函数的 目的,说起来这对屏蔽XSS过滤器来说也有些作用。

defineSetter('x',alert); x=1;

假如你不了解setters/getters,那么上面的例子就是为全局变量x创造了一个设值函数。当一个变量被设定时就会调用设值函数。第二个参数alert是函数调用赋值。这样,当x被赋值成1时,就会调用alert函数,并把1作为参数。

Location允许url编码

location对象允许url用JavaScript编码。这允许你通过双重编码进一步掩饰XSS注入。

location='javascript:%61%6c%65%72%74%28%31%29'

将它们与转义字符结合能够很好地隐藏字符串。

location='javascript:%5c%75%30%30%36%31%5c%75%30%30%36%63%5c%75%30%30%36%35%5c%75%30%30%37%32%5c%75%30%30%37%34(1)'

第一个例子是可行的,因为Opera的地址栏可以识别编码的地址串。通过用URL编码,你可以隐藏JavaScript代码。这点很有用,特别是当传递XSS攻击代码的时候,我们为了更进一步地屏蔽过滤,可以进行双重URL编码。

第二个例子结合了第一个例子利用转义字符的技巧。所以,当你对字符串解码时,就会导致alert函数以这样的形式显示:

\u0061\u006c\u0065\u0072\u0074

注* a 的ASCII编码为0x61

阅读全文

股权众筹鼻祖Naval Ravikant发表36条对于区块链乃至整个世界的思考,不得不读!

原文: Blockchain TweetStorm    译者: 安翔

当走过史前纪事、中本魔咒、以太野望、沧海横流,发展了几近半个世纪的区块链已然成为了一种社会思潮,它预示着人类社会转型、换代的新时代的到来,以分布式网络架构为技术基础的区块链让互联网时代的组织及经济发展规律悄然发生改变。

区块链用技术设计取代权威控制和情感信任,以此建立一种网络结构,所有人都可以参与成为无数节点之一,进行认证、确权、交易、追溯和调整等一系列动作,它公开透明,成本低、速度快、分布广,没有权威可以篡改伪造和取缔记录。我们可以充分地想象今天的商业、艺术、司法、科技、政治乃至社会等各个领域中,这样一个建立在运算能力和技术架构上的网络文明社会基础设施将是多么不同。

尽管它毫无情怀和冷冰冰地运作,但从根本上,摈弃了狂热理想的驱使、自命权威的霸道、垄断财团的曲扭、民粹阴谋的盲动,商业诈骗和情感敲诈也会随之水落石出。

无论我们是否喜欢,区块链理念所驱动的全新社会正在迅速形成。

作为全球股权众筹鼻祖,Naval Ravikant 投资了数百家公司,其中便包括 Twitter、Uber 等。除此之外,他还创立了一个用于天使投资人和初创企业的快速配对平台 AngelList。在不久前,Naval Ravikant 在一天的时间里发布了 36 条推文,其中所包含的,是他关于区块链对社会乃至整个世界的思考结果的精粹。

  1. 区块链将用市场取代网络。
  2. 人类是网络化的物种,是第一个跨越遗传边界从而掌控世界的物种。
  3. 网络为我们提供合作的机会,否则我们将孤身一人。网络分配我们合作所得的成果。
  4. 重叠的网络创造和组织我们的社会。物理的、数字化和精神上的道路共同将所有人联系在一起。
  5. 金钱是一个网络。宗教是一个网络。公司是一个网络。道路是一个网络。电力也是一个网络。
  6. 网络必须按照规则进行组织。他们要求统治者执行这些规则,用于防备骗子。
  7. 网络具有“网络效应”。新增用户可以增加所有现有用户的网络价值。
  8. 网络效应创造一个赢者通吃的胜者。领先的网络往往成为最后唯一存在的网络。
  9. 这些网络的统治者成为社会中最强大的人物。
  10. 有些网络由国王和牧师经营,他们制定货币和法律,神圣不可亵渎。规则依附于权力,并且对外界不开放。
  11. 有些网络由公司经营,比如社交网络、搜索网络、电话或有线网络。这些网络最初就是关闭的。
  12. 有些网络由精英阶层经营,比如大学网络、医疗网络、银行网络。有点开放,有点精明。
  13. 有些是由普通人经营的,比如民主网络、互联网、平民网络。开放但是不够精明而且效率很低。
  14. 专制在战争中比民主更有效率。互联网和物理公共空间被滥用和垃圾邮件超载。
  15. 20世纪创建了一种新型的网络 —— 市场网络。其特点是开放与精明。
  16. 市场的优点取决于资源。资源是金钱,一种冷冻和交易时间的形式。
  17. 市场网络是巨人。比如信贷市场、股市、商品市场、货币市场。
  18. 他们打破市场网络在有承诺的金钱工作。否则他们只是普通网络,到目前为止都应用有限。
  19. 区块链是一种新的发明,允许开放网络中的精英无需统治者和资金的情况下参与政权。
  20. 它们是基于优点的、防篡改的、开放的投票系统。
  21. 网络由有价值的人推动。
  22. 就像社会给你金钱给从而获取你在社会中想要的东西,区块链给你硬币从而得到你想要的网络。
  23. 需要注意的是,区块链使用它独有的货币进行支付,而不是传统金融市场的普通(美元)货币。
  24. 区块链使用硬币支付,但硬币只是用来追踪完成的工作。不同的区块链需要不同的工作。
  25. 比特币用于支付固定账单,Etherium支付(执行和验证)计算。
  26. 区块链将民主和互联网的开放与市场的优点相结合。
  27. 对于区块链来说,它的优点表现在多个方面,比如安全性、计算能力、预判性、注意力、带宽、功率、存储能力、分发、内容,等等。
  28. 区块链将市场模式引入到曾经无法达到的地方。
  29. 区块链基于市场的开放性和优点可以取代先之前由国王、公司、贵族和暴民经营的网络。
  30. 拥有一块没有硬币的区块链是无意义的,就像你空有市场但是手头却没钱一样,毫无意义。
  31. 拥有一个由专政、公司、精英或暴徒控制的区块链也是没有意义的。
  32. 区块链为我们提供了管理网络的新方法。可用于银行业务、投票系统、搜索、社交媒体,以及电话和电力网。
  33. 网络不需要国王、牧师、精英、公司和暴徒。它可以由任何有价值的人来管理。
  34. 基于区块链的市场网络将取代现有网络。从一件事开始慢慢扩大范围,逐渐实现完全取代。
  35. 最终,一个国家就只是一个网络(区块链网络)。
  36. 感谢中本聪,以及所有成就他的人。

阅读全文

服务端 I/O 性能大比拼:Node、PHP、Java 和 Go

理解应用程序的输入/输出(I/O)模型,意味着其在计划处理负载与残酷的实际使用场景之间的差异。若应用程序比较小,也没有服务于很高的负载,也许它影响甚微。但随着应用程序的负载逐渐上涨,采用错误的I/O模型有可能会让你到处踩坑,伤痕累累。

正如大部分存在多种解决途径的场景一样,重点不在于哪一种途径更好,而是在于理解如何进行权衡。让我们来参观下I/O的景观,看下可以从中窃取点什么。

在这篇文章,我们将会结合Apache分别比较Node,Java,Go,和PHP,讨论这些不同的语言如何对他们的I/O进行建模,各个模型的优点和缺点,并得出一些初步基准的结论。如果你关心下一个Web应用的I/O性能,那你就找对文章了。

I/O基础知识:快速回顾

为了理解与I/O密切相关的因素,必须先来回顾在操作系统底层的概念。虽然不会直接处理这些概念的大部分,但通过应用程序的运行时环境你一直在间接地处理他们。而关键在于细节。

系统调用

首先,我们有系统调用,它可以描述成这样:

  • 你的程序(在“用户区域”,正如他们所说的)必须让操作系统内核在它自身执行I/O操作。
  • “系统调用”(syscall)意味着你的程序要求内核做某事。不同的操作系统,实现系统调用的细节有所不同,但基本的概念是一样的。这将会有一些特定的指令,把控制权从你的程序转交到内核(类似函数调用但有一些专门用于处理这种场景的特殊sauce)。通常来说,系统调用是阻塞的,意味着你的程序需要等待内核返回到你的代码。
  • 内核在我们所说的物理设备(硬盘、网卡等)上执行底层的I/O操作,并回复给系统调用。在现实世界中,内核可能需要做很多事情才能完成你的请求,包括等待设备准备就绪,更新它的内部状态等,但作为一名应用程序开发人员,你可以不用关心这些。以下是内核的工作情况。


阻塞调用与非阻塞调用

好了,我刚刚在上面说系统调用是阻塞的,通常来说这是对的。然而,有些调用被分类为“非阻塞”,意味着内核接收了你的请求后,把它放进了队列或者缓冲的某个地方,然后立即返回而并没有等待实际的I/O调用。所以它只是“阻塞”了一段非常短的时间,短到只是把你的请求入列而已。

这里有一些有助于解释清楚的(Linux系统调用)例子:-read() 是阻塞调用——你传给它一个文件句柄和一个存放所读到数据的缓冲,然后此调用会在当数据好后返回。注意这种方式有着优雅和简单的优点。-epoll_create(), epoll_ctl()epoll_wait() 这些调用分别是,让你创建一组用于侦听的句柄,从该组添加/删除句柄,和然后直到有活动时才阻塞。这使得你可以通过一个线程有效地控制一系列I/O操作。如果需要这些功能,这非常棒,但也正如你所看到的,使用起来当然也相当复杂。

理解这里分时差异的数量级是很重要的。如果一个CPU内核运行在3GHz,在没有优化的情况下,它每秒执行30亿次循环(或者每纳秒3次循环)。非阻塞系统调用可能需要10纳秒这样数量级的周期才能完成——或者“相对较少的纳秒”。对于正在通过网络接收信息的阻塞调用可能需要更多的时间——例如200毫秒(0.2秒)。例如,假设非阻塞调用消耗了20纳秒,那么阻塞调用消耗了200,000,000纳秒。对于阻塞调用,你的程序多等待了1000万倍的时间。

内核提供了阻塞I/O(“从网络连接中读取并把数据给我”)和非阻塞I/O(“当这些网络连接有新数据时就告诉我”)这两种方法。而使用何种机制,对应调用过程的阻塞时间明显长度不同。

调度

接下来第三件关键的事情是,当有大量线程或进程开始阻塞时怎么办。

出于我们的目的,线程和进程之间没有太大的区别。实际上,最显而易见的执行相关的区别是,线程共享相同的内存,而每个进程则拥有他们独自的内存空间,使得分离的进程往往占据了大量的内存。但当我们讨论调度时,它最终可归结为一个事件清单(线程和进程类似),其中每个事件需要在有效的CPU内核上获得一片执行时间。如果你有300个线程正在运行并且运行在8核上,那么你得通过每个内核运行一段很短的时间然后切换到下一个线程的方式,把这些时间划分开来以便每个线程都能获得它的分时。这是通过“上下文切换”来实现的,使得CPU可以从正在运行的某个线程/进程切换到下一个。

这些上下文切换有一定的成本——它们消耗了一些时间。在快的时候,可能少于100纳秒,但是根据实现的细节,处理器速度/架构,CPU缓存等,消耗1000纳秒甚至更长的时间也并不罕见。

线程(或者进程)越多,上下文切换就越多。当我们谈论成千上万的线程,并且每一次切换需要数百纳秒时,速度将会变得非常慢。

然而,非阻塞调用本质上是告诉内核“当你有一些新的数据或者这些连接中的任意一个有事件时才调用我”。这些非阻塞调用设计于高效地处理大量的I/O负载,以及减少上下文切换。

到目前为止你还在看这篇文章吗?因为现在来到了有趣的部分:让我们来看下一些流利的语言如何使用这些工具,并就在易用性和性能之间的权衡作出一些结论……以及其他有趣的点评。

请注意,虽然在这篇文章中展示的示例是琐碎的(并且是不完整的,只是显示了相关部分的代码),但数据库访问,外部缓存系统(memcache等全部)和需要I/O的任何东西,都以执行某些背后的I/O操作而结束,这些和展示的示例一样有着同样的影响。同样地,对于I/O被描述为“阻塞”(PHP,Java)这样的情节,HTTP请求与响应的读取与写入本身是阻塞的调用:再一次,更多隐藏在系统中的I/O及其伴随的性能问题需要考虑。

为项目选择编程语言要考虑的因素有很多。当你只考虑性能时,要考虑的因素甚至有更多。但是,如果你关注的是程序主要受限于I/O,如果I/O性能对于你的项目至关重要,那这些都是你需要了解的。“保持简单”的方法:PHP。

回到90年代的时候,很多人穿着匡威鞋,用Perl写着CGI脚本。随后出现了PHP,很多人喜欢使用它,它使得制作动态网页更为容易。

PHP使用的模型相当简单。虽然有一些变化,但基本上PHP服务器看起来像:

HTTP请求来自用户的浏览器,并且访问了你的Apache网站服务器。Apache为每个请求创建一个单独的进程,通过一些优化来重用它们,以便最大程度地减少其需要执行的次数(创建进程相对来说较慢)。Apache调用PHP并告诉它在磁盘上运行相应的php文件。PHP代码执行并做一些阻塞的I/O调用。若在PHP中调用了file_get_contents(),那在背后它会触发read()系统调用并等待结果返回。

当然,实际的代码只是简单地嵌在你的页面中,并且操作是阻塞的:

<?php

// 阻塞的文件I/O
$file_data = file_get_contents('/path/to/file.dat');

// 阻塞的网络I/O
$curl = curl_init('http://example.com/example-microservice');
$result = curl_exec($curl);

// 更多阻塞的网络I/O
$result = $db->query('SELECT id, data FROM examples ORDER BY id DESC limit 100');

?>

关于它如何与系统集成,就像这样:

相当简单:一个请求,一个进程。I/O是阻塞的。优点是什么呢?简单,可行。那缺点是什么呢?同时与20,000个客户端连接,你的服务器就挂了。由于内核提供的用于处理大容量I/O(epoll等)的工具没有被使用,所以这种方法不能很好地扩展。更糟糕的是,为每个请求运行一个单独的进程往往会使用大量的系统资源,尤其是内存,这通常是在这样的场景中遇到的第一件事情。

注意:Ruby使用的方法与PHP非常相似,在广泛而普遍的方式下,我们可以将其视为是相同的。

多线程的方式:Java

所以就在你买了你的第一个域名的时候,Java来了,并且在一个句子之后随便说一句“dot com”是很酷的。而Java具有语言内置的多线程(特别是在创建时),这一点非常棒。

大多数Java网站服务器通过为每个进来的请求启动一个新的执行线程,然后在该线程中最终调用作为应用程序开发人员的你所编写的函数。

在Java的Servlet中执行I/O操作,往往看起来像是这样:

public void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException
{

    // 阻塞的文件I/O
    InputStream fileIs = new FileInputStream("/path/to/file");

    // 阻塞的网络I/O
    URLConnection urlConnection = (new URL("http://example.com/example-microservice")).openConnection();
    InputStream netIs = urlConnection.getInputStream();

    // 更多阻塞的网络I/O
    out.println("...");
}

由于我们上面的doGet方法对应于一个请求并且在自己的线程中运行,而不是每次请求都对应需要有自己专属内存的单独进程,所以我们会有一个单独的线程。这样会有一些不错的优点,例如可以在线程之间共享状态、共享缓存的数据等,因为它们可以相互访问各自的内存,但是它如何与调度进行交互的影响,仍然与前面PHP例子中所做的内容几乎一模一样。每个请求都会产生一个新的线程,而在这个线程中的各种I/O操作会一直阻塞,直到这个请求被完全处理为止。为了最小化创建和销毁它们的成本,线程会被汇集在一起,但是依然,有成千上万个连接就意味着成千上万个线程,这对于调度器是不利的。

一个重要的里程碑是,在Java 1.4 版本(和再次显著升级的1.7 版本)中,获得了执行非阻塞I/O调用的能力。大多数应用程序,网站和其他程序,并没有使用它,但至少它是可获得的。一些Java网站服务器尝试以各种方式利用这一点; 然而,绝大多数已经部署的Java应用程序仍然如上所述那样工作。

Java让我们更进了一步,当然对于I/O也有一些很好的“开箱即用”的功能,但它仍然没有真正解决问题:当你有一个严重I/O绑定的应用程序正在被数千个阻塞线程狂拽着快要坠落至地面时怎么办。

作为一等公民的非阻塞I/O:Node

当谈到更好的I/O时,Node.js无疑是新宠。任何曾经对Node有过最简单了解的人都被告知它是“非阻塞”的,并且它能有效地处理I/O。在一般意义上,这是正确的。但魔鬼藏在细节中,当谈及性能时这个巫术的实现方式至关重要。

本质上,Node实现的范式不是基本上说“在这里编写代码来处理请求”,而是转变成“在这里写代码开始处理请求”。每次你都需要做一些涉及I/O的事情,发出请求或者提供一个当完成时Node会调用的回调函数。

在请求中进行I/O操作的典型Node代码,如下所示:

http.createServer(function(request, response) {  
    fs.readFile('/path/to/file', 'utf8', function(err, data) {
        response.end(data);
    });
});

可以看到,这里有两个回调函数。第一个会在请求开始时被调用,而第二个会在文件数据可用时被调用。

这样做的基本上给了Node一个在这些回调函数之间有效地处理I/O的机会。一个更加相关的场景是在Node中进行数据库调用,但我不想再列出这个烦人的例子,因为它是完全一样的原则:启动数据库调用,并提供一个回调函数给Node,它使用非阻塞调用单独执行I/O操作,然后在你所要求的数据可用时调用回调函数。这种I/O调用队列,让Node来处理,然后获取回调函数的机制称为“事件循环”。它工作得非常好。

然而,这个模型中有一道关卡。在幕后,究其原因,更多是如何实现JavaScript V8 引擎(Chrome的JS引擎,用于Node),而不是其他任何事情。你所编写的JS代码全部都运行在一个线程中。思考一下。这意味着当使用有效的非阻塞技术执行I/O时,正在进行CPU绑定操作的JS可以在运行在单线程中,每个代码块阻塞下一个。 一个常见的例子是循环数据库记录,在输出到客户端前以某种方式处理它们。以下是一个例子,演示了它如何工作:

var handler = function(request, response) {
    connection.query('SELECT ...', function (err, rows) {
        if (err) { throw err };
        for (var i = 0; i < rows.length; i++) {
            // 对每一行纪录进行处理
        }
        response.end(...); // 输出结果
    })
};

虽然Node确实可以有效地处理I/O,但上面的例子中的for循环使用的是在你主线程中的CPU周期。这意味着,如果你有10,000个连接,该循环有可能会让你整个应用程序慢如蜗牛,具体取决于每次循环需要多长时间。每个请求必须分享在主线程中的一段时间,一次一个。

这个整体概念的前提是I/O操作是最慢的部分,因此最重要是有效地处理这些操作,即使意味着串行进行其他处理。这在某些情况下是正确的,但不是全都正确。

另一点是,虽然这只是一个意见,但是写一堆嵌套的回调可能会令人相当讨厌,有些人认为它使得代码明显无章可循。在Node代码的深处,看到嵌套四层、嵌套五层、甚至更多层级的嵌套并不罕见。

我们再次回到了权衡。如果你主要的性能问题在于I/O,那么Node模型能很好地工作。然而,它的阿喀琉斯之踵(译者注:来自希腊神话,表示致命的弱点)是如果不小心的话,你可能会在某个函数里处理HTTP请求并放置CPU密集型代码,最后使得每个连接慢得如蜗牛。

真正的非阻塞:Go

在进入Go这一章节之前,我应该披露我是一名Go粉丝。我已经在许多项目中使用Go,是其生产力优势的公开支持者,并且在使用时我在工作中看到了他们。

也就是说,我们来看看它是如何处理I/O的。Go语言的一个关键特性是它包含自己的调度器。并不是每个线程的执行对应于一个单一的OS线程,Go采用的是“goroutines”这一概念。Go运行时可以将一个goroutine分配给一个OS线程并使其执行,或者把它挂起而不与OS线程关联,这取决于goroutine做的是什么。来自Go的HTTP服务器的每个请求都在单独的Goroutine中处理。

此调度器工作的示意图,如下所示:

这是通过在Go运行时的各个点来实现的,通过将请求写入/读取/连接/等实现I/O调用,让当前的goroutine进入睡眠状态,当可采取进一步行动时用信息把goroutine重新唤醒。

实际上,除了回调机制内置到I/O调用的实现中并自动与调度器交互外,Go运行时做的事情与Node做的事情并没有太多不同。它也不受必须把所有的处理程序代码都运行在同一个线程中这一限制,Go将会根据其调度器的逻辑自动将Goroutine映射到其认为合适的OS线程上。最后代码类似这样:

func ServeHTTP(w http.ResponseWriter, r *http.Request) {
    // 这里底层的网络调用是非阻塞的
    rows, err := db.Query("SELECT ...")
    for _, row := range rows {
        // 处理rows
        // 每个请求在它自己的goroutine中
    }
    w.Write(...) // 输出响应结果,也是非阻塞的
}

正如你在上面见到的,我们的基本代码结构像是更简单的方式,并且在背后实现了非阻塞I/O。

在大多数情况下,这最终是“两个世界中最好的”。非阻塞I/O用于全部重要的事情,但是你的代码看起来像是阻塞,因此往往更容易理解和维护。Go调度器和OS调度器之间的交互处理了剩下的部分。这不是完整的魔法,如果你建立的是一个大型的系统,那么花更多的时间去理解它工作原理的更多细节是值得的; 但与此同时,“开箱即用”的环境可以很好地工作和很好地进行扩展。

Go可能有它的缺点,但一般来说,它处理I/O的方式不在其中。

谎言,诅咒的谎言和基准

对这些各种模式的上下文切换进行准确的定时是很困难的。也可以说这对你来没有太大作用。所以取而代之,我会给出一些比较这些服务器环境的HTTP服务器性能的基准。请记住,整个端对端的HTTP请求/响应路径的性能与很多因素有关,而这里我放在一起所提供的数据只是一些样本,以便可以进行基本的比较。

对于这些环境中的每一个,我编写了适当的代码以随机字节读取一个64k大小的文件,运行一个SHA-256哈希N次(N在URL的查询字符串中指定,例如.../test.php?n=100),并以十六进制形式打印生成的散列。我选择了这个示例,是因为使用一些一致的I/O和一个受控的方式增加CPU使用率来运行相同的基准测试是一个非常简单的方式。关于环境使用,更多细节请参考这些基准要点

首先,来看一些低并发的例子。运行2000次迭代,并发300个请求,并且每次请求只做一次散列(N = 1),可以得到:

时间是在全部并发请求中完成请求的平均毫秒数。越低越好。

很难从一个图表就得出结论,但对于我来说,似乎与连接和计算量这些方面有关,我们看到时间更多地与语言本身的一般执行有关,因此更多在于I/O。请注意,被认为是“脚本语言”(输入随意,动态解释)的语言执行速度最慢。

但是如果将N增加到1000,仍然并发300个请求,会发生什么呢 —— 相同的负载,但是hash迭代是之前的100倍(显着增加了CPU负载):

时间是在全部并发请求中完成请求的平均毫秒数。越低越好。

忽然之间,Node的性能显着下降了,因为每个请求中的CPU密集型操作都相互阻塞了。有趣的是,在这个测试中,PHP的性能要好得多(相对于其他的语言),并且打败了Java。(值得注意的是,在PHP中,SHA-256实现是用C编写的,执行路径在这个循环中花费更多的时间,因为这次我们进行了1000次哈希迭代)。

现在让我们尝试5000个并发连接(并且N = 1)—— 或者接近于此。不幸的是,对于这些环境的大多数,失败率并不明显。对于这个图表,我们会关注每秒的请求总数。越高越好:

每秒的请求总数。越高越好。

这张照片看起来截然不同。这是一个猜测,但是看起来像是对于高连接量,每次连接的开销与产生新进程有关,而与PHP + Apache相关联的额外内存似乎成为主要的因素并制约了PHP的性能。显然,Go是这里的冠军,其次是Java和Node,最后是PHP。

结论

综上所述,很显然,随着语言的演进,处理大量I/O的大型应用程序的解决方案也随之不断演进。

为了公平起见,暂且抛开本文的描述,PHP和Java确实有可用于Web应用程序的非阻塞I/O的实现。 但是这些方法并不像上述方法那么常见,并且需要考虑使用这种方法来维护服务器的伴随的操作开销。更不用说你的代码必须以与这些环境相适应的方式进行结构化; “正常”的PHP或Java Web应用程序通常不会在这样的环境中进行重大改动。

作为比较,如果只考虑影响性能和易用性的几个重要因素,可以得到:

语言 线程或进程 非阻塞I/O 易用性
PHP 进程
Java 线程 可用 需要回调
Node.js 线程 需要回调
Go 线程(Goroutine) 不需要回调

线程通常要比进程有更高的内存效率,因为它们共享相同的内存空间,而进程则没有。结合与非阻塞I/O相关的因素,当我们向下移动列表到一般的启动时,因为它与改善I/O有关,可以看到至少与上面考虑的因素一样。如果我不得不在上面的比赛中选出一个冠军,那肯定会是Go。

即便这样,在实践中,选择构建应用程序的环境与你的团队对于所述环境的熟悉程度以及可以实现的总体生产力密切相关。因此,每个团队只是一味地扎进去并开始用Node或Go开发Web应用程序和服务可能没有意义。事实上,寻找开发人员或内部团队的熟悉度通常被认为是不使用不同的语言和/或不同的环境的主要原因。也就是说,过去的十五年来,时代已经发生了巨大的变化。

希望以上内容可以帮助你更清楚地了解幕后所发生的事件,并就如何处理应用程序现实世界中的可扩展性为你提供的一些想法。快乐输入,快乐输出!

原文出处: BRAD PEABODY     译文出处: dogstar

阅读全文

话题数

0

回复数

0