- 前言
- Go 与操作系统
- Go 内部机制
- Go 基本数据类型
- 4 组合类型的使用
- 5 数据结构
- 6 Go package 中不为人知的知识
- 7 反射和接口
- 8 Go UNIX 系统编程
- 08.1 关于 UNIX 进程
- 08.2 flag 包
- 8.2 flag 包
- 08.3 io.Reader 和 io.Writer 接口
- 08.4 bufio 包
- 08.5 读取文本文件
- 08.6 从文件中读取所需的数据量
- 08.7 为什么我们使用二进制格式
- 08.8 读取 CSV 文件
- 08.9 写入文件
- 08.10 从磁盘加载和保存数据
- 08.11 再看strings包
- 08.12 关于bytes包
- 08.13 文件权限
- 08.14 处理 Unix 信号
- 08.15 Unix 管道编程
- 08.16 遍历目录树
- 08.17 使用 ePBF
- 08.18 关于 syscall.PtraceRegs
- 08.19 跟踪系统调用
- 08.20 User ID 和 group ID
- 08.21 其他资源
- 08.22 练习
- 08.23 总结
- 9 并发 Goroutines、Channel 和 Pipeline
- 10 Go 并发-进阶讨论
- 11 代码测试、优化及分析
- 12 Go 网络编程基础
- 13 网络编程 - 构建服务器与客户端
13.6 并发 TCP 服务器
在这节,您将学习使用 goroutines 开放一个并发 TCP 服务器。TCP 服务器将给每个接入的连接开启一个新的 goroutines 来处理请求。一个并发 TCP 服务器可以接收更多请求,能同时为多个客户端提供服务。
这个 TCP 并发服务器的工作是接收一个正整数并从斐波纳切序列返回一个自然数。如果输入发送错误,则返回值为-1。由于斐波纳切序列数的计算慢,我们将使用曾首次在第11章出现的一个算法,代码测试,优化和分析都包含在 benchmarkMe.go
文件中。另外,这次用到的算法将会详细的讲解。
我们把程序命名为 fiboTCP.go
,并把代码分成五部分。由于把 web 服务的端口号定义为命令行参数被认为是良好的实现方式,这次 fiboTCP.go
将完全按此来做。
fiboTCP.go
的第一部分如下:
package main
import(
"bufio"
"fmt"
"net"
"os"
"strconv"
"strings"
"time"
)
fiboTCP.go
的第二部分如下:
func f(n int) int {
fn := make(map[int]int)
for i := 0; i <= n; i++ {
var f int
if i <= 2 {
f = 1
} else {
f = fn[i-1] + fn[i-2]
}
fn[i] = f
}
return fn[n]
}
上面这段代码,您能看到 f()
函数实现了斐波纳切序列自然数的生成。一开始看这个算法很难理解,但它非常有效且运行速度也很快。首先,f()
函数使用了一个被命名为 fn
的字典,这在计算斐波纳切序列数时很不寻常。第二,f()
函数使用了一个 for
循环,这也相当不寻常。最后,f()
函数没有使用递归,这也是它执行快的主要原因。
在 f()
函数中用到的算法思想使用了动态规划技巧,每当一个斐波纳切数被计算后,就把它放入 fn
字典中,这样它就不会再被计算了。这个简单的想法节省了很多时间,特别是需要计算较大斐波纳切数时,因为您不必对相同的斐波纳切数计算多次。
fiboTCP.go
的第三部分如下:
func handleConnection(c net.Conn) {
for {
netData, err := bufio.NewReader(c).ReadString('\n')
if err != nil {
fmt.Println(err)
os.Exit(100)
}
temp := strings.TrimSpace(string(netData))
if temp == "STOP" {
break
}
fibo := "-1\n"
n, err := strconv.Atoi(temp)
if err == nil {
fibo = strconv.Itoa(f(n)) + "\n"
}
c.Write([]byte(string(fibo)))
}
time.Sleep(5 * time.Second)
c.Close()
}
handleConnection()
函数处理并发 TCP 服务器的每个客户端。
fiboTCP.go
的第四部分如下:
func main() {
arguments := os.Args
if len(arguments) == 1 {
fmt.Println("Please provide a port number!")
return
}
PORT := ":" + arguments[1]
l, err := net.Listen("tcp4", PORT)
if err != nil {
fmt.Println(err)
return
}
defer l.Close()
fiboTCP.go
的剩余代码如下:
for {
c, err := l.Accept()
if err != nil {
fmt.Println(err)
return
}
go handleConnection(c)
}
}
go handleConnection(c)
声明实现了程序的并发性,每次连接一个新 TCP 客户端时它就开启一个新的 goroutine。goroutine 被并发执行,使服务器有机会服务更多的客户端。
执行 fiboTCP.go
并使用 netcat(l)
和 TCPclient.go
在俩个不同的终端窗口和它交互,输出如下:
$ go run fiboTCP.go 9000
n: 10
fibo: 55
n: 0
fibo: 1
n: -1
fibo: 0
n: 100
fibo: 3736710778780434371
n: 12
fibo: 144
n: 12
fibo: 144
在 TCPclient.go
这边的输出如下:
$ go run TCPclient.go localhost:9000
>> 12
->: 144
>> a
->: -1
>> STOP
->: TCP client exiting...
在 netcat(l)
这边的输出如下:
$ nc localhost 9000
10
55
0
1
-1
0
100
3736710778780434371
ads
-1
STOP
当您发送 STOP
字符串给服务进程时。为指定 TCP 客户端服务的 goroutine 将终止,这将引起连接关闭。
最后,值得关注的是俩个客户端被同时提供服务,这可以通过下面的命令输出来验证:
$ netstat -anp TCP | grep 9000
tcp4 0 0 127.0.0.1 9000 127.0.0.1.57309 ESTABLISHED
tcp4 0 0 127.0.0.1.57309 127.0.0.1.9000 ESTABLISHED
tcp4 0 0 127.0.0.1 9000 127.0.0.1.57305 ESTABLISHED
tcp4 0 0 127.0.0.1 57305 127.0.0.1.9000 ESTABLISHED
tcp4 0 0 *.9000 *.* LISTEN
上面命令的最后一行输出告诉我们有一个进程在监听 9000
端口,这意味着您仍能连接 9000
端口。输出的头俩行显示有个客户端使用 57309
端口与服务进程通信。第三和第四行证明有另一个客户端连接到监听 9000
端口的服务上。这个客户端使用的 TCP 端口是 57035
。
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论