48

Skip to content
Why GitHub?
Team
Enterprise
Explore
Marketplace
Pricing

Search

Sign in
Sign up
HDT3213
/
godis
Watch 3 Star 67 Fork 9
Code
Issues
1
Pull requests
Actions
Projects
Security
Insights
master
godis/src/redis/client/client.go / Jump to
Go to file
@HDT3213
HDT3213 refine some code
Latest commit cc6475f 2 days ago
History
1 contributor
322 lines (299 sloc) 8.94 KB
RawBlame

package client

import (
“bufio”
“context”
“errors”
“github.com/HDT3213/godis/src/interface/redis”
“github.com/HDT3213/godis/src/lib/logger”
“github.com/HDT3213/godis/src/lib/sync/wait”
“github.com/HDT3213/godis/src/redis/reply”
“io”
“net”
“strconv”
“strings”
“sync”
“time”
)

type Client struct {
conn net.Conn
sendingReqs chan *Request // waiting sending
waitingReqs chan *Request // waiting response
ticker *time.Ticker
addr string

ctx        context.Context
cancelFunc context.CancelFunc
writing    *sync.WaitGroup

}

type Request struct {
id uint64
args [][]byte
reply redis.Reply
heartbeat bool
waiting *wait.Wait
err error
}

const (
chanSize = 256
maxWait = 3 * time.Second
)

func MakeClient(addr string) (*Client, error) {
conn, err := net.Dial(“tcp”, addr)
if err != nil {
return nil, err
}
ctx, cancel := context.WithCancel(context.Background())
return &Client{
addr: addr,
conn: conn,
sendingReqs: make(chan *Request, chanSize),
waitingReqs: make(chan *Request, chanSize),
ctx: ctx,
cancelFunc: cancel,
writing: &sync.WaitGroup{},
}, nil
}

func (client *Client) Start() {
client.ticker = time.NewTicker(10 * time.Second)
go client.handleWrite()
go func() {
err := client.handleRead()
logger.Warn(err)
}()
go client.heartbeat()
}

func (client *Client) Close() {
// stop new request
close(client.sendingReqs)

// wait stop process
client.writing.Wait()

// clean
client.cancelFunc()
_ = client.conn.Close()
close(client.waitingReqs)

}

func (client *Client) handleConnectionError(err error) error {
err1 := client.conn.Close()
if err1 != nil {
if opErr, ok := err1.(*net.OpError); ok {
if opErr.Err.Error() != “use of closed network connection” {
return err1
}
} else {
return err1
}
}
conn, err1 := net.Dial(“tcp”, client.addr)
if err1 != nil {
logger.Error(err1)
return err1
}
client.conn = conn
go func() {
_ = client.handleRead()
}()
return nil
}

func (client *Client) heartbeat() {
loop:
for {
select {
case <-client.ticker.C:
client.sendingReqs <- &Request{
args: [][]byte{[]byte(“PING”)},
heartbeat: true,
}
case <-client.ctx.Done():
break loop
}
}
}

func (client *Client) handleWrite() {
loop:
for {
select {
case req := <-client.sendingReqs:
client.writing.Add(1)
client.doRequest(req)
case <-client.ctx.Done():
break loop
}
}
}

// todo: wait with timeout
func (client *Client) Send(args [][]byte) redis.Reply {
request := &Request{
args: args,
heartbeat: false,
waiting: &wait.Wait{},
}
request.waiting.Add(1)
client.sendingReqs <- request
timeout := request.waiting.WaitWithTimeout(maxWait)
if timeout {
return reply.MakeErrReply(“server time out”)
}
if request.err != nil {
return reply.MakeErrReply(“request failed”)
}
return request.reply
}

func (client *Client) doRequest(req *Request) {
bytes := reply.MakeMultiBulkReply(req.args).ToBytes()
_, err := client.conn.Write(bytes)
i := 0
for err != nil && i < 3 {
err = client.handleConnectionError(err)
if err == nil {
_, err = client.conn.Write(bytes)
}
i++
}
if err == nil {
client.waitingReqs <- req
} else {
req.err = err
req.waiting.Done()
client.writing.Done()
}
}

func (client *Client) finishRequest(reply redis.Reply) {
request := <-client.waitingReqs
request.reply = reply
if request.waiting != nil {
request.waiting.Done()
}
client.writing.Done()
}

func (client *Client) handleRead() error {
reader := bufio.NewReader(client.conn)
downloading := false
expectedArgsCount := 0
receivedCount := 0
msgType := byte(0) // first char of msg
var args [][]byte
var fixedLen int64 = 0
var err error
var msg []byte
for {
// read line
if fixedLen == 0 { // read normal line
msg, err = reader.ReadBytes(’\n’)
if err != nil {
if err == io.EOF || err == io.ErrUnexpectedEOF {
logger.Info(“connection close”)
} else {
logger.Warn(err)
}

            return errors.New("connection closed")
        }
        if len(msg) == 0 || msg[len(msg)-2] != '\r' {
            return errors.New("protocol error")
        }
    } else { // read bulk line (binary safe)
        msg = make([]byte, fixedLen+2)
        _, err = io.ReadFull(reader, msg)
        if err != nil {
            if err == io.EOF || err == io.ErrUnexpectedEOF {
                return errors.New("connection closed")
            } else {
                return err
            }
        }
        if len(msg) == 0 ||
            msg[len(msg)-2] != '\r' ||
            msg[len(msg)-1] != '\n' {
            return errors.New("protocol error")
        }
        fixedLen = 0
    }

    // parse line
    if !downloading {
        // receive new response
        if msg[0] == '*' { // multi bulk response
            // bulk multi msg
            expectedLine, err := strconv.ParseUint(string(msg[1:len(msg)-2]), 10, 32)
            if err != nil {
                return errors.New("protocol error: " + err.Error())
            }
            if expectedLine == 0 {
                client.finishRequest(&reply.EmptyMultiBulkReply{})
            } else if expectedLine > 0 {
                msgType = msg[0]
                downloading = true
                expectedArgsCount = int(expectedLine)
                receivedCount = 0
                args = make([][]byte, expectedLine)
            } else {
                return errors.New("protocol error")
            }
        } else if msg[0] == '$' { // bulk response
            fixedLen, err = strconv.ParseInt(string(msg[1:len(msg)-2]), 10, 64)
            if err != nil {
                return err
            }
            if fixedLen == -1 { // null bulk
                client.finishRequest(&reply.NullBulkReply{})
                fixedLen = 0
            } else if fixedLen > 0 {
                msgType = msg[0]
                downloading = true
                expectedArgsCount = 1
                receivedCount = 0
                args = make([][]byte, 1)
            } else {
                return errors.New("protocol error")
            }
        } else { // single line response
            str := strings.TrimSuffix(string(msg), "\n")
            str = strings.TrimSuffix(str, "\r")
            var result redis.Reply
            switch msg[0] {
            case '+':
                result = reply.MakeStatusReply(str[1:])
            case '-':
                result = reply.MakeErrReply(str[1:])
            case ':':
                val, err := strconv.ParseInt(str[1:], 10, 64)
                if err != nil {
                    return errors.New("protocol error")
                }
                result = reply.MakeIntReply(val)
            }
            client.finishRequest(result)
        }
    } else {
        // receive following part of a request
        line := msg[0 : len(msg)-2]
        if line[0] == '$' {
            fixedLen, err = strconv.ParseInt(string(line[1:]), 10, 64)
            if err != nil {
                return err
            }
            if fixedLen <= 0 { // null bulk in multi bulks
                args[receivedCount] = []byte{}
                receivedCount++
                fixedLen = 0
            }
        } else {
            args[receivedCount] = line
            receivedCount++
        }

        // if sending finished
        if receivedCount == expectedArgsCount {
            downloading = false // finish downloading progress

            if msgType == '*' {
                reply := reply.MakeMultiBulkReply(args)
                client.finishRequest(reply)
            } else if msgType == '$' {
                reply := reply.MakeBulkReply(args[0])
                client.finishRequest(reply)
            }


            // finish reply
            expectedArgsCount = 0
            receivedCount = 0
            args = nil
            msgType = byte(0)
        }
    }
}

}
© 2020 GitHub, Inc.
Terms
Privacy
Security
Status
Help
Contact GitHub
Pricing
API
Training
Blog
About

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值