From 2fe72e35c851e090bfd52131090d07d07fcd1f8d Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 16:22:38 +0800 Subject: [PATCH 01/20] Create 15.0.md --- eBook/15.0.md | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 eBook/15.0.md diff --git a/eBook/15.0.md b/eBook/15.0.md new file mode 100644 index 0000000..db4c71c --- /dev/null +++ b/eBook/15.0.md @@ -0,0 +1,9 @@ +# 15.0 网络,模板和web应用 + +go在编写web应用方面非常得力。因为目前它还没有GUI(Graphic User Interface 即图形化用户界面)的框架,通过文本或者模板展现的html界面是目前go编写应用程序的唯一方式。(**译者注:实际上在翻译的时候,已经有了一些不太成熟的GUI库例如:go ui。) + +## 链接 + +- [目录](directory.md) +- 上一节:[使用通道(channel)并发修改对象数据](14.17.md) +- 下一节:[一个Tcp服务器](15.1.md) From 3d5fade00dc9dbfccb094c993d83a2760bc2bac1 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 16:28:17 +0800 Subject: [PATCH 02/20] Create 15.1.md --- eBook/15.1.md | 333 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 333 insertions(+) create mode 100644 eBook/15.1.md diff --git a/eBook/15.1.md b/eBook/15.1.md new file mode 100644 index 0000000..6d606e0 --- /dev/null +++ b/eBook/15.1.md @@ -0,0 +1,333 @@ +# 15.1 基本类型和运算符 + +这部分我们将使用TCP协议和在14章讲到的携程范式编写一个简单的客户端-服务器应用,一个(web)服务器应用需要响应众多客户端的并发请求:go会为每一个客户端产生一个携程用来处理请求。我们需要使用net包中网络通信的功能。它包含了用于TCP/IP以及UDP协议、域名解析等方法。 + +服务器代码,单独的一个文件: + +示例 15.1 [server.go](examples/chapter_15/server.go) +```go +package main + +import ( + "fmt" + "net" +) + +func main() { + fmt.Println("Starting the server ...") + // 创建 listener + listener, err := net.Listen("tcp", "localhost:50000") + if err != nil { + fmt.Println("Error listening", err.Error()) + return //终止程序 + } + // 监听并接受来自客户端的连接 + for { + conn, err := listener.Accept() + if err != nil { + fmt.Println("Error accepting", err.Error()) + return // 终止程序 + } + go doServerStuff(conn) + } +} + +func doServerStuff(conn net.Conn) { + for { + buf := make([]byte, 512) + _, err := conn.Read(buf) + if err != nil { + fmt.Println("Error reading", err.Error()) + return //终止程序 + } + fmt.Printf("Received data: %v", string(buf)) + } +} + +``` + +我们在`main()`创建了一个`net.Listener`的变量,他是一个服务器的基本函数:用来监听和接收来自客户端的请求(来自localhost即IP地址为127.0.0.1端口为50000基于TCP协议)。这个`Listen()`函数可以返回一个`error`类型的错误变量。用一个无限for循环的`listener.Accept()`来等待客户端的请求。客户端的请求将产生一个`net.Conn`类型的连接变量。然后一个独立的携程使用这个连接执行`doServerStuff()`,开始使用一个512字节的缓冲`data`来读取客户端发送来的数据并且把它们打印到服务器的终端;当客户端发送的所有数据都被读取完成时,携程就结束了。这段程序会为每一个客户端连接创建一个独立的携程。必须先运行服务器代码,再运行客户端代码。 + +客户端代码写在另外一个文件client.go中: + +示例 15.2 [client.go](examples/chapter_15/client.go) +```go +package main + +import ( + "bufio" + "fmt" + "net" + "os" + "strings" +) + +func main() { + //打开连接: + conn, err := net.Dial("tcp", "localhost:50000") + if err != nil { + //由于目标计算机积极拒绝而无法创建连接 + fmt.Println("Error dialing", err.Error()) + return // 终止程序 + } + + inputReader := bufio.NewReader(os.Stdin) + fmt.Println("First, what is your name?") + clientName, _ := inputReader.ReadString('\n') + // fmt.Printf("CLIENTNAME %s", clientName) + trimmedClient := strings.Trim(clientName, "\r\n") // Windows 平台下用 "\r\n",Linux平台下使用 "\n" + // 给服务器发送信息直到程序退出: + for { + fmt.Println("What to send to the server? Type Q to quit.") + input, _ := inputReader.ReadString('\n') + trimmedInput := strings.Trim(input, "\r\n") + // fmt.Printf("input:--s%--", input) + // fmt.Printf("trimmedInput:--s%--", trimmedInput) + if trimmedInput == "Q" { + return + } + _, err = conn.Write([]byte(trimmedClient + " says: " + trimmedInput)) + } +} +``` +客户端通过`net.Dial`创建了一个和服务器之间的连接 + +它通过无限循环中的os.Stdin接收来自键盘的输入直到输入了“Q”。注意使用`\r`和`\n`换行符分割字符串(在windows平台下使用`\r\n`)。接下来分割后的输入通过`connection`的`Write`方法被发送到服务器。 + +当然,服务器必须先启动好,如果服务器并未开始监听,客户端是无法成功连接的。 + +如果在服务器没有开始监听的情况下运行客户端程序,客户端会停止并打印出以下错误信息:`对tcp 127.0.0.1:50000发起连接时产生错误:由于目标计算机的积极拒绝而无法创建连接`。 + +打开控制台并转到服务器和客户端可执行程序所在的目录,Windows系统下输入server.exe(或者只输入server),Linux系统下输入./server。 + +接下来控制台出现以下信息:`Starting the server ...` + +在Windows系统中,我们可以通过CTRL/C停止程序。 + +然后开启2个或者3个独立的控制台窗口,然后分别输入client回车启动客户端程序 + +以下是服务器的输出(在移除掉512字节的字符串中内容为空的区域后): +``` +Starting the Server ... +Received data: IVO says: Hi Server, what's up ? +Received data: CHRIS says: Are you busy server ? +Received data: MARC says: Don't forget our appointment tomorrow ! +``` +当客户端输入 Q 并结束程序时,服务器会输出以下信息: +``` +Error reading WSARecv tcp 127.0.0.1:50000: The specified network name is no longer available. +``` +在网络编程中`net.Dial`函数是非常重要的,一旦你连接到远程系统,就会返回一个Conn类型接口,我们可以用它发送和接收数据。`Dial`函数巧妙的抽象了网络结构及传输。所以IPv4或者IPv6,TCP或者UDP都可以使用这个公用接口。 + +下边这个示例先使用TCP协议连接远程80端口,然后使用UDP协议连接,最后使用TCP协议连接IPv6类型的地址: + +示例 15.3 [dial.go](examples/chapter_15/dial.go) +```go +// make a connection with www.example.org: +package main + +import ( + "fmt" + "net" + "os" +) + +func main() { + conn, err := net.Dial("tcp", "192.0.32.10:80") // tcp ipv4 + checkConnection(conn, err) + conn, err = net.Dial("udp", "192.0.32.10:80") // udp + checkConnection(conn, err) + conn, err = net.Dial("tcp", "[2620:0:2d0:200::10]:80") // tcp ipv6 + checkConnection(conn, err) +} +func checkConnection(conn net.Conn, err error) { + if err != nil { + fmt.Printf("error %v connecting!") + os.Exit(1) + } + fmt.Println("Connection is made with %v", conn) +} +``` +下边也是一个使用net包从socket中打开,写入,读取数据的例子: + +示例 15.4 [socket.go](examples/chapter_15/socket.go) +```go +package main + +import ( + "fmt" + "io" + "net" +) + +func main() { + var ( + host = "www.apache.org" + port = "80" + remote = host + ":" + port + msg string = "GET / \n" + data = make([]uint8, 4096) + read = true + count = 0 + ) + // 创建一个socket + con, err := net.Dial("tcp", remote) + // 发送我们的消息,一个http GET请求 + io.WriteString(con, msg) + // 读取服务器的响应 + for read { + count, err = con.Read(data) + read = (err == nil) + fmt.Printf(string(data[0:count])) + } + con.Close() +} +``` +**练习 15.1** 编写新版本的客户端和服务器(client1.to / server1.go): +* 增加一个检查错误的函数`checkError(error)`;讨论如下方案的利弊:为什么这个重构可能并没有那么理想?看看在示例15.14中它是如何被解决的 +* 使客户端可以通过发送一条命令SH来关闭服务器 +* 让服务器可以保存已经连接的客户端列表(他们的名字);当客户端发送WHO指令的时候,服务器将显示如下列表: + +``` +This is the client list: 1:active, 0=inactive +User IVO is 1 +User MARC is 1 +User CHRIS is 1 +``` +注意:当服务器运行的时候,你无法编译/连接同一个目录下的源码来产生一个新的版本,因为`server.exe`正在被操作系统使用而无法被替换成新的版本。 + +下边这个版本的 simple_tcp_server.go 从很多方面优化了第一个tcp服务器的示例 server.go 并且拥有更好的结构,它只用了80行代码! + +示例 15.5 [simple_tcp_server.go](examples/chapter_15/simple_tcp_server.go): +```go +// Simple multi-thread/multi-core TCP server. +package main + +import ( + "flag" + "fmt" + "net" + "os" +) + +const maxRead = 25 + +func main() { + flag.Parse() + if flag.NArg() != 2 { + panic("usage: host port") + } + hostAndPort := fmt.Sprintf("%s:%s", flag.Arg(0), flag.Arg(1)) + listener := initServer(hostAndPort) + for { + conn, err := listener.Accept() + checkError(err, "Accept: ") + go connectionHandler(conn) + } +} +func initServer(hostAndPort string) *net.TCPListener { + serverAddr, err := net.ResolveTCPAddr("tcp", hostAndPort) + checkError(err, "Resolving address:port failed: '"+hostAndPort+"'") + listener, err := net.ListenTCP("tcp", serverAddr) + checkError(err, "ListenTCP: ") + println("Listening to: ", listener.Addr().String()) + return listener +} +func connectionHandler(conn net.Conn) { + connFrom := conn.RemoteAddr().String() + println("Connection from: ", connFrom) + sayHello(conn) + for { + var ibuf []byte = make([]byte, maxRead+1) + length, err := conn.Read(ibuf[0:maxRead]) + ibuf[maxRead] = 0 // to prevent overflow + switch err { + case nil: + handleMsg(length, err, ibuf) + case os.EAGAIN: // try again + continue + default: + goto DISCONNECT + } + } +DISCONNECT: + err := conn.Close() + println("Closed connection: ", connFrom) + checkError(err, "Close: ") +} +func sayHello(to net.Conn) { + obuf := []byte{'L', 'e', 't', '\'', 's', ' ', 'G', 'O', '!', '\n'} + wrote, err := to.Write(obuf) + checkError(err, "Write: wrote "+string(wrote)+" bytes.") +} +func handleMsg(length int, err error, msg []byte) { + if length > 0 { + print("<", length, ":") + for i := 0; ; i++ { + if msg[i] == 0 { + break + } + fmt.Printf("%c", msg[i]) + } + print(">") + } +} +func checkError(error error, info string) { + if error != nil { + panic("ERROR: " + info + " " + error.Error()) // terminate program + } +} +``` +>(**译者注:应该是由于go版本的更新,会提示os.EAGAIN undefined ,修改后的代码:[simple_tcp_server_v1.go](examples/chapter_15/simple_tcp_server_v1.go)**) + +都有哪些改进? +(1)服务器地址和端口不再是硬编码,而是通过命令行传入参数并通过`flag`包来读取这些参数。这里使用了`flag.NArg()`检查是否按照期望传入了2个参数: +```go + if flag.NArg() != 2{ + panic("usage: host port") + } +``` +传入的参数通过`fmt.Sprintf`函数格式化成字符串 +```go + hostAndPort := fmt.Sprintf("%s:%s", flag.Arg(0), flag.Arg(1)) +``` +(2)在`initServer`函数中通过`net.ResolveTCPAddr`指定了服务器地址和端口,这个函数最终返回了一个`*net.TCPListener` +(3)每一个连接都会以携程的方式运行`connectionHandler`函数。这些开始于当通过`conn.RemoteAddr()`获取到客户端的地址 +(4)它使用`conn.Write`发送改进的go-message给客户端 +(5)它使用一个25字节的缓冲读取客户端发送的数据并一一打印出来。如果读取的过程中出现错误,代码会进入`switch`语句的`default`分支关闭连接。如果是操作系统的`EAGAIN`错误,它会重试。 +(6)所有的错误检查都被重构在独立的函数'checkError'中,用来分发出现的上下文错误。 + +在命令行中输入`simple_tcp_server localhost 50000`来启动服务器程序,然后在独立的命令行窗口启动一些client.go的客户端。当有两个客户端连接的情况下服务器的典型输出如下,这里我们可以看到每个客户端都有自己的地址: +``` +E:\Go\GoBoek\code examples\chapter 14>simple_tcp_server localhost 50000 +Listening to: 127.0.0.1:50000 +Connection from: 127.0.0.1:49346 +<25:Ivo says: Hi server, do y><12:ou hear me ?> +Connection from: 127.0.0.1:49347 +<25:Marc says: Do you remembe><25:r our first meeting serve><2:r?> +``` +net.Error: +这个`net`包返回错误的错误类型,下边是约定的写法,不过`net.Error`接口还定义了一些其他的错误实现,有些额外的方法。 +```go +package net + +type Error interface{ + Timeout() bool // 错误是否超时 + Temporary() bool // 是否是临时错误 +``` +通过类型断言,客户端代码可以用来测试`net.Error`,从而区分哪些临时发生的错误或者必然会出现的错误。举例来说,一个网络爬虫程序在遇到临时发生的错误时可能会休眠或者重试,如果是一个必然发生的错误,则他会放弃继续执行。 +```go +// in a loop - some function returns an error err +if nerr, ok := err.(net.Error); ok && nerr.Temporary(){ + time.Sleep(1e9) + continue // try again +} +if err != nil{ + log.Fatal(err) +``` + +## 链接 + +- [目录](directory.md) +- 上一节:[网络、模版与网页应用](15.0.md) +- 下一节:[一个简单的web服务器](15.2.md) From 2c2a390a246695ac978a4f1438016f614488b5be Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 16:30:17 +0800 Subject: [PATCH 03/20] Create client.go --- eBook/examples/chapter_15/client.go | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 eBook/examples/chapter_15/client.go diff --git a/eBook/examples/chapter_15/client.go b/eBook/examples/chapter_15/client.go new file mode 100644 index 0000000..bedb8be --- /dev/null +++ b/eBook/examples/chapter_15/client.go @@ -0,0 +1,37 @@ +package main + +import ( + "bufio" + "fmt" + "net" + "os" + "strings" +) + +func main() { + //打开连接: + conn, err := net.Dial("tcp", "localhost:50000") + if err != nil { + //由于目标计算机积极拒绝而无法创建连接 + fmt.Println("Error dialing", err.Error()) + return // 终止程序 + } + + inputReader := bufio.NewReader(os.Stdin) + fmt.Println("First, what is your name?") + clientName, _ := inputReader.ReadString('\n') + // fmt.Printf("CLIENTNAME %s", clientName) + trimmedClient := strings.Trim(clientName, "\r\n") // Windows 平台下用 "\r\n",Linux平台下使用 "\n" + // 给服务器发送信息直到程序退出: + for { + fmt.Println("What to send to the server? Type Q to quit.") + input, _ := inputReader.ReadString('\n') + trimmedInput := strings.Trim(input, "\r\n") + // fmt.Printf("input:--s%--", input) + // fmt.Printf("trimmedInput:--s%--", trimmedInput) + if trimmedInput == "Q" { + return + } + _, err = conn.Write([]byte(trimmedClient + " says: " + trimmedInput)) + } +} From c0410a66a8c793b324c345ec1fa6de563784cdf5 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 16:30:43 +0800 Subject: [PATCH 04/20] Create dial.go --- eBook/examples/chapter_15/dial.go | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 eBook/examples/chapter_15/dial.go diff --git a/eBook/examples/chapter_15/dial.go b/eBook/examples/chapter_15/dial.go new file mode 100644 index 0000000..432cea0 --- /dev/null +++ b/eBook/examples/chapter_15/dial.go @@ -0,0 +1,24 @@ +// make a connection with www.example.org: +package main + +import ( + "fmt" + "net" + "os" +) + +func main() { + conn, err := net.Dial("tcp", "192.0.32.10:80") // tcp ipv4 + checkConnection(conn, err) + conn, err = net.Dial("udp", "192.0.32.10:80") // udp + checkConnection(conn, err) + conn, err = net.Dial("tcp", "[2620:0:2d0:200::10]:80") // tcp ipv6 + checkConnection(conn, err) +} +func checkConnection(conn net.Conn, err error) { + if err != nil { + fmt.Printf("error %v connecting!") + os.Exit(1) + } + fmt.Println("Connection is made with %v", conn) +} From 25d03712c864a6e63aa68a1ae35948c40a152576 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 16:31:14 +0800 Subject: [PATCH 05/20] Create server.go --- eBook/examples/server.go | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 eBook/examples/server.go diff --git a/eBook/examples/server.go b/eBook/examples/server.go new file mode 100644 index 0000000..39c32b0 --- /dev/null +++ b/eBook/examples/server.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + "net" +) + +func main() { + fmt.Println("Starting the server ...") + // 创建 listener + listener, err := net.Listen("tcp", "localhost:50000") + if err != nil { + fmt.Println("Error listening", err.Error()) + return //终止程序 + } + // 监听并接受来自客户端的连接 + for { + conn, err := listener.Accept() + if err != nil { + fmt.Println("Error accepting", err.Error()) + return // 终止程序 + } + go doServerStuff(conn) + } +} + +func doServerStuff(conn net.Conn) { + for { + buf := make([]byte, 512) + _, err := conn.Read(buf) + if err != nil { + fmt.Println("Error reading", err.Error()) + return //终止程序 + } + fmt.Printf("Received data: %v", string(buf)) + } +} From e5c4b4a3ce2c6c33c41f1b3a1e57e195fbf3ec1f Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 16:31:49 +0800 Subject: [PATCH 06/20] Create simple_tcp_server.go --- .../examples/chapter_15/simple_tcp_server.go | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 eBook/examples/chapter_15/simple_tcp_server.go diff --git a/eBook/examples/chapter_15/simple_tcp_server.go b/eBook/examples/chapter_15/simple_tcp_server.go new file mode 100644 index 0000000..8b7ffea --- /dev/null +++ b/eBook/examples/chapter_15/simple_tcp_server.go @@ -0,0 +1,77 @@ +// Simple multi-thread/multi-core TCP server. +package main + +import ( + "flag" + "fmt" + "os" + "net" +) + +const maxRead = 25 + +func main() { + flag.Parse() + if flag.NArg() != 2 { + panic("usage: host port") + } + hostAndPort := fmt.Sprintf("%s:%s", flag.Arg(0), flag.Arg(1)) + listener := initServer(hostAndPort) + for { + conn, err := listener.Accept() + checkError(err, "Accept: ") + go connectionHandler(conn) + } +} +func initServer(hostAndPort string) *net.TCPListener { + serverAddr, err := net.ResolveTCPAddr("tcp", hostAndPort) + checkError(err, "Resolving address:port failed: '"+hostAndPort+"'") + listener, err := net.ListenTCP("tcp", serverAddr) + checkError(err, "ListenTCP: ") + println("Listening to: ", listener.Addr().String()) + return listener +} +func connectionHandler(conn net.Conn) { + connFrom := conn.RemoteAddr().String() + println("Connection from: ", connFrom) + sayHello(conn) + for { + var ibuf []byte = make([]byte, maxRead+1) + length, err := conn.Read(ibuf[0:maxRead]) + ibuf[maxRead] = 0 // to prevent overflow + switch err { + case nil: + handleMsg(length, err, ibuf) + case os.EAGAIN: // try again + continue + default: + goto DISCONNECT + } + } +DISCONNECT: + err := conn.Close() + println("Closed connection: ", connFrom) + checkError(err, "Close: ") +} +func sayHello(to net.Conn) { + obuf := []byte{'L', 'e', 't', '\'', 's', ' ', 'G', 'O', '!', '\n'} + wrote, err := to.Write(obuf) + checkError(err, "Write: wrote "+string(wrote)+" bytes.") +} +func handleMsg(length int, err error, msg []byte) { + if length > 0 { + print("<", length, ":") + for i := 0; ; i++ { + if msg[i] == 0 { + break + } + fmt.Printf("%c", msg[i]) + } + print(">") + } +} +func checkError(error error, info string) { + if error != nil { + panic("ERROR: " + info + " " + error.Error()) // terminate program + } +} From 412725d3b0cfa1f3b6860e1b70a1577ee0ced7fd Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 16:32:06 +0800 Subject: [PATCH 07/20] Create server.go --- eBook/examples/chapter_15/server.go | 37 +++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) create mode 100644 eBook/examples/chapter_15/server.go diff --git a/eBook/examples/chapter_15/server.go b/eBook/examples/chapter_15/server.go new file mode 100644 index 0000000..39c32b0 --- /dev/null +++ b/eBook/examples/chapter_15/server.go @@ -0,0 +1,37 @@ +package main + +import ( + "fmt" + "net" +) + +func main() { + fmt.Println("Starting the server ...") + // 创建 listener + listener, err := net.Listen("tcp", "localhost:50000") + if err != nil { + fmt.Println("Error listening", err.Error()) + return //终止程序 + } + // 监听并接受来自客户端的连接 + for { + conn, err := listener.Accept() + if err != nil { + fmt.Println("Error accepting", err.Error()) + return // 终止程序 + } + go doServerStuff(conn) + } +} + +func doServerStuff(conn net.Conn) { + for { + buf := make([]byte, 512) + _, err := conn.Read(buf) + if err != nil { + fmt.Println("Error reading", err.Error()) + return //终止程序 + } + fmt.Printf("Received data: %v", string(buf)) + } +} From 2b0568318e0a07c946c3f876dd0e6b48fd329701 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 16:32:30 +0800 Subject: [PATCH 08/20] Create simple_tcp_server_v1.go --- .../chapter_15/simple_tcp_server_v1.go | 77 +++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 eBook/examples/chapter_15/simple_tcp_server_v1.go diff --git a/eBook/examples/chapter_15/simple_tcp_server_v1.go b/eBook/examples/chapter_15/simple_tcp_server_v1.go new file mode 100644 index 0000000..6ad96aa --- /dev/null +++ b/eBook/examples/chapter_15/simple_tcp_server_v1.go @@ -0,0 +1,77 @@ +// Simple multi-thread/multi-core TCP server. +package main + +import ( + "flag" + "fmt" + "net" + "syscall" +) + +const maxRead = 25 + +func main() { + flag.Parse() + if flag.NArg() != 2 { + panic("usage: host port") + } + hostAndPort := fmt.Sprintf("%s:%s", flag.Arg(0), flag.Arg(1)) + listener := initServer(hostAndPort) + for { + conn, err := listener.Accept() + checkError(err, "Accept: ") + go connectionHandler(conn) + } +} +func initServer(hostAndPort string) *net.TCPListener { + serverAddr, err := net.ResolveTCPAddr("tcp", hostAndPort) + checkError(err, "Resolving address:port failed: '"+hostAndPort+"'") + listener, err := net.ListenTCP("tcp", serverAddr) + checkError(err, "ListenTCP: ") + println("Listening to: ", listener.Addr().String()) + return listener +} +func connectionHandler(conn net.Conn) { + connFrom := conn.RemoteAddr().String() + println("Connection from: ", connFrom) + sayHello(conn) + for { + var ibuf []byte = make([]byte, maxRead+1) + length, err := conn.Read(ibuf[0:maxRead]) + ibuf[maxRead] = 0 // to prevent overflow + switch err { + case nil: + handleMsg(length, err, ibuf) + case syscall.Errno(0xb): // try again + continue + default: + goto DISCONNECT + } + } +DISCONNECT: + err := conn.Close() + println("Closed connection: ", connFrom) + checkError(err, "Close: ") +} +func sayHello(to net.Conn) { + obuf := []byte{'L', 'e', 't', '\'', 's', ' ', 'G', 'O', '!', '\n'} + wrote, err := to.Write(obuf) + checkError(err, "Write: wrote "+string(wrote)+" bytes.") +} +func handleMsg(length int, err error, msg []byte) { + if length > 0 { + print("<", length, ":") + for i := 0; ; i++ { + if msg[i] == 0 { + break + } + fmt.Printf("%c", msg[i]) + } + print(">") + } +} +func checkError(error error, info string) { + if error != nil { + panic("ERROR: " + info + " " + error.Error()) // terminate program + } +} From c3a8ab833acc0844a7f8571b4104587ce7b3e550 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 16:32:47 +0800 Subject: [PATCH 09/20] Create socket.go --- eBook/examples/chapter_15/socket.go | 30 +++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 eBook/examples/chapter_15/socket.go diff --git a/eBook/examples/chapter_15/socket.go b/eBook/examples/chapter_15/socket.go new file mode 100644 index 0000000..3260ea6 --- /dev/null +++ b/eBook/examples/chapter_15/socket.go @@ -0,0 +1,30 @@ +package main + +import ( + "fmt" + "io" + "net" +) + +func main() { + var ( + host = "www.apache.org" + port = "80" + remote = host + ":" + port + msg string = "GET / \n" + data = make([]uint8, 4096) + read = true + count = 0 + ) + // 创建一个socket + con, err := net.Dial("tcp", remote) + // 发送我们的消息,一个http GET请求 + io.WriteString(con, msg) + // 读取服务器的响应 + for read { + count, err = con.Read(data) + read = (err == nil) + fmt.Printf(string(data[0:count])) + } + con.Close() +} From 00abf99ff2daba7c782451411a2eec012c2e854b Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 16:34:58 +0800 Subject: [PATCH 10/20] Update 15.0.md --- eBook/15.0.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/15.0.md b/eBook/15.0.md index db4c71c..3f8cb77 100644 --- a/eBook/15.0.md +++ b/eBook/15.0.md @@ -1,4 +1,4 @@ -# 15.0 网络,模板和web应用 +# 15.0 网络,模板和网页应用 go在编写web应用方面非常得力。因为目前它还没有GUI(Graphic User Interface 即图形化用户界面)的框架,通过文本或者模板展现的html界面是目前go编写应用程序的唯一方式。(**译者注:实际上在翻译的时候,已经有了一些不太成熟的GUI库例如:go ui。) From 4632d27c71ece0b0cb7e9164ec3bb9543203cb01 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 19:51:01 +0800 Subject: [PATCH 11/20] Update directory.md --- eBook/directory.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/directory.md b/eBook/directory.md index db10141..3474898 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -136,7 +136,7 @@ - 13.9 [用(测试数据)表驱动测试](13.9.md) - 13.10 [性能调试:分析并优化 Go 程序](13.10.md) - 第14章:[协程(goroutine)与通道(channel)](14.0.md) -- 第15章:网络、模版与网页应用 +- 第15章:[网络、模版与网页应用](15.0.md) ## 第四部分:实际应用 From 989b909ba8f04a75f87b7e90e3ca3b8443c185bf Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 20:06:58 +0800 Subject: [PATCH 12/20] Update 15.1.md --- eBook/15.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/15.1.md b/eBook/15.1.md index 6d606e0..82cb4c1 100644 --- a/eBook/15.1.md +++ b/eBook/15.1.md @@ -1,4 +1,4 @@ -# 15.1 基本类型和运算符 +# 15.1 网络、模版与网页应用 这部分我们将使用TCP协议和在14章讲到的携程范式编写一个简单的客户端-服务器应用,一个(web)服务器应用需要响应众多客户端的并发请求:go会为每一个客户端产生一个携程用来处理请求。我们需要使用net包中网络通信的功能。它包含了用于TCP/IP以及UDP协议、域名解析等方法。 From 0ab5bcd91d491be1a19a9bca2da2559a700ca945 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 20:08:28 +0800 Subject: [PATCH 13/20] Update 15.1.md --- eBook/15.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/15.1.md b/eBook/15.1.md index 82cb4c1..15eb478 100644 --- a/eBook/15.1.md +++ b/eBook/15.1.md @@ -1,4 +1,4 @@ -# 15.1 网络、模版与网页应用 +# 15.1 tcp服务器 这部分我们将使用TCP协议和在14章讲到的携程范式编写一个简单的客户端-服务器应用,一个(web)服务器应用需要响应众多客户端的并发请求:go会为每一个客户端产生一个携程用来处理请求。我们需要使用net包中网络通信的功能。它包含了用于TCP/IP以及UDP协议、域名解析等方法。 From 2576336174844e65727a772f90fca81bb0b93817 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 20:09:11 +0800 Subject: [PATCH 14/20] Update directory.md --- eBook/directory.md | 1 + 1 file changed, 1 insertion(+) diff --git a/eBook/directory.md b/eBook/directory.md index 3474898..2b930af 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -137,6 +137,7 @@ - 13.10 [性能调试:分析并优化 Go 程序](13.10.md) - 第14章:[协程(goroutine)与通道(channel)](14.0.md) - 第15章:[网络、模版与网页应用](15.0.md) + - 13.1 [tcp服务器](15.1.md) ## 第四部分:实际应用 From 1738d60347c133ae5fef2c1fe7ec1aec121b0874 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 20:09:55 +0800 Subject: [PATCH 15/20] Update 15.1.md --- eBook/15.1.md | 1 + 1 file changed, 1 insertion(+) diff --git a/eBook/15.1.md b/eBook/15.1.md index 15eb478..fd51135 100644 --- a/eBook/15.1.md +++ b/eBook/15.1.md @@ -281,6 +281,7 @@ func checkError(error error, info string) { >(**译者注:应该是由于go版本的更新,会提示os.EAGAIN undefined ,修改后的代码:[simple_tcp_server_v1.go](examples/chapter_15/simple_tcp_server_v1.go)**) 都有哪些改进? + (1)服务器地址和端口不再是硬编码,而是通过命令行传入参数并通过`flag`包来读取这些参数。这里使用了`flag.NArg()`检查是否按照期望传入了2个参数: ```go if flag.NArg() != 2{ From a8f0bd56cab9977d12e5af40caa776eb105c8e73 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 20:11:04 +0800 Subject: [PATCH 16/20] Update 15.1.md --- eBook/15.1.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/15.1.md b/eBook/15.1.md index fd51135..3ee46ae 100644 --- a/eBook/15.1.md +++ b/eBook/15.1.md @@ -278,7 +278,7 @@ func checkError(error error, info string) { } } ``` ->(**译者注:应该是由于go版本的更新,会提示os.EAGAIN undefined ,修改后的代码:[simple_tcp_server_v1.go](examples/chapter_15/simple_tcp_server_v1.go)**) +(**译者注:应该是由于go版本的更新,会提示os.EAGAIN undefined ,修改后的代码:[simple_tcp_server_v1.go](examples/chapter_15/simple_tcp_server_v1.go)**) 都有哪些改进? From 3118c364ca8ff41fdc99a2f451a2c199b231a79e Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 20:11:52 +0800 Subject: [PATCH 17/20] Update directory.md --- eBook/directory.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/eBook/directory.md b/eBook/directory.md index 2b930af..f7121be 100644 --- a/eBook/directory.md +++ b/eBook/directory.md @@ -137,7 +137,7 @@ - 13.10 [性能调试:分析并优化 Go 程序](13.10.md) - 第14章:[协程(goroutine)与通道(channel)](14.0.md) - 第15章:[网络、模版与网页应用](15.0.md) - - 13.1 [tcp服务器](15.1.md) + - 15.1 [tcp服务器](15.1.md) ## 第四部分:实际应用 From da831d9c8dacabc704d25efafee26675309a86f3 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 20:15:11 +0800 Subject: [PATCH 18/20] Update 15.1.md --- eBook/15.1.md | 1 + 1 file changed, 1 insertion(+) diff --git a/eBook/15.1.md b/eBook/15.1.md index 3ee46ae..a9f49e3 100644 --- a/eBook/15.1.md +++ b/eBook/15.1.md @@ -325,6 +325,7 @@ if nerr, ok := err.(net.Error); ok && nerr.Temporary(){ } if err != nil{ log.Fatal(err) +} ``` ## 链接 From 6ba196338e8755ff3fce2994dd2def4e5b84b59a Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 20:17:30 +0800 Subject: [PATCH 19/20] Update simple_tcp_server.go --- eBook/examples/chapter_15/simple_tcp_server.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/eBook/examples/chapter_15/simple_tcp_server.go b/eBook/examples/chapter_15/simple_tcp_server.go index 8b7ffea..8e56d6e 100644 --- a/eBook/examples/chapter_15/simple_tcp_server.go +++ b/eBook/examples/chapter_15/simple_tcp_server.go @@ -23,6 +23,7 @@ func main() { go connectionHandler(conn) } } + func initServer(hostAndPort string) *net.TCPListener { serverAddr, err := net.ResolveTCPAddr("tcp", hostAndPort) checkError(err, "Resolving address:port failed: '"+hostAndPort+"'") @@ -31,6 +32,7 @@ func initServer(hostAndPort string) *net.TCPListener { println("Listening to: ", listener.Addr().String()) return listener } + func connectionHandler(conn net.Conn) { connFrom := conn.RemoteAddr().String() println("Connection from: ", connFrom) @@ -53,11 +55,13 @@ DISCONNECT: println("Closed connection: ", connFrom) checkError(err, "Close: ") } + func sayHello(to net.Conn) { obuf := []byte{'L', 'e', 't', '\'', 's', ' ', 'G', 'O', '!', '\n'} wrote, err := to.Write(obuf) checkError(err, "Write: wrote "+string(wrote)+" bytes.") } + func handleMsg(length int, err error, msg []byte) { if length > 0 { print("<", length, ":") @@ -70,6 +74,7 @@ func handleMsg(length int, err error, msg []byte) { print(">") } } + func checkError(error error, info string) { if error != nil { panic("ERROR: " + info + " " + error.Error()) // terminate program From b5f526993fb34ecd2764c7fa38a551f49b9aef81 Mon Sep 17 00:00:00 2001 From: glight2000 <173959153@qq.com> Date: Tue, 8 Dec 2015 20:18:20 +0800 Subject: [PATCH 20/20] Update 15.1.md --- eBook/15.1.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/eBook/15.1.md b/eBook/15.1.md index a9f49e3..48f1764 100644 --- a/eBook/15.1.md +++ b/eBook/15.1.md @@ -225,6 +225,7 @@ func main() { go connectionHandler(conn) } } + func initServer(hostAndPort string) *net.TCPListener { serverAddr, err := net.ResolveTCPAddr("tcp", hostAndPort) checkError(err, "Resolving address:port failed: '"+hostAndPort+"'") @@ -233,6 +234,7 @@ func initServer(hostAndPort string) *net.TCPListener { println("Listening to: ", listener.Addr().String()) return listener } + func connectionHandler(conn net.Conn) { connFrom := conn.RemoteAddr().String() println("Connection from: ", connFrom) @@ -255,11 +257,13 @@ DISCONNECT: println("Closed connection: ", connFrom) checkError(err, "Close: ") } + func sayHello(to net.Conn) { obuf := []byte{'L', 'e', 't', '\'', 's', ' ', 'G', 'O', '!', '\n'} wrote, err := to.Write(obuf) checkError(err, "Write: wrote "+string(wrote)+" bytes.") } + func handleMsg(length int, err error, msg []byte) { if length > 0 { print("<", length, ":") @@ -272,6 +276,7 @@ func handleMsg(length int, err error, msg []byte) { print(">") } } + func checkError(error error, info string) { if error != nil { panic("ERROR: " + info + " " + error.Error()) // terminate program