- 前言
- 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.1 简洁的并发TCP服务器
尽管上节的并发 TCP 服务器运作良好,但是它还不能为实际应用提供服务。因此,在这节您将学到怎样把第四章中的 keyValue.go
文件对于复杂类型的使用转化为一个功能齐全的并发 TCP 应用。
为了能够和网络中的 key-value 存储交互,我们创建自定义的 TCP 协议。您将需要为 key-value 存储的每一个函数定义关键字。为简单起见,每个关键字都跟着相关数据。大多数命令的结果将是成功或失败消息。
设计您自己的 TCP 或 UDP 协议不是一个简单的工作。这意味着设计一个新协议时,您必须特别细致小心。这里的关键是在您开始编写生产代码之前文档化所有内容。
这个主题使用的工具命名为 kvTCP.go
,它被分为六个部分。
kvTCP.go
的第一部分如下:
package main
import (
"bufio"
"encoding/gob"
"fmt"
"net"
"os"
"strings"
)
type myElement struct {
Name string
Surname string
Id string
}
const welcome = "Welcome to the Key-value store!\n"
var DATA = make(map[string]myElement)
var DATAFILE = "/tmp/dataFile.gob"
kvTCP.go
的第二部分如下:
func handleConnection(c net.Conn) {
c.Write([]byte(welcome))
for {
netData, err := bufio.NewReader(c).ReadString('\n')
if err != nil {
fmt.Println(err)
return
}
command := strings.TrimSpace(string(netData))
tokens := strings.Fields(command)
switch len(tokens) {
case 0:
continue
case 1:
tokens = append(tokens, "")
tokens = append(tokens, "")
tokens = append(tokens, "")
tokens = append(tokens, "")
case 2:
tokens = append(tokens, "")
tokens = append(tokens, "")
tokens = append(tokens, "")
case 3:
tokens = append(tokens, "")
tokens = append(tokens, "")
case 4:
tokens = append(tokens, "")
}
switch tokens[0] {
case "STOP":
err = save()
if err != nil {
fmt.Println(err)
}
c.Close()
return
case "PRINT":
PRINT(c)
case "DELETE":
if !DELETE(tokens[1]) {
netData := "Delete operation failed!\n"
c.Write([]byte(netData))
}else{
netData := "Delete operation successful!\n"
c.Write([]byte(netData))
}
case "ADD":
n := myElement{tokens[2], tokens[3], tokens[4]}
if !ADD(tokens[1], n) {
netData := "Add operation failed!\n"
c.Write([]byte(netData))
} else {
netData := "Add operation successful!\n"
c.Write([]byte(netData))
}
err = save()
if err != nil {
fmt.Println(err)
}
case "LOOKUP":
n := LOOKUP(tokens[1])
if n != nil {
netData := fmt.Sprintf("%v\n", *n)
c.Write([]byte(netData))
} else {
netData := "Did not find key!\n"
c.Write([]byte(netData))
}
case "CHANGE":
n := myElement{tokens[2], tokens[3], tokens[4]}
if !CHANGE(tokens[1], n) {
netData := "Update operation failed!\n"
c.Write([]byte(netData))
} else {
netData := "Update operation successful!\n"
c.Write([]byte(netData))
}
err = save()
if err != nil {
fmt.Println(err)
}
default:
netData := "Unknown command - please try again!\n"
c.Write([]byte(netData))
}
}
}
handleConnection()
函数和每个 TCP 客户端交互并解析客户端的输入。
kvTCP.go
的第三部分包含如下代码:
func save() error {
fmt.Println("Saving", DATAFILE)
err := os.Remove(DATAFILE)
if err != nil {
fmt.Println(err)
}
saveTo, err := os.Create(DATAFILE)
if err != nil {
fmt.Println("Cannot create", DATAFILE)
return err
}
defer saveTo.Close()
encoder := gob.NewEncoder(saveTo)
err = encoder.Encode(DATA)
if err != nil {
fmt.Println("Cannot save to", DATAFILE)
return err
}
return nil
}
func load() error {
fmt.Println("Loading", DATAFILE)
loadFrom, err := os.Open(DATAFILE)
defer loadFrom.Close()
if err != nil {
fmt.Println("Empty key/value store!")
return err
}
decoder := gob.NewDecoder(loadFrom)
decoder.Decode(&DATA)
return nil
}
kvTCP.go
的第四段如下:
func ADD(k string, n myElement) bool {
if k == "" {
return false
}
if LOOKUP(k) == nil {
DATA[k] = n
return true
}
return false
}
func DELETE(k string) bool {
if LOOKUP(k) != nil {
delete(DATA, k)
return true
}
return false
}
func LOOKUP(k string) *myElement {
_, ok := DATA[k]
if ok {
n := DATA[k]
return &n
} else {
return nil
}
}
func CHANGE(k string, n myElement) bool {
DATA[k] = n
return true
}
上面的这些函数实现与 keyValue.go
一样。它们没有直接和 TCP 客户端交互。
kvTCP.go
的第五部分包含代码如下:
func PRINT(c net.Conn) {
for k, d := range DATA {
netData := fmt.Sprintf("key: %s value: %v\n", k, d)
c.Write([]byte(netData))
}
}
PRINT()
函数直接发送数据给 TCP 客户端,一次一行。
这个程序的剩余代码如下:
func main() {
arguments := os.Args
if len(arguments) == 1 {
fmt.Println("Please provide a port number!")
return
}
PORT := ":" + arguments[1]
l, err := net.Listen("tcp", PORT)
if err != nil {
fmt.Println(err)
return
}
defer l.Close()
err = load()
if err != nil {
fmt.Println(err)
}
for {
c, err := l.Accept()
if err != nil {
fmt.Println(err)
os.Exit(100)
}
go handleConnection(c)
}
}
执行 kvTCP.go
将产生如下输出:
$ go run kvTCP.go 9000
Loading /tmp/dataFile.gob
Empty key/value store!
open /tmp/dataFile.gob: no such file or directory
Saving /tmp/dataFile.gob
remove /tmp/dataFile.gob: no such file or directory
Saving /tmp/dataFile.gob
Saving /tmp/dataFile.gob
为了这节的目的,netcat(l)
工具用来作为 kvTCP.go
的客户端:
$ nc localhost 9000
Welcome to the Key-value store!
PRINT
LOOKUP 1
Did not find key!
ADD 1 2 3 4
Add operation successful!
LOOKUP 1
{2 3 4}
ADD 4 -1 -2 -3
Add operation successful!
PRINT
key: 1 value: {2 3 4}
key: 4 value: {-1 -2 -3}
STOP
kvTCP.go
文件是一个使用 goroutines 的并发应用,它能够同时服务多个 TCP 客户端。然而,所有的 TCP 客户端共享相同的数据!
如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论