Last active
June 15, 2019 13:01
-
-
Save ii64/adbee49ef4ab118bdff9c5c5e7d3a49a to your computer and use it in GitHub Desktop.
redis server implemented with golang
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
package main | |
/* | |
https://github.com/anysz | |
*/ | |
import ( | |
"net" | |
"fmt" | |
"strconv" | |
) | |
var ( | |
Port string = ":3232" | |
ReadMem int = 512*1 | |
) | |
var ( | |
TList byte = 42 | |
TSimpleString byte = 43 | |
TString byte = 36 | |
TInt byte = 58 | |
TErr byte = 45 | |
CR byte = 13 | |
CN byte = 10 | |
) | |
var ( | |
RES_OK = []byte("+OK\r\n") | |
RES_PONG = []byte("+PONG\r\n") | |
ERR_INV_ARG_LEN = []byte("-ERR command not resolved, needed first argument as key to resolve shard node\r\n") | |
ERR_IMPL = []byte("-ERR 500 Not Implemented") | |
EMPTY_BYTE_STACK = []byte{} | |
) | |
func main() { | |
svr, err := net.Listen("tcp", Port) | |
if err != nil { | |
fmt.Println("error:", err) | |
return | |
} | |
fmt.Println("Server on", Port) | |
// Accepting incomming tcp connect | |
for { | |
conn, err := svr.Accept() | |
if err != nil { | |
continue | |
} | |
go hConn(conn) | |
} | |
} | |
func hConn(c net.Conn) { | |
tmp_byte := []byte{} | |
for { | |
//fmt.Println("\r ============ RECEIVED =============") | |
data := make([]byte, ReadMem) | |
n, err := c.Read(data) | |
if err != nil { | |
//fmt.Println(err) | |
c.Close() | |
return | |
} | |
if n == ReadMem { | |
//incomplete_read := hProcessor(c, data) | |
tmp_byte = append(tmp_byte, data...) | |
}else{ | |
tmp_byte = append(tmp_byte, data[:n]...) | |
go hProcessor(c, tmp_byte) | |
tmp_byte = EMPTY_BYTE_STACK | |
} | |
//fmt.Println("n value:", n) | |
//fmt.Printf("data: %#+v\r\n", string(data)) | |
//fmt.Printf("data cut: %#+v\r\n", string(data[:n])) | |
} | |
} | |
type rsWriter struct { | |
writer net.Conn | |
buf []byte | |
} | |
func NewResWriter(c net.Conn) rsWriter { | |
return rsWriter{c, []byte{}} | |
} | |
func (s *rsWriter) Flush() { | |
s.writer.Write(s.buf) | |
s.buf = []byte{} | |
} | |
func (s *rsWriter) Write(b []byte) { | |
//s.buf = append(s.buf, b...) | |
s.writer.Write(b) | |
} | |
func (s *rsWriter) WriteNil() { | |
s.buf = append(s.buf, TString, 45, 49, CR, CN) | |
} | |
func (s *rsWriter) WriteString(d string) { | |
p := []byte(d) | |
s.buf = append(s.buf, TString) | |
s.buf = append(s.buf, []byte(strconv.Itoa(len(p)))...) | |
s.buf = append(s.buf, CR, CN) | |
s.buf = append(s.buf, p...) | |
s.buf = append(s.buf, CR, CN) | |
} | |
func (s *rsWriter) WriteInt(d int) { | |
s.buf = append(s.buf, TInt) | |
s.buf = append(s.buf, []byte(strconv.Itoa(d))...) | |
s.buf = append(s.buf, CR, CN) | |
} | |
func (s *rsWriter) WriteRes(d interface{}) { | |
switch d.(type) { | |
case nil: | |
s.WriteNil() | |
case string: | |
s.WriteString(d.(string)) | |
case int: | |
s.WriteInt(d.(int)) | |
case []string: | |
p := d.([]string) | |
s.buf = append(s.buf, TList) | |
s.buf = append(s.buf, []byte(strconv.Itoa(len(p)))...) | |
s.buf = append(s.buf, CR, CN) | |
for _, item := range p { | |
s.WriteRes(item) | |
} | |
case []int: | |
p := d.([]int) | |
s.buf = append(s.buf, TList) | |
s.buf = append(s.buf, []byte(strconv.Itoa(len(p)))...) | |
s.buf = append(s.buf, CR, CN) | |
for _, item := range p { | |
s.WriteRes(item) | |
} | |
case []interface{}: | |
p := d.([]interface{}) | |
s.buf = append(s.buf, TList) | |
s.buf = append(s.buf, []byte(strconv.Itoa(len(p)))...) | |
s.buf = append(s.buf, CR, CN) | |
for _, item := range p { | |
s.WriteRes(item) | |
} | |
} | |
return | |
} | |
type rqReader struct { | |
buf []byte | |
} | |
func NewReqReader(buf []byte) rqReader { return rqReader{buf} } | |
func (s *rqReader) ReadVarint() (r int, e error) { | |
if len(s.buf) < 1 {} | |
cnv := []byte{} | |
last := 0 | |
for i, b := range s.buf { | |
// byte of numbers | |
if b == 48 || b == 49 || b == 50 || b == 51 || b == 52 || b == 53 || b == 54 || b == 55 || b == 56 || b == 57 { | |
cnv = append(cnv, b) | |
} else if b == CN { | |
last = i + 1 | |
break | |
} | |
} | |
s.buf = s.buf[last:] | |
r, e = strconv.Atoi(string(cnv)) | |
return | |
} | |
func (s *rqReader) ReadData() (r []byte, e error) { | |
// Stricted due to redis-cli send all args as string | |
size, e := s.ReadVarint() | |
r = s.buf[0:size] | |
s.buf = s.buf[size+2:] | |
return | |
} | |
func (s *rqReader) ReadSkip(skip int) { | |
// Skip based on first varint of Args TList | |
if skip < 1 { return } | |
skip = skip *2 | |
foundCN := 0 | |
for i, b := range s.buf { | |
if b == CN { | |
foundCN += 1 | |
if foundCN == skip { | |
s.buf = s.buf[i+1:] | |
break | |
} | |
} | |
} | |
} | |
func (s *rqReader) IsPING() bool { | |
if len(s.buf) > 4 { | |
if ping := s.buf[:4]; (ping[0] == 112 || ping[0] == 80 /*pP*/) && (ping[1] == 105 || ping[1] == 73) && (ping[2] == 110 || ping[2] == 78) && (ping[3] == 103 || ping[3] == 71) { | |
// mark as readed | |
s.buf = s.buf[6:] | |
return true | |
} | |
} | |
return false | |
} | |
func hProcessor(c net.Conn, data []byte) (incomplete []byte) { | |
rd := NewReqReader(data) | |
w := NewResWriter(c) | |
//fmt.Printf("hProc %#+v\r\n", string(data)) | |
for { | |
// Check ping request | |
if len(rd.buf) == 0 { break } | |
if rd.IsPING() { | |
//fmt.Println("Ping received!") | |
go func() { | |
w.Write(RES_PONG) | |
}() | |
}else{ | |
argslen, _ := rd.ReadVarint() | |
cmd, _ := rd.ReadData() | |
_ = cmd | |
rd.ReadSkip(argslen-1) // skip entire args | |
//fmt.Println("Recv cmd", string(cmd)) | |
w.Write(RES_OK) | |
} | |
} | |
w.Flush() | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment