Servers
用Go编写网络服务器是非常容易的。我们首先看一下如何创建一个TCP服务器:
package main
import (
"encoding/gob"
"fmt"
"net"
)
func server() {
// listen on a port
ln, err := net.Listen("tcp", ":9999")
if err != nil {
fmt.Println(err)
return
}
for {
// accept a connection
c, err := ln.Accept()
if err != nil {
fmt.Println(err)
continue
}
// handle the connection
go handleServerConnection(c)
}
}
func handleServerConnection(c net.Conn) {
// receive the message
var msg string
err := gob.NewDecoder(c).Decode(&msg)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Received", msg)
}
c.Close()
}
func client() {
// connect to the server
c, err := net.Dial("tcp", "127.0.0.1:9999")
if err != nil {
fmt.Println(err)
return
}
// send the message
msg := "Hello World"
fmt.Println("Sending", msg)
err = gob.NewEncoder(c).Encode(msg)
if err != nil {
fmt.Println(err)
}
c.Close()
}
func main() {
go server()
go client()
var input string
fmt.Scanln(&input)
}
这个例子使用了encoding/gob包,它可以很容易地对Go的数值进行编码,以便其他G程序(或者在这种情况下是同一个Go程序)可以读取它们。在encoding下面的包(如encoding/json)以及第三方包中都有其他的编码方式。(例如,我们可以使用 /v2/mgo/bson来支持bson)。
HTTP
HTTP服务器的设置和使用更加容易。
package main
import (
"io"
"net/http"
)
func hello(res http.ResponseWriter, req *http.Request) {
res.Header().Set(
"Content-Type",
"text/html",
)
io.WriteString(
res,
`<doctype html>
<html>
<head>
<title>Hello World</title>
</head>
<body>
Hello World!
</body>
</html>`,
)
}
func main() {
http.HandleFunc("/hello", hello)
http.ListenAndServe(":9000", nil)
}
HandleFunc通过调用给定的函数来处理一个URL路由(/hello)。我们还可以通过使用FileServer来处理静态文件
http.Handle(
"/assets/",
http.StripPrefix(
"/assets/",
http.FileServer(http.Dir("assets")),
),
)
RPC
net/rpc(远程过程调用)和 net/rpc/jsonrpc
软件包提供了一种简单的方法来公开方法,因此它们可以通过网络被调用。 (而不是仅仅在运行它们的程序中)。
package main
import (
"fmt"
"net"
"net/rpc"
)
type Server struct{}
func (this *Server) Negate(i int64, reply *int64) error {
*reply = -i
return nil
}
func server() {
rpc.Register(new(Server))
ln, err := net.Listen("tcp", ":9999")
if err != nil {
fmt.Println(err)
return
}
for {
c, err := ln.Accept()
if err != nil {
continue
}
go rpc.ServeConn(c)
}
}
func client() {
c, err := rpc.Dial("tcp", "127.0.0.1:9999")
if err != nil {
fmt.Println(err)
return
}
var result int64
err = c.Call("Server.Negate", int64(999),
&result)
if err != nil {
fmt.Println(err)
} else {
fmt.Println("Server.Negate(999) =",
result)
}
}
func main() {
go server()
go client()
var input string
fmt.Scanln(&input)
}
这个程序与TCP的例子相似,只是现在我们创建了一个对象来保存我们想要公开的所有方法,并且我们从 客户端调用否定方法。更多细节请参见 net/rpc 的文档。
剖析命令行参数
当我们在终端上调用一个命令时,有可能将该命令的参数传递给它。我们已经在go命令中看到了这一点
go run myfile.go
run 和 myfile.go 是参数。我们还可以将标志给一个命令。
go run -v myfile.go
标志包允许我们解析发送给我们程序的参数和标志。下面是一个生成0到6之间的数字的程序实例。我们可以通过发送一个标志(max=100)来改变最大值 程序
package main
import ("fmt";"flag";"math/rand"
)
func main() {
// Define flags
maxp := flag.Int("max", 6, "the max value")
// Parse
flag.Parse()
// Generate a number between 0 and max
fmt.Println(rand.Intn(*maxp))
}
任何额外的非flag参数都可以通过 flag.Args()
来获取。用 flag.Args() 检索,它返回一个字符串数组。
同步原语
在Go中处理并发和同步的首选方式是通过goroutines和channel,正如第10章所讨论的。然而,Go也提供了更多传统的多线程例程,如sync 和 sync/atomic 包中提供了更为传统的多线程例程.
Mutexes
互斥锁(mutex)将一段代码锁定在一个单一的线程上。锁定一段代码,用于保护共享资源不受非原子操作的影响。用于保护共享资源免受非原子操作的影响。下面是 一个互斥锁的例子:
package main
import (
"fmt"
"sync"
"time"
)
func main() {
m := new(sync.Mutex)
for i := 0; i < 10; i++ {
go func(i int) {
m.Lock()
fmt.Println(i, "start")
time.Sleep(time.Second)
fmt.Println(i, "end")
m.Unlock()
}(i)
}
var input string
fmt.Scanln(&input)
}
当 mutex (m)
被锁定时,任何其他试图锁定的行为都会被阻止,直到它被解锁。在使用mutexes或sync/atomic包中提供的同步原语时,应该非常小心。
传统的多线程编程是很困难的;很容易犯错,而且这些错误很难发现,因为它们可能取决于非常具体的、相对罕见的、难以重现的一系列情况。Go最大的优势之一是它所提供的并发功能比线程和锁更容易理解和正确使用。