This commit is contained in:
jiazhizhong
2022-03-10 17:09:03 +08:00
commit 1279635d7f
97 changed files with 10632 additions and 0 deletions

111
pkg/rpc/client.go Normal file
View File

@@ -0,0 +1,111 @@
package rpc
import (
"context"
"errors"
"jiacrontab/pkg/proto"
"net"
"net/rpc"
"time"
"github.com/iwannay/log"
)
const (
diaTimeout = 5 * time.Second
callTimeout = 1 * time.Minute
pingDuration = 3 * time.Second
)
var (
ErrRpc = errors.New("rpc is not available")
ErrRpcTimeout = errors.New("rpc call timeout")
ErrRpcCancel = errors.New("rpc call cancel")
ErrShutdown = rpc.ErrShutdown
)
type ClientOptions struct {
Network string
Addr string
}
type Client struct {
*rpc.Client
options ClientOptions
quit chan struct{}
err error
}
func Dial(options ClientOptions) (c *Client) {
c = &Client{}
c.options = options
c.dial()
c.quit = make(chan struct{}, 100)
return c
}
func (c *Client) dial() (err error) {
conn, err := net.DialTimeout(c.options.Network, c.options.Addr, diaTimeout)
if err != nil {
return err
}
c.Client = rpc.NewClient(conn)
return nil
}
func (c *Client) Call(serviceMethod string, ctx context.Context, args interface{}, reply interface{}) error {
if serviceMethod != PingService && serviceMethod != RegisterService {
log.Info("rpc call", c.options.Addr, serviceMethod)
}
if c.Client == nil {
return ErrRpc
}
select {
case <-ctx.Done():
return ErrRpcCancel
case call := <-c.Client.Go(serviceMethod, args, reply, make(chan *rpc.Call, 1)).Done:
return call.Error
case <-time.After(callTimeout):
return ErrRpcTimeout
}
}
func (c *Client) Error() error {
return c.err
}
func (c *Client) Close() {
c.quit <- struct{}{}
}
func (c *Client) Ping(serviceMethod string) {
var (
err error
)
for {
select {
case <-c.quit:
goto closed
default:
}
if c.Client != nil && c.err == nil {
if err = c.Call(serviceMethod, context.TODO(), &proto.EmptyArgs{}, &proto.EmptyReply{}); err != nil {
c.err = err
c.Client.Close()
log.Infof("client.Call(%s, args, reply) error (%v) \n", serviceMethod, err)
}
} else {
if err = c.dial(); err == nil {
c.err = nil
log.Info("client reconnet ", c.options.Addr)
}
}
time.Sleep(pingDuration)
}
closed:
log.Info("rpc quited", c.options.Addr)
if c.Client != nil {
c.Client.Close()
}
}

66
pkg/rpc/client_test.go Normal file
View File

@@ -0,0 +1,66 @@
package rpc
import (
"jiacrontab/pkg/proto"
"log"
"net/http"
_ "net/http/pprof"
"sync"
"testing"
"time"
)
type Logic struct {
}
func (l *Logic) Ping(args *proto.EmptyArgs, reply *proto.EmptyReply) error {
return nil
}
func (p *Logic) Say(args string, reply *string) error {
*reply = "hello boy"
time.Sleep(100 * time.Second)
return nil
}
func TestCall(t *testing.T) {
done := make(chan struct{})
go func() {
done <- struct{}{}
log.Println("start server")
err := listen(":6478", &Logic{})
if err != nil {
t.Fatal("server error:", err)
}
}()
<-done
time.Sleep(5 * time.Second)
// 等待server启动
var wg sync.WaitGroup
for i := 0; i < 100; i++ {
wg.Add(1)
go func(i int) {
defer wg.Done()
var ret string
// var args string
err := Call(":6478", "Logic.Say", "", &ret)
if err != nil {
log.Println(i, "error:", err)
}
t.Log(i, ret)
}(i)
}
go func() {
t.Log("listen :6060")
t.Log(http.ListenAndServe(":6060", nil))
}()
wg.Wait()
log.Println("end")
time.Sleep(2 * time.Minute)
}

86
pkg/rpc/clients.go Normal file
View File

@@ -0,0 +1,86 @@
package rpc
import (
"context"
"net/rpc"
"sync"
"github.com/iwannay/log"
)
var (
defaultClients *clients
PingService = "Srv.Ping"
RegisterService = "Srv.Register"
)
type clients struct {
lock sync.RWMutex
clients map[string]*Client
}
func (c *clients) get(addr string) *Client {
var (
cli *Client
ok bool
op ClientOptions
)
c.lock.Lock()
defer c.lock.Unlock()
if cli, ok = c.clients[addr]; ok {
return cli
}
op.Network = "tcp4"
op.Addr = addr
cli = Dial(op)
c.clients[addr] = cli
go cli.Ping(PingService)
return cli
}
func (c *clients) del(addr string) {
c.lock.Lock()
defer c.lock.Unlock()
if cli, ok := c.clients[addr]; ok {
cli.Close()
}
delete(c.clients, addr)
}
func Call(addr string, serviceMethod string, args interface{}, reply interface{}) error {
err := defaultClients.get(addr).Call(serviceMethod, context.TODO(), args, reply)
if err == rpc.ErrShutdown {
log.Debug("rpc remove", addr)
Del(addr)
}
return err
}
func CallCtx(addr string, serviceMethod string, ctx context.Context, args interface{}, reply interface{}) error {
err := defaultClients.get(addr).Call(serviceMethod, ctx, args, reply)
if err == rpc.ErrShutdown {
log.Debug("rpc remove", addr)
Del(addr)
}
return err
}
func Del(addr string) {
if defaultClients != nil {
defaultClients.del(addr)
}
}
func DelNode(addr string) {
if defaultClients != nil {
defaultClients.del(addr)
}
}
func init() {
defaultClients = &clients{
clients: make(map[string]*Client),
}
}

41
pkg/rpc/server.go Normal file
View File

@@ -0,0 +1,41 @@
package rpc
import (
"github.com/iwannay/log"
"net"
"net/rpc"
)
// listen Start rpc server
func listen(addr string, srcvr ...interface{}) error {
var err error
for _, v := range srcvr {
if err = rpc.Register(v); err != nil {
return err
}
}
l, err := net.Listen("tcp4", addr)
if err != nil {
return err
}
defer func() {
log.Info("listen rpc", addr, "close")
if err := l.Close(); err != nil {
log.Infof("listen.Close() error(%v)", err)
}
}()
rpc.Accept(l)
return nil
}
// ListenAndServe run rpc server
func ListenAndServe(addr string, srcvr ...interface{}) {
log.Info("rpc server listen:", addr)
err := listen(addr, srcvr...)
if err != nil {
panic(err)
}
}