作者:李骁
os 包有一个 StartProcess 函数可以调用或启动外部系统命令和二进制可执行文件;它的第一个参数是要运行的进程,第二个参数用来传递选项或参数,第三个参数是含有系统环境基本信息的结构体。
这个函数返回被启动进程的 id(pid),或者启动失败返回错误。
exec 包中也有同样功能的更简单的结构体和函数;主要是 exec.Command(name string, arg ...string) 和 Run()。首先需要用系统命令或可执行文件的名字创建一个 Command 对象,然后用这个对象作为接收器调用 Run()。下面的程序(因为是执行 Linux 命令,只能在 Linux 下面运行)演示了它们的使用:
package main
import (
"fmt"
"os"
)
func main() {
// 1) os.StartProcess //
/*********************/
/* Linux: */
env := os.Environ()
procAttr := &os.ProcAttr{
Env: env,
Files: []*os.File{
os.Stdin,
os.Stdout,
os.Stderr,
},
}
// 1st example: list files
Pid, err := os.StartProcess("/bin/ls", []string{"ls", "-l"}, procAttr)
if err != nil {
fmt.Printf("Error %v starting process!", err) //
os.Exit(1)
}
fmt.Printf("The process id is %v", pid)
}
信号处理在平
作者:李骁
对于文件和目录的操作,Go主要在os 提供了的相应函数:
func Mkdir(name string, perm FileMode) error
func Chdir(dir string) error
func TempDir() string
func Rename(oldpath, newpath string) error
func Chmod(name string, mode FileMode) error
func Open(name string) (*File, error) {
return OpenFile(name, O_RDONLY, 0)
}
func Create(name string) (*File, error) {
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
testlog.Open(name)
return openFileNolog(name, flag, perm)
}
从上面函数定义中我们可以发现一个情况:那就是os中不同函数打开(创建)文件的操作,最终还是通过OpenFile来实现,而OpenFile由编译器根据系统的情况来选择不同的底层功能来实现,对这个实现细节有兴趣可以根据标准包来仔细了解,这里就不展开讲了。
os.Open(name string) 使用只读模式打开文件;
os.Create(name string) 创建新文件,如文件存在则原文件内容会丢失;
os.OpenFile(name string, flag int, perm FileMode) 这个函数可以指定flag和FileMode 。这三个函数都会返回一个文件对象。
Flag:
O_RDONLY int = syscall.O_RDONLY // 只读打开文件和os.Open()同义
O_WRONLY int =
作者:李骁
上一章我们有提到fmt格式化I/O,这一章我们就详细来说说。在fmt包,有关格式化输入输出的方法就两大类:Scan 和 Print ,分别在scan.go 和 print.go 文件中。
print.go文件中定义了如下函数:
func Printf(format string, a ...interface{}) (n int, err error)
func Fprintf(w io.Writer, format string, a ...interface{}) (n int, err error)
func Sprintf(format string, a ...interface{}) string
func Print(a ...interface{}) (n int, err error)
func Fprint(w io.Writer, a ...interface{}) (n int, err error)
func Sprint(a ...interface{}) string
func Println(a ...interface{}) (n int, err error)
func Fprintln(w io.Writer, a ...interface{}) (n int, err error)
func Sprintln(a ...interface{}) string
这9个函数,按照两个维度来说明,基本上可以说明白了。当然这两个维度是我个人为了记忆而分,并不是官方的说法。
一:如果把"Print"理解为核心关键字,那么后面跟的后缀有"f"和"ln"以及"",着重的是内容输出的结果;
如果后缀是"f", 则指定了format
如果后缀是"ln", 则有换行符
Println、Fprintln、Sprintln 输出内容时会加上换行符;
Print、Fprint、Sprint 输出内容时不加上换行符;
Printf、Fprintf、Sprintf 按照指定格式化文本输出内容。
作者:李骁
tcp/udp、ip构成了网络通信的基石,tcp/ip是面向连接的通信协议,要求建立连接时进行3次握手确保连接已被建立,关闭连接时需要4次通信来保证客户端和服务端都已经关闭,也就是我们常说的三次握手,四次挥手。在通信过程中还有保证数据不丢失,在连接不畅通时还需要进行超时重试等等。
socket就是封装了这一套基于tcp/udp/ip协议细节,提供了一系列套接字接口进行通信。
我们知道Socket有两种:TCP Socket和UDP Socket,TCP和UDP是协议,而要确定一个进程的需要三元组,还需要IP地址和端口。
目前的全球因特网所采用的协议族是TCP/IP协议。IP是TCP/IP协议中网络层的协议,是TCP/IP协议族的核心协议。目前主要采用的IP协议的版本号是4(简称为IPv4),IPv4的地址位数为32位,也就是最多有2的32次方的网络设备可以联到Internet上。
地址格式类似这样:127.0.0.1
IPv6是新一版本的互联网协议,也可以说是新一代互联网的协议,它是为了解决IPv4在实施过程中遇到的各种问题而被提出的,IPv6采用128位地址长度,几乎可以不受限制地提供地址。在IPv6的设计过程中除了一劳永逸地解决了地址短缺问题以外,还考虑了在IPv4中解决不好的其它问题,主要有端到端IP连接、服务质量(QoS)、安全性、多播、移动性、即插即用等。
地址格式类似这样:2002:c0e8:82e7:0:0:0:c0e8:82e7
Go是自带runtime的跨平台编程语言,Go中暴露给语言使用者的tcp socket api是建立OS原生tcp socket接口之上的,所以在使用上相对简单。
TCP Socket
建立网络连接过程:TCP连接的建立需要经历客户端和服务端的三次握手的过程。Go 语言net包封装了系列API,在TCP连接中,服务端是一个标准的Listen + Accept的结构,而在客户端Go语言使用net.Dial或
作者:李骁
写命令行程序时需要对命令参数进行解析,这时我们可以使用os库。os可以通过变量Args来获取命令参数,os.Args返回一个字符串数组,其中第一个参数就是执行文件本身。
package main
import (
"fmt"
"os"
)
func main() {
fmt.Println(os.Args)
}
编译执行后执行
$ ./cmd -user="root"
[./cmd -user=root]
这种方式操作起来要自己封装,比较费时费劲。
Go提供了flag库,可以很方便的操作命名行参数,下面介绍下flag的用法。
几个概念:
1)命令行参数(或参数):是指运行程序提供的参数
2)已定义命令行参数:是指程序中通过flag.Xxx等这种形式定义了的参数
3)非flag(non-flag)命令行参数(或保留的命令行参数):先可以简单理解为flag包不能解析的参数
package main
import (
"flag"
"fmt"
"os"
)
var (
h, H bool
v bool
q *bool
D string
Conf string
)
func init() {
flag.BoolVar(&h, "h", false, "帮助信息")
flag.BoolVar(&h, "H", false, "帮助信息")
flag.BoolVar(&v, "v", false, "显示版本号")
//
flag.StringVar(&D, "D", "deamon", "set descripton ")
flag.StringVar(&Conf, "Conf", "/dev/conf/cli.conf", "set Conf filename ")
// 另一种绑定方式
q = flag.Bool("q", false, "退出程序")
// 像flag.Xxx函数格式都是一样的
作者:李骁
Printf也可以做到输出格式化,当然,对于简单的例子来说足够了,但是我们有时候还是需要复杂的输出格式,甚至需要将格式化代码分离开来。这时,可以使用text/template和html/template。
Go 官方库提供了两个模板库: text/template 和 html/template 。这两个库类似,当需要输出html格式的代码时需要使用 html/template。
所谓模板引擎,则将模板和数据进行渲染的输出格式化后的字符程序。对于Go,执行这个流程大概需要三步。
其中最后一步就是把加载的字符和数据进行格式化。
package main
import (
"log"
"os"
"text/template"
)
const templ = `
{{range .}}----------------------------------------
Name: {{.Name}}
Price: {{.Price | printf "%4s"}}
{{end}}`
var report = template.Must(template.New("report").Parse(templ))
type Book struct {
Name string
Price float64
}
func main() {
Data := []Book{{"《三国演义》", 19.82}, {"《儒林外史》", 99.09}, {"《史记》", 26.89}}
if err := report.Execute(os.Stdout, Data); err != nil {
log.Fatal(err)
}
}
程序输出:
----------------------------------------
Name: 《三国演义》
Price: %!s(float64=19.82)
-------------------------------------
作者:李骁
在Go中,搭建一个http server简单到令人难以置信。只需要引入net/http包,写几行代码,一个http服务器就可以正常运行并接受访问请求。
下面就是Go最简单的http服务器:
package main
import (
"fmt"
"net/http"
)
func myfunc(w http.ResponseWriter, r *http.Request) {
fmt.Fprintf(w, "hi")
}
func main() {
http.HandleFunc("/", myfunc)
http.ListenAndServe(":8080", nil)
}
访问 http://localhost:8080/ , 我们可以看到网页输出"hi" !
下面我们通过分析net/http的源代码,来深入理解这个包的实现原理。在net/http源代码中,我们可以深深体会到Go语言的接口设计哲学,这个包主要有4个文件,分别是:
client.go
server.go
request.go
response.go
我们知道有http Request 请求和 http Response 响应,以及client和server,我们先从这四个方面讲讲net/http包:
http Request请求是由客户端发出的消息, 用来使服务器执行动作.发出的消息包括起始行, Headers, Body。
在net/http包中,request.go文件定义了结构:
type Request struct
代表客户端给服务器端发送的一个请求或者是服务端收到的一个请求,但是服务器端和客户端使用Request时语义区别很大。
// 利用指定的method, url以及可选的body返回一个新的请求.如果body参数实现了
// io.Closer接口,Request返回值的Body 字段会被设置为body,并会被Client
// 类型的Do、Post和PostForm方法以及Transport.RoundTrip
作者:李骁
在Go中,每个请求的request在单独的goroutine中进行,处理一个request也可能涉及多个goroutine之间的交互。一个请求衍生出的各个 goroutine 之间需要满足一定的约束关系,以实现一些诸如有效期,中止routine树,传递请求全局变量之类的功能。于是Go为我们提供一个解决方案,标准context包。使用context可以使开发者方便的在这些goroutine之间传递request相关的数据、取消goroutine的signal或截止时间等。
每个goroutine在执行之前,都要先知道程序当前的执行状态,通常将这些执行状态封装在一个Context变量中,传递给要执行的goroutine中。上下文则几乎已经成为传递与请求同生存周期变量的标准方法。在网络编程下,当接收到一个网络请求Request,处理Request时,我们可能需要开启不同的goroutine来获取数据与逻辑处理,即一个请求Request,会在多个goroutine中处理。而这些goroutine可能需要共享Request的一些信息;同时当Request被取消或者超时的时候,所有从这个Request创建的所有goroutine也应该被结束。
context包不仅实现了在程序单元之间共享状态变量的方法,同时能通过简单的方法,使我们在被调用程序单元的外部,通过设置ctx变量值,将过期或撤销这些信号传递给被调用的程序单元。若存在A调用B的API,B再调用C的API,若A调用B取消,那也要取消B调用C,通过在A, B, C的API调用之间传递Context,以及判断其状态。
Context结构
// Context包含过期,取消信号,request值传递等,方法在多个goroutine中协程安全
type Context interface {
// Done 方法在context被取消或者超时返回一个close的channel
Done() <-chan struct{}
Err() error
// Deadline 返回con
作者:李骁
数据结构要在网络中传输或保存到文件,就必须对其编码和解码;目前存在很多编码格式:Json,XML,Gob,Google 缓冲协议等等。Go 语言支持所有这些编码格式。
结构可能包含二进制数据,如果将其作为文本打印,那么可读性是很差的。另外结构内部可能包含匿名字段,而不清楚数据的用意。
通过把数据转换成纯文本,使用命名的字段来标注,让其具有可读性。这样的数据格式可以通过网络传输,而且是与平台无关的,任何类型的应用都能够读取和输出,不与操作系统和编程语言的类型相关。
下面是一些术语说明:
序列化是在内存中把数据转换成指定格式(data -> string),反之亦然(string -> data structure)
编码也是一样的,只是输出一个数据流(实现了 io.Writer 接口);解码是从一个数据流(实现了 io.Reader)输出到一个数据结构。
Json.Marshal() 的函数签名是 func Marshal(v interface{}) ([]byte, error)
出于安全考虑,在 web 应用中最好使用 Json.MarshalforHTML() 函数,其对数据执行HTML转码,所以文本可以被安全地嵌在 HTML
作者:李骁
Go 提供了database/sql包用于对SQL数据库的访问,作为操作数据库的入口对象sql.DB,主要为我们提供了两个重要的功能:
需要注意的是,sql.DB表示操作数据库的抽象访问接口, 而非一个数据库连接对象;它可以根据driver打开关闭数据库连接,管理连接池。正在使用的连接被标记为繁忙,用完后回到连接池等待下次使用。所以,如果你没有把连接释放回连接池,会导致过多连接使系统资源耗尽。
导入mysql数据库驱动
import (
"database/sql"
_ "github.com/go-sql-driver/mysql"
)
通常来说,不应该直接使用驱动所提供的方法,而是应该使用 sql.DB,因此在导入 mysql 驱动时,这里使用了匿名导入的方式(在包路径前添加 _),当导入了一个数据库驱动后,此驱动会自行初始化并注册自己到Go的database/sql上下文中,因此我们就可以通过 database/sql 包提供的方法访问数据库了。
我们先建立表结构:
CREATE TABLE t_article_cate (
`cid` int(10) NOT NULL AUTO_INCREMENT,
`cname` varchar(60) NOT NULL,
`ename` varchar(100),
`cateimg` varchar(255),
`addtime` int(10) unsigned NOT NULL DEFAULT '0',
`publishtime` int(10) unsigned NOT NULL DEFAULT '0',
`scope` int(10) unsigned NOT NULL DEFAULT '10000',
`status` tinyint(1) unsigned NOT NULL