Просмотр исходного кода

Merge branch 'master' of github.com:songleo/the-way-to-go_ZH_CN

leo 10 лет назад
Родитель
Сommit
0b31043b9b
19 измененных файлов с 43 добавлено и 45 удалено
  1. 1 1
      eBook/02.3.md
  2. 5 5
      eBook/04.4.md
  3. 0 2
      eBook/05.1.md
  4. 1 1
      eBook/08.1.md
  5. 0 0
      eBook/09.11.md
  6. 1 1
      eBook/09.2.md
  7. 6 6
      eBook/10.1.md
  8. 6 6
      eBook/10.2.md
  9. 1 1
      eBook/10.3.md
  10. 2 2
      eBook/10.4.md
  11. 4 4
      eBook/10.5.md
  12. 5 5
      eBook/10.6.md
  13. 1 1
      eBook/10.7.md
  14. 1 1
      eBook/11.10.md
  15. 2 2
      eBook/11.9.md
  16. 2 2
      eBook/12.2.md
  17. 3 3
      eBook/14.1.md
  18. 1 1
      eBook/14.4.md
  19. 1 1
      eBook/15.1.md

+ 1 - 1
eBook/02.3.md

@@ -35,7 +35,7 @@
 	从 [官方页面](https://golang.org/dl/) 或 [国内镜像](http://www.golangtc.com/download) 下载 Go 的源码包到你的计算机上,然后将解压后的目录 `go` 通过命令移动到 `$GOROOT` 所指向的位置。
 
 		wget https://storage.googleapis.com/golang/go<VERSION>.src.tar.gz
-		tar zxv go<VERSION>.src.tar.gz
+		tar -zxvf go<VERSION>.src.tar.gz
 		sudo mv go $GOROOT
 
 4. 构建 Go

+ 5 - 5
eBook/04.4.md

@@ -8,7 +8,7 @@
 
 首先,它是为了避免像 C 语言中那样含糊不清的声明形式,例如:`int* a, b;`。在这个例子中,只有 a 是指针而 b 不是。如果你想要这两个变量都是指针,则需要将它们分开书写(你可以在 [Go 语言的声明语法](http://blog.golang.org/2010/07/gos-declaration-syntax.html) 页面找到有关于这个话题的更多讨论)。
 
-而在 Go 中,则可以轻松地将它们都声明为指针类型:
+而在 Go 中,则可以轻松地将它们都声明为指针类型:
 
 ```go
 var a, b *int
@@ -21,7 +21,7 @@ var a, b *int
 ```go
 var a int
 var b bool
-var str string	
+var str string
 ```
 
 你也可以改写成这种形式:
@@ -165,7 +165,7 @@ func main() {
 
 ![](../images/4.4.2_fig4.3.jpg?raw=true)
 
-这个内存地址称之为指针(你可以从上图中很清晰地看到,第 4.9 节将会详细说明),这个指针实际上也被存在另外的某一个字中。
+这个内存地址称之为指针(你可以从上图中很清晰地看到,第 4.9 节将会详细说明),这个指针实际上也被存在另外的某一个字中。
 
 同一个引用类型的指针指向的多个字可以是在连续的内存地址中(内存布局是连续的),这也是计算效率最高的一种存储形式;也可以将这些字分散存放在内存中,每个字都指示了下一个字所在的内存地址。
 
@@ -192,7 +192,7 @@ func Printf(format string, list of variables to be printed)
 
 ```go
 fmt.Print("Hello:", 23)
-``` 
+```
 
 将输出:`Hello: 23`。
 
@@ -255,7 +255,7 @@ a, b, c := 5, 7, "abc"
 
 (在 Go 语言中,这样省去了使用交换函数的必要)
 
-空白标识符 `_` 也被用于抛弃值,如值 `5` 在:`_, b = 5, 7` 中被抛弃。 
+空白标识符 `_` 也被用于抛弃值,如值 `5` 在:`_, b = 5, 7` 中被抛弃。
 
 `_` 实际上是一个只写变量,你不能得到它的值。这样做是因为 Go 语言中你必须使用所有被声明的变量,但有时你并不需要使用从一个函数得到的所有返回值。
 

+ 0 - 2
eBook/05.1.md

@@ -193,8 +193,6 @@ func main() {
 下面的代码片段展示了如何通过在初始化语句中获取函数 `process()` 的返回值,并在条件语句中作为判定条件来决定是否执行 if 结构中的代码:
 
 ```go
-if value := process(data); value > max {
-	...
 if value := process(data); value > max {
 	...
 }

+ 1 - 1
eBook/08.1.md

@@ -9,7 +9,7 @@ var map1 map[keytype]valuetype
 var map1 map[string]int
 ```
 
-(`[keytype]`` 和 `valuetype` 之间允许有空格,但是 gofmt 移除了空格)
+(`[keytype]` 和 `valuetype` 之间允许有空格,但是 gofmt 移除了空格)
 
 在声明的时候不需要知道 map 的长度,map 是可以动态增长的。
 

Разница между файлами не показана из-за своего большого размера
+ 0 - 0
eBook/09.11.md


+ 1 - 1
eBook/09.2.md

@@ -13,7 +13,7 @@ ok, _ := regexp.Match(pat, []byte(searchIn))
 变量 ok 将返回 true 或者 false,我们也可以使用 `MatchString`:
 
 ```go
-ok, _ := regexp.MathString(pat, searchIn)
+ok, _ := regexp.MatchString(pat, searchIn)
 ```
 
 更多方法中,必须先将正则通过 `Compile` 方法返回一个 Regexp 对象。然后我们将掌握一些匹配,查找,替换相关的功能。

+ 6 - 6
eBook/10.1.md

@@ -35,7 +35,7 @@ t = new(T)
 
 写这条语句的惯用方法是:`t := new(T)`,变量 `t` 是一个指向 `T`的指针,此时结构体字段的值是它们所属类型的零值。
 
-声明 `var t T` 也会给 `t` 分配内存,并零值化内存,但是这个时候 `t` 是类型T。在这两种方式中,`t` 通常被称做类型 T 的一个实例(instance)或对象(Object)。
+声明 `var t T` 也会给 `t` 分配内存,并零值化内存,但是这个时候 `t` 是类型T。在这两种方式中,`t` 通常被称做类型 T 的一个实例(instance)或对象(object)。
 
 示例 10.1 [structs_fields.go](examples/chapter_10/structs_fields.go) 给出了一个非常简单的例子:
 
@@ -85,7 +85,7 @@ v.i
 p.i
 ```
 
-初始化一个结构体实例(一个结构体字面量:struct-literal)的更简短和惯用的方式如下:
+初始化一个结构体实例(一个结构体字面量:struct-literal)的更简短和惯用的方式如下:
 
 ```go
     ms := &struct1{10, 15.5, "Chris"}
@@ -95,8 +95,8 @@ p.i
 或者:
 
 ```go
-    var mt struct1
-    ms := struct1{10, 15.5, "Chris"}
+    var ms struct1
+    ms = struct1{10, 15.5, "Chris"}
 ```
 
 混合字面量语法(composite literal syntax)`&struct1{a, b, c}` 是一种简写,底层仍然会调用 `new ()`,这里值的顺序必须按照字段顺序来写。在下面的例子中能看到可以通过在值的前面放上字段名来初始化字段的方式。表达式 `new(Type)` 和 `&Type{}` 是等价的。
@@ -120,7 +120,7 @@ intr := Interval{end:5}           (C)
 
 在(A)中,值必须以字段在结构体定义时的顺序给出,**&** 不是必须的。(B)显示了另一种方式,字段名加一个冒号放在值的前面,这种情况下值的顺序不必一致,并且某些字段还可以被忽略掉,就像(C)中那样。
 
-结构体类型和字段的命名遵循可见性规则(第 [4.2](04.2.md) 节),一个导出的结构体类型中有些字段是导出的,另一些不是,这是可能的。
+结构体类型和字段的命名遵循可见性规则(第 [4.2](04.2.md) 节)一个导出的结构体类型中有些字段是导出的,另一些不是,这是可能的。
 
 下图说明了结构体类型实例和一个指向它的指针的内存布局:
 
@@ -138,7 +138,7 @@ type Point struct { x, y int }
 
 类型 strcut1 在定义它的包 pack1 中必须是唯一的,它的完全类型名是:`pack1.struct1`。
 
-下面的例子 [Listing 10.2—person.go](examples/person.go) 显示了一个结构体 Person,一个方法,方法有一个类型为 `*Person` 的参数(因此对象本身是可以被改变的),以及三种调用这个方法的不同方式:
+下面的例子 [Listing 10.2—person.go](examples/chapter_10/person.go) 显示了一个结构体 Person,一个方法,方法有一个类型为 `*Person` 的参数(因此对象本身是可以被改变的),以及三种调用这个方法的不同方式:
 
 ```go
 package main

+ 6 - 6
eBook/10.2.md

@@ -2,7 +2,7 @@
 
 ## 10.2.1 结构体工厂
 
-Go 语言不支持面向对象编程语言中那样的构造子方法,但是可以很容易的在 Go 中实现 “构造子工厂方法。为了方便通常会为类型定义一个工厂,按惯例,工厂的名字以 new 或 New 开头。假设定义了如下的 File 结构体类型:
+Go 语言不支持面向对象编程语言中那样的构造子方法,但是可以很容易的在 Go 中实现 “构造子工厂方法。为了方便通常会为类型定义一个工厂,按惯例,工厂的名字以 new 或 New 开头。假设定义了如下的 File 结构体类型:
 
 ```go
 type File struct {
@@ -19,7 +19,7 @@ func NewFile(fd int, name string) *File {
         return nil
     }
 
-    return &File(fd, name)
+    return &File{fd, name}
 }
 ```
 
@@ -29,7 +29,7 @@ func NewFile(fd int, name string) *File {
 f := NewFile(10, "./test.txt")
 ```
 
-在 Go 语言中常常像上面这样在工厂方法里使用初始化来简便的实现构造
+在 Go 语言中常常像上面这样在工厂方法里使用初始化来简便的实现构造函数
 
 如果 `File` 是一个结构体类型,那么表达式 `new(File)` 和 `&File{}` 是等价的。
 
@@ -41,7 +41,7 @@ f := NewFile(10, "./test.txt")
 
 **如何强制使用工厂方法**
 
-通过应用可见性规则(参考第 4.2.1、9.5 节)就可以禁止使用 new 函数,强制用户使用工厂方法,从而使类型变成私有的,就像在面向对象语言中那样。
+通过应用可见性规则参考[4.2.1节](04.2.md)、[9.5 节](09.5.md)就可以禁止使用 new 函数,强制用户使用工厂方法,从而使类型变成私有的,就像在面向对象语言中那样。
 
 ```go
 type matrix struct {
@@ -72,7 +72,7 @@ new 和 make 这两个内置函数已经在第 [7.2.4](07.2.md) 节通过切片
 
     slices  /  maps / channels(见第 14 章)
 
-下面的例子说明了在映射上使用 new 和 make 的区别以及可能发生的错误:
+下面的例子说明了在映射上使用 new 和 make 的区别以及可能发生的错误:
 
 示例 10.4 new_make.go(不能编译)
 
@@ -113,5 +113,5 @@ func main() {
 ## 链接
 
 - [目录](directory.md)
-- 上一节:[结构体定义](10.1.md)
+- 上一节:[结构体定义](10.1.md)
 - 下一节:[使用自定义包中的结构体](10.3.md)

+ 1 - 1
eBook/10.3.md

@@ -2,7 +2,7 @@
 
 下面的例子中,main.go 使用了一个结构体,它来自 struct_pack 下的包 structPack。
 
-示例 10.5 structPack.go:
+示例 10.5 [structPack.go](examples/chapter_10/struct_pack/structPack.go)
 
 ```go
 package structPack

+ 2 - 2
eBook/10.4.md

@@ -1,8 +1,8 @@
 # 10.4 带标签的结构体
 
-结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag):它是一个附属于字段的字符串,可以是文档或其他的重要标记。标签的内容不可以在一般的编程中使用,只有包 `reflect` 能获取它。我们将在下一章(第 11.10 节)中深入的探讨 `reflect`包,它可以在运行时自省类型、属性和方法,比如:在一个变量上调用 `reflect.TypeOf()` 可以获取变量的正确类型,如果变量是一个结构体类型,就可以通过 Field 来索引结构体的字段,然后就可以使用 Tag 属性。
+结构体中的字段除了有名字和类型外,还可以有一个可选的标签(tag):它是一个附属于字段的字符串,可以是文档或其他的重要标记。标签的内容不可以在一般的编程中使用,只有包 `reflect` 能获取它。我们将在下一章(第 [11.10 节](11.10.md))中深入的探讨 `reflect`包,它可以在运行时自省类型、属性和方法,比如:在一个变量上调用 `reflect.TypeOf()` 可以获取变量的正确类型,如果变量是一个结构体类型,就可以通过 Field 来索引结构体的字段,然后就可以使用 Tag 属性。
 
-示例 10.7 struct_tag.go:
+示例 10.7 [struct_tag.go](examples/chapter_10/struct_tag.go)
 
 ```go
 package main

+ 4 - 4
eBook/10.5.md

@@ -8,7 +8,7 @@
 
 考虑如下的程序:
 
-示例 10.8 structs_anonymous_fields.go:
+示例 10.8 [structs_anonymous_fields.go](examples/chapter_10/structs_anonymous_fields.go)
 
 ```go
 package main
@@ -43,7 +43,7 @@ func main() {
 
 	// 使用结构体字面量
 	outer2 := outerS{6, 7.5, 60, innerS{5, 10}}
-	fmt.Printf("outer2 is:", outer2)
+	fmt.Println("outer2 is:", outer2)
 }
 ```
 
@@ -64,7 +64,7 @@ func main() {
 
 另外一个例子:
 
-示例 10.9 embedd_struct.go:
+示例 10.9 [embedd_struct.go](examples/chapter_10/embedd_struct.go)
 
 ```go
 package main
@@ -100,7 +100,7 @@ func main() {
 
 当两个字段拥有相同的名字(可能是继承来的名字)时该怎么办呢?
 
-1. 外层名字会覆盖内层名字(但是两者的内存空间都保留),这提供了一种重载字段或方法的方式
+1. 外层名字会覆盖内层名字(但是两者的内存空间都保留),这提供了一种重载字段或方法的方式
 2. 如果相同的名字在同一级别出现了两次,如果这个名字被程序使用了,将会引发一个错误(不使用没关系)。没有办法来解决这种问题引起的二义性,必须由程序员自己修正。
 
 例子:

+ 5 - 5
eBook/10.6.md

@@ -139,7 +139,7 @@ func (t time.Time) first3Chars() string {
 }
 ```
 
-类型在其他的,或是非本地的包里定义,在它上面定义方法都会得到和上面同样的错误。
+类型在其他的,或是非本地的包里定义,在它上面定义方法都会得到和上面同样的错误。
 
 但是有一个间接的方式:可以先定义该类型(比如:int 或 float)的别名类型,然后再为别名类型定义方法。或者像下面这样将它作为匿名类型嵌入在一个新的结构体中。当然方法只在这个别名类型上有效。
 
@@ -198,7 +198,7 @@ First 3 chars: Mon
 
 如果想要方法改变接收者的数据,就在接收者的指针类型上定义该方法。否则,就在普通的值类型上定义方法。
 
-下面的例子 `pointer_value.go` 作了说明:`change()`接受一个指向 B 的指针,并改变它内部的成员;`write()` 接受通过拷贝接受 B 的值并只输出B的内容。注意 Go 为我们做了探测工作,我们自己并没有指出是否在指针上调用方法,Go 替我们做了这些事情。b1 是值而 b2 是指针,方法都支持运行了。
+下面的例子 `pointer_value.go` 作了说明:`change()`接受一个指向 B 的指针,并改变它内部的成员;`write()` 接受通过拷贝接受 B 的值并只输出B的内容。注意 Go 为我们做了探测工作,我们自己并没有指出是否在指针上调用方法,Go 替我们做了这些事情。b1 是值而 b2 是指针,方法都支持运行了。
 
 示例 10.13 pointer_value.go:
 
@@ -238,9 +238,9 @@ func main() {
 我们知道方法将指针作为接收者不是必须的,如下面的例子,我们只是需要 `Point3` 的值来做计算:
 
 ```go
-type Point3 struct { x, y, z float }
+type Point3 struct { x, y, z float64 }
 // A method on Point3
-func (p Point3) Abs float {
+func (p Point3) Abs() float64 {
     return math.Sqrt(p.x*p.x + p.y*p.y + p.z*p.z)
 }
 ```
@@ -292,7 +292,7 @@ func main() {
 
 ## 10.6.4 方法和未导出字段
 
-考虑 `person2.go` 中的 `person` 包:类型 `Person` 被明确的导出了,但是它的字段没有被导出。例如在 `use_person2.go` 中 `p.firsetname` 就是错误的。该如何在另一个程序中修改或者只是读取一个 `Person` 的名字呢?
+考虑 `person2.go` 中的 `person` 包:类型 `Person` 被明确的导出了,但是它的字段没有被导出。例如在 `use_person2.go` 中 `p.firstName` 就是错误的。该如何在另一个程序中修改或者只是读取一个 `Person` 的名字呢?
 
 这可以通过面向对象语言一个众所周知的技术来完成:提供 getter 和 setter 方法。对于 setter 方法使用 Set 前缀,对于 getter 方法只适用成员名。
 

+ 1 - 1
eBook/10.7.md

@@ -1,6 +1,6 @@
 # 10.7 类型的 String() 方法和格式化描述符
 
-当定义一个有很多方法的类型时,十之八九你会使用 `String()` 方法来定制类型的字符串形式的输出,换句话说:一种可阅读性和打印性的输出。如果类型定义了 `String()` 方法,它会被用在 `fmt.Printf()` 中生成默认的输出:等同于使用格式化描述符 `%v` 产生的输出。还有 `fmt.Print()` 和 `fmt.Println()` 也会自动使用 `String()` 方法。
+当定义一个有很多方法的类型时,十之八九你会使用 `String()` 方法来定制类型的字符串形式的输出,换句话说:一种可阅读性和打印性的输出。如果类型定义了 `String()` 方法,它会被用在 `fmt.Printf()` 中生成默认的输出:等同于使用格式化描述符 `%v` 产生的输出。还有 `fmt.Print()` 和 `fmt.Println()` 也会自动使用 `String()` 方法。
 
 我们使用第 10.4 节中程序的类型来进行测试:
 

+ 1 - 1
eBook/11.10.md

@@ -102,7 +102,7 @@ func main() {
 
 ```
 type: float64
-value: <float64 Value>
+value: 3.4
 type: float64
 kind: float64
 value: 3.4

+ 2 - 2
eBook/11.9.md

@@ -1,4 +1,4 @@
-# 11.9 空接口
+# 11.9 空接口
 
 ## 11.9.1 概念
 
@@ -164,7 +164,7 @@ var interfaceSlice []interface{} = dataSlice
 var dataSlice []myType = FuncReturnSlice()
 var interfaceSlice []interface{} = make([]interface{}, len(dataSlice))
 for ix, d := range dataSlice {
-    interfaceSlice[i] = d
+    interfaceSlice[ix] = d
 }
 ```
 

+ 2 - 2
eBook/12.2.md

@@ -246,7 +246,7 @@ func main () {
 除了文件句柄,我们还需要 `bufio` 的 `Writer`。我们以只写模式打开文件 `output.dat`,如果文件不存在则自动创建:
 
 ```go
-outputFile, outputError := os.OpenFile(“output.dat”, os.O_WRONLY|os.O_ CREATE, 0666)
+outputFile, outputError := os.OpenFile(“output.dat”, os.O_WRONLY|os.O_CREATE, 0666)
 ```
 
 可以看到,`OpenFile` 函数有三个参数:文件名、一个或多个标志(使用逻辑运算符“|”连接),使用的文件权限。
@@ -254,7 +254,7 @@ outputFile, outputError := os.OpenFile(“output.dat”, os.O_WRONLY|os.O_ CREAT
 我们通常会用到以下标志:
 
 - `os.O_RDONLY`:只读  
-- `os.WRONLY`:只写  
+- `os.O_WRONLY`:只写  
 - `os.O_CREATE`:创建:如果指定文件不存在,就创建该文件。  
 - `os.O_TRUNC`:截断:如果指定文件已存在,就将该文件的长度截为0。
 

+ 3 - 3
eBook/14.1.md

@@ -2,7 +2,7 @@
 
 ## 14.1.1 什么是协程
 
-一个应用程序是运行在机器上的一个进程;进程是一个运行在自己内存地址空间里的独立执行体。一个进程由一个或多个操作系统线程组成,这些线程其实是共享同一个内存地址空间的一起工作的执行体。几乎所有'正式'的程序都是多线程的,以便让用户或计算机不必等待,或者能够同时服务多个请求(如 Web 服务器),或增加性能和吞吐量(例如,通过对不同的数据集并行执行代码)。一个并发程序可以在一个处理器或者内核上使用多个线程来执行任务,但是只有同一个程序在某个时间点在多个些处理内核或处理器上同时执行的任务才是真正的并行。
+一个应用程序是运行在机器上的一个进程;进程是一个运行在自己内存地址空间里的独立执行体。一个进程由一个或多个操作系统线程组成,这些线程其实是共享同一个内存地址空间的一起工作的执行体。几乎所有'正式'的程序都是多线程的,以便让用户或计算机不必等待,或者能够同时服务多个请求(如 Web 服务器),或增加性能和吞吐量(例如,通过对不同的数据集并行执行代码)。一个并发程序可以在一个处理器或者内核上使用多个线程来执行任务,但是只有同一个程序在某个时间点同时运行在多核或者多处理器上才是真正的并行。
 
 并行是一种通过使用多处理器以提高速度的能力。所以并发程序可以是并行的,也可以不是。
 
@@ -12,7 +12,7 @@
 
 解决之道在于同步不同的线程,对数据加锁,这样同时就只有一个线程可以变更数据。在 Go 的标准库 `sync` 中有一些工具用来在低级别的代码中实现加锁;我们在第 [9.3](9.3.md) 节中讨论过这个问题。不过过去的软件开发经验告诉我们这会带来更高的复杂度,更容易使代码出错以及更低的性能,所以这个经典的方法明显不再适合现代多核/多处理器编程:`thread-per-connection` 模型不够有效。
 
-Go 更倾向于其他的方式,在诸多比较合适的范式中,有个被称作 `Communicating Sequential Processes(顺序通信处理)`(CSP, C. Hoare 发明的)还有一个叫做 `message passing-model(消息传递)`(已经运用在了其他语言中,比如 Eralng)。
+Go 更倾向于其他的方式,在诸多比较合适的范式中,有个被称作 `Communicating Sequential Processes(顺序通信处理)`(CSP, C. Hoare 发明的)还有一个叫做 `message passing-model(消息传递)`(已经运用在了其他语言中,比如 Erlang)。
 
 在 Go 中,应用程序并发处理的部分被称作 `goroutines(协程)`,它可以进行更有效的并发运算。在协程和操作系统线程之间并无一对一的关系:协程是根据一个或多个线程的可用性,映射(多路复用,执行于)在他们之上的;协程调度器在 Go 运行时很好的完成了这个工作。
 
@@ -143,7 +143,7 @@ At the end of main() // after 17 s
 
 协程更有用的一个例子应该是在一个非常长的数组中查找一个元素。
 
-将数组分割为若干个不重复的切片,然后给每一个切片启动一个协程进行查找计算。这样许多并行的线程可以用来进行查找任务,整体的查找时间会缩短(除以协程的数量)。
+将数组分割为若干个不重复的切片,然后给每一个切片启动一个协程进行查找计算。这样许多并行的程可以用来进行查找任务,整体的查找时间会缩短(除以协程的数量)。
 
 ## 14.1.5 Go 协程(goroutines)和协程(coroutines)
 

+ 1 - 1
eBook/14.4.md

@@ -128,7 +128,7 @@ Received on channel 1: 94348
 - `channel1` 用来接收极坐标
 - `channel2` 用来接收笛卡尔坐标
 
-转换过程需要在协程中进行,从 channel1 中读取然后发送到 channel2。实际上做这种计算不提倡使用协程和通道,但是如果运算量很大很耗时,这种方案设计就非常合适了。
+转换过程需要在协程中进行,从 channel1 中读取然后发送到 channel2。实际上做这种计算不提倡使用协程和通道,但是如果运算量很大很耗时,这种方案设计就非常合适了。
 
 练习 14.11: [concurrent_pi.go](exercises/chapter_14/concurrent_pi.go) / [concurrent_pi2.go](exercises/chapter_14/concurrent_pi2.go)
 

+ 1 - 1
eBook/15.1.md

@@ -1,6 +1,6 @@
 # 15.1 tcp服务器
 
-这部分我们将使用TCP协议和在14章讲到的程范式编写一个简单的客户端-服务器应用,一个(web)服务器应用需要响应众多客户端的并发请求:go会为每一个客户端产生一个程用来处理请求。我们需要使用net包中网络通信的功能。它包含了用于TCP/IP以及UDP协议、域名解析等方法。
+这部分我们将使用TCP协议和在14章讲到的程范式编写一个简单的客户端-服务器应用,一个(web)服务器应用需要响应众多客户端的并发请求:go会为每一个客户端产生一个程用来处理请求。我们需要使用net包中网络通信的功能。它包含了用于TCP/IP以及UDP协议、域名解析等方法。
 
 服务器代码,单独的一个文件:
 

Некоторые файлы не были показаны из-за большого количества измененных файлов