Golang中如何通过net/rpc获取客户端的真实IP

Golang中如何通过net/rpc获取客户端的真实IP 我有一个需求,需要RPC服务器获取客户端的真实IP和当前连接的net.Conn

我过去通过以下示例方法获取:

// example_server.go
type MathService struct{}
type Args struct {
	A, B int
}
type Reply struct {
	Result int
}
func (m *MathService) Multiply(args *Args, reply *Reply) error {
	reply.Result = args.A * args.B
	return nil
}
func main() {
	// Create an instance of the MathService
	mathService := new(MathService)
	// Register MathService for RPC
	rpc.Register(mathService)
	// Create a TCP listener
	listener, err := net.Listen("tcp", "0.0.0.0:1234")
	if err != nil {
		fmt.Println("Error starting server:", err)
		return
	}
	defer listener.Close()
	fmt.Println("Server listening on :1234")
	for {
		// Accept incoming connections
		conn, err := listener.Accept()
		if err != nil {
			fmt.Println("Error accepting connection:", err)
			continue
		}
		fmt.Printf("remote ip addr: %s\n", conn.RemoteAddr().String())
		// Serve the connection in a new goroutine
		go rpc.ServeConn(conn)
	}
}

// example_client.go
type Args struct {
	A, B int
}
type Reply struct {
	Result int
}
func main() {
	// Connect to the server
	client, err := rpc.Dial("tcp", "127.0.0.1:1234")
	if err != nil {
		fmt.Println("Error connecting to server:", err)
		return
	}
	defer client.Close()
	// Prepare the arguments for the RPC call
	args := &Args{A: 5, B: 3}
	// Call the Multiply method remotely
	var reply Reply
	err = client.Call("MathService.Multiply", args, &reply)
	if err != nil {
		fmt.Println("Error calling Multiply:", err)
		return
	}
	// Print the result
	fmt.Printf("Result: %d\n", reply.Result)
}

但是RemoteAddr方法获取的并不一定是客户端的真实IP

因此,我希望能够手动调用ReportRealIP函数来报告真实IP,但我无法在RPC函数中获取到当前连接的net.Conn

我搜索了一些资料,但没有找到实现我需求的方法。是否有办法获取到我想要的这两个变量?期待您的回复。

需要真实IPnet.Conn的原因大致是为了双向通信。我希望能够向特定IP的客户端发送RPC请求以执行某些操作。例如,向某些Agent服务发出RPC命令来执行某个脚本。


更多关于Golang中如何通过net/rpc获取客户端的真实IP的实战教程也可以访问 https://www.itying.com/category-94-b0.html

2 回复

稍微回顾一下:

moluzhui:

需要真实IPnet.Conn的原因大致是为了双向通信。我希望能够向特定IP的客户端发送RPC请求以执行某些操作。例如,向某些Agent服务发出RPC命令来执行某个脚本。

你确定RPC是这里适合的工具吗?如果你想要的只是双向通信,或许可以使用WebSocket。好处包括:你可能已经熟悉标准库的Web相关功能,因此上手会容易得多。看看我为了帮同事验证概念而用 gorilla/mux 做的这个演示:

GitHub - DeanPDX/go-websocket-job-runner: A websocket job-runner concept...

GitHub - DeanPDX/go-websocket-job-runner: A websocket job-runner concept…

一个WebSocket任务运行器概念应用。通过在GitHub上创建账户来为DeanPDX/go-websocket-job-runner的开发做出贡献。

另外,gRPC和协议缓冲区可能也是一个很好的用例:

gRPC

gRPC

一个高性能、开源的通用RPC框架

然后你可以使用这个方法来获取IP:

github.com/grpc/grpc-go

github.com/grpc/grpc-go

Is there any guide to get the client IP address and user-agent?

请让我知道这里是否可以提问,对此表示抱歉。

What version of gRPC are you using?

I’m using v1.12.0

What version of Go are you using (go version)?

I’m using v1.10.2

What operating system (Linux, Windows, …) and version?

I’m using macOS

What did you do?

If possible, provide a recipe for reproducing the error. I’m setting up a serials of grpc calls following your official guides which all work fine. but now i’m required to get the client IP address and user-agent. Unfortunately I don’t see any pages that introducing how to get it out.

What did you expect to see?

I expect to see a page giving an example how to get the client IP address and user-agent.

What did you see instead?

nothing.

无论如何,只是一些想法。我对net/rpc不是特别熟悉。大多数时候当我需要类似功能时,我使用的是gRPC和protobuf。

更多关于Golang中如何通过net/rpc获取客户端的真实IP的实战系列教程也可以访问 https://www.itying.com/category-94-b0.html


在Go的net/rpc包中,确实没有直接的方法在RPC方法中获取当前连接的net.Conn。不过,可以通过自定义编解码器来实现这个需求。下面是一个完整的示例:

package main

import (
    "encoding/gob"
    "fmt"
    "io"
    "net"
    "net/rpc"
    "sync"
)

// 自定义连接结构,包装net.Conn并存储客户端IP
type wrappedConn struct {
    net.Conn
    clientIP string
}

// 自定义RPC请求结构
type request struct {
    ClientIP string
    ServiceMethod string
    Seq uint64
    Next interface{}
}

// 自定义编解码器
type serverCodec struct {
    dec *gob.Decoder
    enc *gob.Encoder
    c   *wrappedConn
    mu  sync.Mutex
    pending map[uint64]*request
}

func newServerCodec(conn net.Conn) *serverCodec {
    wrapped := &wrappedConn{
        Conn: conn,
        clientIP: conn.RemoteAddr().String(),
    }
    
    return &serverCodec{
        dec: gob.NewDecoder(conn),
        enc: gob.NewEncoder(conn),
        c:   wrapped,
        pending: make(map[uint64]*request),
    }
}

func (c *serverCodec) ReadRequestHeader(r *rpc.Request) error {
    var req request
    if err := c.dec.Decode(&req); err != nil {
        return err
    }
    
    r.ServiceMethod = req.ServiceMethod
    r.Seq = req.Seq
    
    // 存储请求,以便在ReadRequestBody中获取客户端IP
    c.mu.Lock()
    c.pending[r.Seq] = &req
    c.mu.Unlock()
    
    return nil
}

func (c *serverCodec) ReadRequestBody(body interface{}) error {
    if body == nil {
        return nil
    }
    
    // 从pending中获取当前请求的客户端IP
    var req *request
    c.mu.Lock()
    // 这里需要知道当前的Seq,但net/rpc没有直接提供
    // 我们可以通过其他方式传递,这里简化处理
    for _, r := range c.pending {
        req = r
        break
    }
    c.mu.Unlock()
    
    if req != nil {
        // 将客户端IP注入到body中(如果body有相应字段)
        if withIP, ok := body.(interface{ SetClientIP(string) }); ok {
            withIP.SetClientIP(req.ClientIP)
        }
        if withConn, ok := body.(interface{ SetConn(*wrappedConn) }); ok {
            withConn.SetConn(c.c)
        }
    }
    
    return c.dec.Decode(body)
}

func (c *serverCodec) WriteResponse(r *rpc.Response, body interface{}) error {
    c.mu.Lock()
    delete(c.pending, r.Seq)
    c.mu.Unlock()
    
    if err := c.enc.Encode(r); err != nil {
        return err
    }
    return c.enc.Encode(body)
}

func (c *serverCodec) Close() error {
    return c.c.Close()
}

// 客户端编解码器
type clientCodec struct {
    dec *gob.Decoder
    enc *gob.Encoder
    c   io.ReadWriteCloser
    mu  sync.Mutex
    pending map[uint64]string
}

func newClientCodec(conn io.ReadWriteCloser) *clientCodec {
    return &clientCodec{
        dec: gob.NewDecoder(conn),
        enc: gob.NewEncoder(conn),
        c:   conn,
        pending: make(map[uint64]string),
    }
}

func (c *clientCodec) WriteRequest(r *rpc.Request, body interface{}) error {
    // 在请求头中发送客户端IP
    req := request{
        ClientIP: "真实IP地址", // 这里可以设置真实的客户端IP
        ServiceMethod: r.ServiceMethod,
        Seq: r.Seq,
        Next: body,
    }
    
    c.mu.Lock()
    c.pending[r.Seq] = req.ClientIP
    c.mu.Unlock()
    
    if err := c.enc.Encode(req); err != nil {
        return err
    }
    return c.enc.Encode(body)
}

func (c *clientCodec) ReadResponseHeader(r *rpc.Response) error {
    return c.dec.Decode(r)
}

func (c *clientCodec) ReadResponseBody(body interface{}) error {
    if body == nil {
        return nil
    }
    return c.dec.Decode(body)
}

func (c *clientCodec) Close() error {
    return c.c.Close()
}

// 服务端实现
type MathService struct {
    connections map[string]*wrappedConn
    mu sync.RWMutex
}

type Args struct {
    A, B int
    clientIP string
    conn *wrappedConn
}

func (a *Args) SetClientIP(ip string) {
    a.clientIP = ip
}

func (a *Args) SetConn(conn *wrappedConn) {
    a.conn = conn
}

type Reply struct {
    Result int
}

func (m *MathService) Multiply(args *Args, reply *Reply) error {
    fmt.Printf("客户端真实IP: %s\n", args.clientIP)
    fmt.Printf("连接信息: %v\n", args.conn.RemoteAddr())
    
    // 存储连接以便后续使用
    m.mu.Lock()
    if m.connections == nil {
        m.connections = make(map[string]*wrappedConn)
    }
    m.connections[args.clientIP] = args.conn
    m.mu.Unlock()
    
    reply.Result = args.A * args.B
    return nil
}

// 向特定客户端发送消息的方法
func (m *MathService) SendToClient(ip string, message string) error {
    m.mu.RLock()
    conn, exists := m.connections[ip]
    m.mu.RUnlock()
    
    if !exists {
        return fmt.Errorf("客户端 %s 未连接", ip)
    }
    
    // 这里可以通过conn向客户端发送数据
    _, err := conn.Write([]byte(message))
    return err
}

func main() {
    // 创建服务实例
    mathService := &MathService{}
    rpc.Register(mathService)
    
    // 启动TCP监听
    listener, err := net.Listen("tcp", "0.0.0.0:1234")
    if err != nil {
        fmt.Println("Error starting server:", err)
        return
    }
    defer listener.Close()
    
    fmt.Println("Server listening on :1234")
    
    for {
        conn, err := listener.Accept()
        if err != nil {
            fmt.Println("Error accepting connection:", err)
            continue
        }
        
        // 使用自定义编解码器
        codec := newServerCodec(conn)
        go rpc.ServeCodec(codec)
    }
}

客户端代码:

package main

import (
    "fmt"
    "net/rpc"
)

type Args struct {
    A, B int
}

type Reply struct {
    Result int
}

func main() {
    // 连接到服务器
    conn, err := net.Dial("tcp", "127.0.0.1:1234")
    if err != nil {
        fmt.Println("Error connecting to server:", err)
        return
    }
    defer conn.Close()
    
    // 使用自定义编解码器
    client := rpc.NewClientWithCodec(newClientCodec(conn))
    defer client.Close()
    
    // 准备参数
    args := &Args{A: 5, B: 3}
    
    // 调用远程方法
    var reply Reply
    err = client.Call("MathService.Multiply", args, &reply)
    if err != nil {
        fmt.Println("Error calling Multiply:", err)
        return
    }
    
    fmt.Printf("Result: %d\n", reply.Result)
}

这个实现的关键点:

  1. 自定义wrappedConn结构包装原始连接,可以存储客户端真实IP
  2. 自定义serverCodecclientCodec编解码器,在RPC消息中传递客户端IP信息
  3. Args结构中添加SetClientIPSetConn方法,用于接收服务器端注入的连接信息
  4. 服务器端维护连接映射,可以通过IP地址找到对应的连接进行双向通信

这样你就可以在RPC方法中获取到客户端的真实IP和当前的net.Conn,并实现向特定客户端发送消息的功能。

回到顶部