Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
295 lines (254 sloc) 7.14 KB

简洁的并发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 客户端共享相同的数据!

You can’t perform that action at this time.