简介
主要用于DeApp与node之间的交互
结构
1 n 1 n use <<interface>> ServerCodec Server serviceRegistry service callback Client jsonCodec
Client:服务端接收到一个连接后创建Client来处理请求,表示一个到服务端的连接
go
type Client struct {
idgen func() ID // for subscriptions
isHTTP bool // connection type: http, ws or ipc
services *serviceRegistry
idCounter atomic.Uint32
// This function, if non-nil, is called when the connection is lost.
reconnectFunc reconnectFunc
// config fields
batchItemLimit int
batchResponseMaxSize int
// writeConn is used for writing to the connection on the caller's goroutine. It should
// only be accessed outside of dispatch, with the write lock held. The write lock is
// taken by sending on reqInit and released by sending on reqSent.
writeConn jsonWriter
// for dispatch
close chan struct{}
closing chan struct{} // closed when client is quitting
didClose chan struct{} // closed when client quits
reconnected chan ServerCodec // where write/reconnect sends the new connection
readOp chan readOp // read messages
readErr chan error // errors from read
reqInit chan *requestOp // register response IDs, takes write lock
reqSent chan error // signals write completion, releases write lock
reqTimeout chan *requestOp // removes response IDs when call timeout expires
}
客户端创建Client使用newClient,服务端接收连接创建表示连接的客户端使用initClient。即客户端主要使用下面几个方法
- DialOptions
- DialHTTPWithClient(http)
- DialInProc(inproc进程内)
- DialIPC(进程间)
- DialWebsocketWithDialer或者DialWebsocket(websocket)
go
func newClient(initctx context.Context, cfg *clientConfig, connect reconnectFunc) (*Client, error) {
conn, err := connect(initctx)
if err != nil {
return nil, err
}
c := initClient(conn, new(serviceRegistry), cfg)
c.reconnectFunc = connect
return c, nil
}
func initClient(conn ServerCodec, services *serviceRegistry, cfg *clientConfig) *Client {
_, isHTTP := conn.(*httpConn)
c := &Client{
isHTTP: isHTTP,
services: services,
idgen: cfg.idgen,
batchItemLimit: cfg.batchItemLimit,
batchResponseMaxSize: cfg.batchResponseLimit,
writeConn: conn,
close: make(chan struct{}),
closing: make(chan struct{}),
didClose: make(chan struct{}),
reconnected: make(chan ServerCodec),
readOp: make(chan readOp),
readErr: make(chan error),
reqInit: make(chan *requestOp),
reqSent: make(chan error, 1),
reqTimeout: make(chan *requestOp),
}
// Set defaults.
if c.idgen == nil {
c.idgen = randomIDGenerator()
}
// Launch the main loop.
if !isHTTP {
go c.dispatch(conn)
}
return c
}
Server:rpc服务器
go
type Server struct {
services serviceRegistry
idgen func() ID
mutex sync.Mutex
codecs map[ServerCodec]struct{}
run atomic.Bool
batchItemLimit int
batchResponseLimit int
httpBodyLimit int
wsReadLimit int64
}
serviceRegistry:用于管理服务器对外提供的服务
go
type serviceRegistry struct {
mu sync.Mutex
services map[string]service
}
service:对外提供的一个服务
go
type service struct {
name string // name for service
callbacks map[string]*callback // registered handlers
subscriptions map[string]*callback // available subscriptions/notifications
}
callback:服务中的一个方法
go
type callback struct {
fn reflect.Value // the function
rcvr reflect.Value // receiver object of method, set if fn is method
argTypes []reflect.Type // input argument types
hasCtx bool // method's first argument is a context (not included in argTypes)
errPos int // err return idx, of -1 when method cannot return error
isSubscribe bool // true if this is a subscription callback
}
ServerCodec :网络编解码接口
go
type ServerCodec interface {
peerInfo() PeerInfo
readBatch() (msgs []*jsonrpcMessage, isBatch bool, err error)
close()
jsonWriter
}
rpc
有四种
- inproc(进程内)
- ipc(同机进程间)
- http
- websocket
- 认证的http和websocket
http类rpc
通过httpServer来管理
httpServer rpcHandler rpc.Server http.Handler
httpServer结构定义为
go
type httpServer struct {
log log.Logger
timeouts rpc.HTTPTimeouts
mux http.ServeMux // registered handlers go here
mu sync.Mutex
server *http.Server
listener net.Listener // non-nil when server is running
// HTTP RPC handler things.
httpConfig httpConfig
httpHandler atomic.Pointer[rpcHandler]
// WebSocket handler things.
wsConfig wsConfig
wsHandler atomic.Pointer[rpcHandler]
// These are set by setListenAddr.
endpoint string
host string
port int
handlerNames map[string]string
}
开启rpc
步聚为
- 通过httpServer的enableRPC方法,设置httpHandler
- 调用httpServer的start方法
- 创建http.Server,设置Handler为自己(即httpServer),设置h.server
- 创建listener
- goroutine执行server.Serve,接收请求,执行httpServer.ServeHTTP
go
func (h *httpServer) enableRPC(apis []rpc.API, config httpConfig) error {
h.mu.Lock()
defer h.mu.Unlock()
if h.rpcAllowed() {
return errors.New("JSON-RPC over HTTP is already enabled")
}
// Create RPC server and handler.
srv := rpc.NewServer()
srv.SetBatchLimits(config.batchItemLimit, config.batchResponseSizeLimit)
if config.httpBodyLimit > 0 {
srv.SetHTTPBodyLimit(config.httpBodyLimit)
}
if err := RegisterApis(apis, config.Modules, srv); err != nil {
return err
}
h.httpConfig = config
h.httpHandler.Store(&rpcHandler{
Handler: NewHTTPHandlerStack(srv, config.CorsAllowedOrigins, config.Vhosts, config.jwtSecret),
prefix: config.prefix,
server: srv,
})
return nil
}
func (h *httpServer) start() error {
h.mu.Lock()
defer h.mu.Unlock()
if h.endpoint == "" || h.listener != nil {
return nil // already running or not configured
}
// Initialize the server.
h.server = &http.Server{Handler: h}
if h.timeouts != (rpc.HTTPTimeouts{}) {
CheckTimeouts(&h.timeouts)
h.server.ReadTimeout = h.timeouts.ReadTimeout
h.server.ReadHeaderTimeout = h.timeouts.ReadHeaderTimeout
h.server.WriteTimeout = h.timeouts.WriteTimeout
h.server.IdleTimeout = h.timeouts.IdleTimeout
}
// Start the server.
listener, err := net.Listen("tcp", h.endpoint)
if err != nil {
// If the server fails to start, we need to clear out the RPC and WS
// configuration so they can be configured another time.
h.disableRPC()
h.disableWS()
return err
}
h.listener = listener
go h.server.Serve(listener)
if h.wsAllowed() {
url := fmt.Sprintf("ws://%v", listener.Addr())
if h.wsConfig.prefix != "" {
url += h.wsConfig.prefix
}
h.log.Info("WebSocket enabled", "url", url)
}
// if server is websocket only, return after logging
if !h.rpcAllowed() {
return nil
}
// Log http endpoint.
h.log.Info("HTTP server started",
"endpoint", listener.Addr(), "auth", h.httpConfig.jwtSecret != nil,
"prefix", h.httpConfig.prefix,
"cors", strings.Join(h.httpConfig.CorsAllowedOrigins, ","),
"vhosts", strings.Join(h.httpConfig.Vhosts, ","),
)
// Log all handlers mounted on server.
var paths []string
for path := range h.handlerNames {
paths = append(paths, path)
}
sort.Strings(paths)
logged := make(map[string]bool, len(paths))
for _, path := range paths {
name := h.handlerNames[path]
if !logged[name] {
log.Info(name+" enabled", "url", "http://"+listener.Addr().String()+path)
logged[name] = true
}
}
return nil
}
func (h *httpServer) ServeHTTP(w http.ResponseWriter, r *http.Request) {
// check if ws request and serve if ws enabled
ws := h.wsHandler.Load()
if ws != nil && isWebsocket(r) {
if checkPath(r, ws.prefix) {
ws.ServeHTTP(w, r)
}
return
}
// if http-rpc is enabled, try to serve request
rpc := h.httpHandler.Load()
if rpc != nil {
// First try to route in the mux.
// Requests to a path below root are handled by the mux,
// which has all the handlers registered via Node.RegisterHandler.
// These are made available when RPC is enabled.
muxHandler, pattern := h.mux.Handler(r)
if pattern != "" {
muxHandler.ServeHTTP(w, r)
return
}
if checkPath(r, rpc.prefix) {
rpc.ServeHTTP(w, r)
return
}
}
w.WriteHeader(http.StatusNotFound)
}