简洁的并发TCP服务器

    为了能够和网络中的 key-value 存储交互,我们创建自定义的 TCP 协议。您将需要为 key-value 存储的每一个函数定义关键字。为简单起见,每个关键字都跟着相关数据。大多数命令的结果将是成功或失败消息。

    这个主题使用的工具命名为 kvTCP.go,它被分为六个部分。

    kvTCP.go 的第一部分如下:

    1. func handleConnection(c net.Conn) {
    2. c.Write([]byte(welcome))
    3. for {
    4. netData, err := bufio.NewReader(c).ReadString('\n')
    5. if err != nil {
    6. fmt.Println(err)
    7. return
    8. }
    9. command := strings.TrimSpace(string(netData))
    10. tokens := strings.Fields(command)
    11. switch len(tokens) {
    12. case 0:
    13. continue
    14. case 1:
    15. tokens = append(tokens, "")
    16. tokens = append(tokens, "")
    17. tokens = append(tokens, "")
    18. tokens = append(tokens, "")
    19. case 2:
    20. tokens = append(tokens, "")
    21. tokens = append(tokens, "")
    22. tokens = append(tokens, "")
    23. case 3:
    24. tokens = append(tokens, "")
    25. tokens = append(tokens, "")
    26. case 4:
    27. tokens = append(tokens, "")
    28. }
    29. switch tokens[0] {
    30. case "STOP":
    31. err = save()
    32. if err != nil {
    33. fmt.Println(err)
    34. }
    35. c.Close()
    36. return
    37. case "PRINT":
    38. PRINT(c)
    39. case "DELETE":
    40. if !DELETE(tokens[1]) {
    41. netData := "Delete operation failed!\n"
    42. c.Write([]byte(netData))
    43. }else{
    44. netData := "Delete operation successful!\n"
    45. c.Write([]byte(netData))
    46. }
    47. case "ADD":
    48. n := myElement{tokens[2], tokens[3], tokens[4]}
    49. if !ADD(tokens[1], n) {
    50. netData := "Add operation failed!\n"
    51. c.Write([]byte(netData))
    52. } else {
    53. netData := "Add operation successful!\n"
    54. }
    55. if err != nil {
    56. fmt.Println(err)
    57. }
    58. case "LOOKUP":
    59. n := LOOKUP(tokens[1])
    60. if n != nil {
    61. netData := fmt.Sprintf("%v\n", *n)
    62. c.Write([]byte(netData))
    63. } else {
    64. netData := "Did not find key!\n"
    65. c.Write([]byte(netData))
    66. }
    67. case "CHANGE":
    68. n := myElement{tokens[2], tokens[3], tokens[4]}
    69. if !CHANGE(tokens[1], n) {
    70. netData := "Update operation failed!\n"
    71. c.Write([]byte(netData))
    72. } else {
    73. netData := "Update operation successful!\n"
    74. c.Write([]byte(netData))
    75. }
    76. err = save()
    77. if err != nil {
    78. fmt.Println(err)
    79. }
    80. default:
    81. netData := "Unknown command - please try again!\n"
    82. c.Write([]byte(netData))
    83. }
    84. }
    85. }

    handleConnection() 函数和每个 TCP 客户端交互并解析客户端的输入。

    kvTCP.go 的第三部分包含如下代码:

    kvTCP.go 的第四段如下:

    1. func ADD(k string, n myElement) bool {
    2. if k == "" {
    3. return false
    4. }
    5. if LOOKUP(k) == nil {
    6. DATA[k] = n
    7. return true
    8. }
    9. return false
    10. }
    11. func DELETE(k string) bool {
    12. if LOOKUP(k) != nil {
    13. delete(DATA, k)
    14. return true
    15. }
    16. return false
    17. }
    18. func LOOKUP(k string) *myElement {
    19. if ok {
    20. n := DATA[k]
    21. return &n
    22. return nil
    23. }
    24. }
    25. func CHANGE(k string, n myElement) bool {
    26. DATA[k] = n
    27. return true
    28. }

    上面的这些函数实现与 keyValue.go 一样。它们没有直接和 TCP 客户端交互。

    PRINT() 函数直接发送数据给 TCP 客户端,一次一行。

    这个程序的剩余代码如下:

    1. func main() {
    2. arguments := os.Args
    3. if len(arguments) == 1 {
    4. fmt.Println("Please provide a port number!")
    5. return
    6. }
    7. PORT := ":" + arguments[1]
    8. l, err := net.Listen("tcp", PORT)
    9. if err != nil {
    10. fmt.Println(err)
    11. return
    12. }
    13. defer l.Close()
    14. err = load()
    15. if err != nil {
    16. fmt.Println(err)
    17. }
    18. for {
    19. c, err := l.Accept()
    20. if err != nil {
    21. fmt.Println(err)
    22. os.Exit(100)
    23. }
    24. go handleConnection(c)
    25. }
    26. }

    执行 kvTCP.go 将产生如下输出:

    为了这节的目的,netcat(l) 工具用来作为 kvTCP.go 的客户端:

    1. $ nc localhost 9000
    2. Welcome to the Key-value store!
    3. PRINT
    4. LOOKUP 1
    5. Did not find key!
    6. ADD 1 2 3 4
    7. Add operation successful!
    8. LOOKUP 1
    9. {2 3 4}
    10. ADD 4 -1 -2 -3
    11. Add operation successful!
    12. PRINT
    13. key: 1 value: {2 3 4}
    14. STOP