0%

通过Go+Map写k-v数据库(SET-GET简单实现TCP版)

通过实现一个简单的K–V存储数据库,学习Go语言(四)

为了能够和网络中的 key-value 存储交互,我们创建自定义的 TCP 协议。

您将需要为 key-value 存储的每一个函数定义关键字。

为简单起见,每个关键字都跟着相关数据。大多数命令的结果将是成功或失败消息

通过TCP连接,实现myRedis客户端与服务端

  • 服务端

目前存在小BUG,待优化——如客户端断开重连问题等

package main

import (
	"bufio"
	"encoding/gob"
	"fmt"
	"net"
	"os"
	"strings"
)

var DATA = map[string]string{}
var DATAFILE = "dataFile.gob"

//myRedis基础功能
//启动应用时,加载数据
func init() {
	err := load()
	if err != nil {
		fmt.Println(err)
	}
}
func load() error {
	loadFile, err := os.Open(DATAFILE)
	defer loadFile.Close()
	if err != nil {
		fmt.Println("Empty key/value store!")
		return err
	}
	//反序列化读取文件
	decoder := gob.NewDecoder(loadFile)
	//解码数据
	decoder.Decode(&DATA)
	fmt.Println("初始化:", DATA)
	return nil
}

func SET(key string, value string) string {
	if _, ok := DATA[key]; !ok {
		DATA[key] = value
		return ""
	}
	return "error: existence of this key!"

}
func GET(key string) string {
	if _, ok := DATA[key]; !ok {
		return "error: not found this key!"
	}
	return DATA[key]

}
func DELETE(key string) string {
	if _, ok := DATA[key]; !ok {
		return "error: not found this key!"
	}
	delete(DATA, key)
	return ""
}
func UPDATE(key string, value string) string {
	if _, ok := DATA[key]; !ok {
		return "error: not found this key!"
	}
	DATA[key] = value
	return ""

}

func SHOW() string {
	str := ""
	for k, v := range DATA {
		str += fmt.Sprintf("%s:%s ", k, v)
	}
	return str
}

//数据持久化

//保存数据在磁盘上
func save() error {
	//去除原数据文件,保存为新数据文件
	err := os.Remove(DATAFILE)
	if err != nil {
		fmt.Println(err)
	}

	saveFile, err := os.Create(DATAFILE)
	if err != nil {
		fmt.Println("Cannot create", DATAFILE)
		return err
	}
	defer saveFile.Close()
	//序列化存储数据到文件
	encoder := gob.NewEncoder(saveFile)
	//转码数据
	err = encoder.Encode(DATA)
	fmt.Println(DATA, saveFile, DATAFILE)
	if err != nil {
		fmt.Println("Cannot save to", DATAFILE)
		return err
	}
	return nil
}

func myRedis(text string) string {
	cmd := []string{}
	cmd = strings.Split(text, " ")
	switch cmd[0] {
	case "SET":
		s := SET(cmd[1], cmd[2])
		if s == "" {
			break
		}
		return s
	case "GET":
		s := GET(cmd[1])
		if s == "" {
			break
		}
		return s
	case "SHOW":
		s := SHOW()
		if s == "" {
			break
		}
		return s
	case "DELETE":
		s := DELETE(cmd[1])
		if s == "" {
			break
		}
		return s
	case "UPDATE":
		s := UPDATE(cmd[1], cmd[2])
		if s == "" {
			break
		}
		return s
	case "STOP":
		err := save()
		if err != nil {
			fmt.Println(err)
		}
	default:
		return "Unknown command - please try again!"
	}
	return "sucess"

}
func main() {
	//net.Listen() 函数用于监听连接,如果第二个参数不包含 IP 地址,只有端口号的话,net.Listen()
	//将监听本地系统上所有可用的 IP 地址。
	listen, err := net.Listen("tcp", "localhost:5000")
    fmt.Println("myRedis localhost:5000 启动...")
	if err != nil {
		fmt.Println(err)
		return
	}
	defer listen.Close()
	conn, err := listen.Accept()
	if err != nil {
		fmt.Println(err)
		return
	}
	fmt.Println("=客户端连接成功啦!")
	for {
		//获取客户端发来的消息
		data, err := bufio.NewReader(conn).ReadString('\n')
		if err != nil {
			fmt.Println(err)
			return
		}
		sendMsg := myRedis(strings.TrimSpace(data))
		fmt.Println("接受客户端消息为:", strings.TrimSpace(data))

		//发送给客户端
		if sendMsg != "" {
			conn.Write([]byte(sendMsg + "\n"))
		}
	}
}
  • 客户端
package main

import (
	"bufio"
	"fmt"
	"net"
	"os"
)

func main() {
	//连接服务端
	conn, err := net.Dial("tcp", "localhost:5000")
	if err != nil {
		fmt.Println(err)
		return
	}

	for {
		//通过io输入消息给服务端
		reader := bufio.NewReader(os.Stdin)
		fmt.Print(">> ")
		//获取每一行输入
		text, _ := reader.ReadString('\n')
		//标准格式,通过conn数据发送
		//fmt.Fprintf(conn, text)
		conn.Write([]byte(text))
		//读取接受消息
		acceptMsg, _ := bufio.NewReader(conn).ReadString('\n')
		fmt.Println(acceptMsg)
	}
}

测试结果

  • 客户端输入
>> SHOW
nil
   
>> SET test 1
sucess
      
>> SET test1 2
sucess
      
>> SET test2 3 
sucess

>> SHOW
test1:2 test2:3 test:1 
//保存
>> STOP
sucess

//手动断开连接,重启
>> SHOW
test:1 test1:2 test2:3 

>>
  • 服务端输出
myRedis localhost:5000 启动...
客户端连接成功啦!
接受客户端消息为: SHOW
接受客户端消息为: SET test 1
接受客户端消息为: SET test1 2
接受客户端消息为: SET test2 3
接受客户端消息为: SHOW
//保存
map[test:1 test1:2 test2:3] &{0xc00010f180} dataFile.gob
接受客户端消息为: STOP

//手动断开连接,重启
myRedis localhost:5000 启动...
客户端连接成功啦!
接受客户端消息为: SHOW
-------------本文结束感谢您的阅读-------------
打赏一瓶矿泉水