TCP redis like
This commit is contained in:
parent
984616b527
commit
47bd07057a
1
.gitignore
vendored
1
.gitignore
vendored
@ -47,3 +47,4 @@ converter-main/converter-main
|
|||||||
tcpclient/tcpclient
|
tcpclient/tcpclient
|
||||||
tcpserver/tcpserver
|
tcpserver/tcpserver
|
||||||
echoserver/echoserver
|
echoserver/echoserver
|
||||||
|
example-redis/example-redis
|
||||||
|
116
example-redis/main.go
Normal file
116
example-redis/main.go
Normal file
@ -0,0 +1,116 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bufio"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"log"
|
||||||
|
"net"
|
||||||
|
"strings"
|
||||||
|
)
|
||||||
|
|
||||||
|
func getCommand(reader io.Reader) (string, error) {
|
||||||
|
scanner := bufio.NewScanner(reader)
|
||||||
|
for scanner.Scan() {
|
||||||
|
str := scanner.Text()
|
||||||
|
return str, nil
|
||||||
|
}
|
||||||
|
return "", scanner.Err()
|
||||||
|
}
|
||||||
|
|
||||||
|
func isCommand(str string) bool {
|
||||||
|
switch str {
|
||||||
|
case "GET":
|
||||||
|
fallthrough
|
||||||
|
case "SET":
|
||||||
|
fallthrough
|
||||||
|
case "DEL":
|
||||||
|
return true
|
||||||
|
default:
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCmd(line string) (string, string, string, error) {
|
||||||
|
cmd, key, val := "", "", ""
|
||||||
|
var err error
|
||||||
|
parts := strings.Split(line, " ")
|
||||||
|
if len(parts) != 2 && len(parts) != 3 {
|
||||||
|
err = errors.New("Invalid number of arguments")
|
||||||
|
} else {
|
||||||
|
for i, part := range parts {
|
||||||
|
switch i {
|
||||||
|
case 0:
|
||||||
|
if isCommand(part) {
|
||||||
|
cmd = part
|
||||||
|
} else {
|
||||||
|
err = errors.New("Unknown command")
|
||||||
|
break
|
||||||
|
}
|
||||||
|
case 1:
|
||||||
|
key = part
|
||||||
|
case 2:
|
||||||
|
if cmd == "SET" {
|
||||||
|
val = part
|
||||||
|
} else {
|
||||||
|
err = errors.New("Invalid number of arguments")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cmd, key, val, err
|
||||||
|
}
|
||||||
|
|
||||||
|
func respond(writer io.Writer, ret string) {
|
||||||
|
_, err := io.WriteString(writer, ret+"\n")
|
||||||
|
if err != nil {
|
||||||
|
log.Println(err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
db := make(map[string]string)
|
||||||
|
|
||||||
|
ln, err := net.Listen("tcp", ":9000")
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
defer ln.Close()
|
||||||
|
|
||||||
|
for {
|
||||||
|
conn, err := ln.Accept()
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
|
||||||
|
go func() {
|
||||||
|
line, err := getCommand(conn)
|
||||||
|
if err != nil {
|
||||||
|
respond(conn, fmt.Sprint(err))
|
||||||
|
}
|
||||||
|
cmd, key, val, err := parseCmd(line)
|
||||||
|
if err != nil {
|
||||||
|
respond(conn, fmt.Sprint(err))
|
||||||
|
}
|
||||||
|
switch cmd {
|
||||||
|
case "DEL":
|
||||||
|
log.Println("Deleting", key)
|
||||||
|
delete(db, key)
|
||||||
|
case "GET":
|
||||||
|
log.Println("Getting", key)
|
||||||
|
ret, ok := db[key]
|
||||||
|
if ok {
|
||||||
|
log.Println("Value found", ret)
|
||||||
|
respond(conn, ret)
|
||||||
|
} else {
|
||||||
|
log.Println("Unknown key", key)
|
||||||
|
}
|
||||||
|
case "SET":
|
||||||
|
log.Println("Setting", key)
|
||||||
|
db[key] = val
|
||||||
|
}
|
||||||
|
defer conn.Close()
|
||||||
|
}()
|
||||||
|
}
|
||||||
|
}
|
88
example-redis/main_test.go
Normal file
88
example-redis/main_test.go
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetCommand(t *testing.T) {
|
||||||
|
r := strings.NewReader("test\n")
|
||||||
|
line, err := getCommand(r)
|
||||||
|
if err != nil {
|
||||||
|
t.Log("Should not err")
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if line != "test" {
|
||||||
|
t.Log("Invalid command")
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestIsCommand(t *testing.T) {
|
||||||
|
tests := map[string]bool{
|
||||||
|
"GET": true,
|
||||||
|
"SET": true,
|
||||||
|
"DEL": true,
|
||||||
|
"XXX": false,
|
||||||
|
}
|
||||||
|
for k, v := range tests {
|
||||||
|
if isCommand(k) != v {
|
||||||
|
t.Log("Error " + k)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRespond(t *testing.T) {
|
||||||
|
var bs []byte
|
||||||
|
buf := bytes.NewBuffer(bs)
|
||||||
|
respond(buf, "test")
|
||||||
|
if buf.String() != "test\n" {
|
||||||
|
t.Log("Invalid response (" + buf.String() + ")")
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
type ParseCmdTest struct {
|
||||||
|
line string
|
||||||
|
fails bool
|
||||||
|
cmd string
|
||||||
|
key string
|
||||||
|
val string
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseCommand(t *testing.T) {
|
||||||
|
tests := []ParseCmdTest{
|
||||||
|
{"test", true, "", "", ""},
|
||||||
|
{"test test", true, "", "", ""},
|
||||||
|
{"SET 1 2 3", true, "", "", ""},
|
||||||
|
{"GET 1 2", true, "", "", ""},
|
||||||
|
}
|
||||||
|
for _, test := range tests {
|
||||||
|
c, k, v, e := parseCmd(test.line)
|
||||||
|
if test.fails {
|
||||||
|
if e == nil {
|
||||||
|
t.Log("should fail (" + test.line + ")")
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if e != nil {
|
||||||
|
t.Log("should no fail", e)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if c != test.cmd {
|
||||||
|
t.Log("Error command", c, test.cmd)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if k != test.key {
|
||||||
|
t.Log("Error key", k, test.key)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
if v != test.val {
|
||||||
|
t.Log("Error value", v, test.val)
|
||||||
|
t.Fail()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user