微博的网站连接是怎么做的,网站挂马 屏蔽站长的ip,华夏望子成龙网站开发背景,网站开发课程设计说明书这里我们来实现这个RPC的client端
为了实现RPC的效果#xff0c;我们调用的Hello方法#xff0c;即server端的方法#xff0c;应该是由代理来调用#xff0c;让proxy里面封装网络请求#xff0c;消息的发送和接受处理。而上一篇文章提到的服务端的代理已经在.rpc.go文件中…这里我们来实现这个RPC的client端
为了实现RPC的效果我们调用的Hello方法即server端的方法应该是由代理来调用让proxy里面封装网络请求消息的发送和接受处理。而上一篇文章提到的服务端的代理已经在.rpc.go文件中实现我们将客户端的实现也写在这里
ClientProxy
// 客户端代理接口
type HelloClientProxy interface {Hello(ctx context.Context, in *HelloRequest, opts ...client.Option) (*HelloReply, error)
}// 客户端代理实现
type HelloClientProxyImpl struct {client client.Clientopts []client.Option
}// 创建客户端代理
func NewHelloClientProxy(opts ...client.Option) HelloClientProxy {return HelloClientProxyImpl{client: client.DefaultClient,opts: opts,}
}
这里的HelloClientProxyImpl其中的client类主要是负责invoke方法抽象网络IO和编解码opts主要是记录客户端启动时传入的配置项如server的ip地址等创建出客户端代理我们就可以通过代理来调用Hello方法 // 实现Hello方法
func (c *HelloClientProxyImpl) Hello(ctx context.Context, req *HelloRequest, opts ...client.Option) (*HelloReply, error) {// 创建一个msg结构存储service相关的数据如serviceName等并放到context中// 用msg结构可以避免在context中太多withValue传递过多的参数msg : internel.NewMsg()msg.WithServiceName(helloworld)msg.WithMethodName(Hello)ctx context.WithValue(ctx, internel.ContextMsgKey, msg)rsp : HelloReply{}// 这里需要将opts添加前面newProxy时传入的optsnewOpts : append(c.opts, opts...)err : c.client.Invoke(ctx, req, rsp, newOpts...)if err ! nil {return nil, err}return rsp, nil
}这里需要明确service的名字和对应方法为了后续封装在协议数据里到达server端才能正确路由。当代理类实现了这个Hello后我们就可以通过proxy.Hello得到相应结果Invoke方法隐藏了具体的网络处理我们跟进Invoke方法
ClientclientTransPort
上文提到client类主要处理invoke方法我们可以预见它的职责就是
序列化请求体编码发送请求接受响应解码反序列化响应体返回客户端 为了代码的解耦我们和server的处理一样将以上操作放到clientTransPort上client持有transPort让transPort处理以上的逻辑 // 实现Send方法
func (c *clientTransport) Send(ctx context.Context, reqBody interface{}, rspBody interface{}, opt *ClientTransportOption) error {// 获取连接// TODO 这里的连接后续可以优化从连接池获取conn, err : net.Dial(tcp, opt.Address)if err ! nil {return err}defer conn.Close()// reqbody序列化reqData, err : codec.Marshal(reqBody)if err ! nil {return err}// reqbody编码返回请求帧framedata, err : opt.Codec.Encode(ctx, reqData)if err ! nil {return err}// 写数据到连接中err c.tcpWriteFrame(ctx, conn, framedata)if err ! nil {return err}// 读取tcp帧rspDataBuf, err : c.tcpReadFrame(ctx, conn)if err ! nil {return err}// 获取msgctx, msg : internel.GetMessage(ctx)// rspDataBuf解码提取响应体数据rspData, err : opt.Codec.Decode(msg, rspDataBuf)if err ! nil {return err}// 将rspData反序列化为rspBodyerr codec.Unmarshal(rspData, rspBody)if err ! nil {return err}return nil
}序列化是根据protobuf协议编码的格式我们之间写Server的时候提到我们需要将数据编码成以下格式 当编码完成后我们就需要写数据到连接中并监听该连接的数据当有数据后我们再依次解码得到响应体再将响应体反序列化返回客户端。 写数据到连接和读连接中的数据也很简单这里我们直接开启一个连接调用Write写而codec.ReadFrame在server端的时候已经介绍过
func (c *clientTransport) tcpWriteFrame(ctx context.Context, conn net.Conn, frame []byte) error {// 写入tcp_, err : conn.Write(frame)if err ! nil {return fmt.Errorf(write frame error: %v, err)}return nil
}func (c *clientTransport) tcpReadFrame(ctx context.Context, conn net.Conn) ([]byte, error) {return codec.ReadFrame(conn)
}效果测试
至此client端处理完毕我们来看看效果
//client端的测试main.go:
func main() {c : pb.NewHelloClientProxy(client.WithTarget(127.0.0.1:8000))if c nil {fmt.Println(Failed to create client)return}rsp, err : c.Hello(context.Background(), pb.HelloRequest{Msg: world})if err ! nil {fmt.Println(RPC call error:, err)return}fmt.Println(Response:, rsp.Msg)
}// server端的测试的main.go
func main() {// Create a new server instances : server.NewServer()// Register the HelloService with the serverpb.RegisterHelloServer(s, HelloServerImpl{})// Start the server on port 50051if err : s.Serve(:8000); err ! nil {panic(err)}fmt.Print(启动成功)
}// 创建一个HelloServer的实现类
type HelloServerImpl struct{}
// 实现HelloServer接口的Hello方法
func (h *HelloServerImpl) Hello(req *pb.HelloRequest) (*pb.HelloReply, error) {// 这里可以实现具体的业务逻辑reply : pb.HelloReply{Msg: Hello req.Msg,}return reply, nil
}server端启动
server端接收到client的连接请求
client收到响应
现在version1开发完了目前的版本主要是实现基础功能并且为了考虑后续的扩展性做了比较多的解耦在后面的版本我们可以逐渐提升这个rpc的性能和融入更多的功能