V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
sdenvi
V2EX  ›  问与答

请教一个关于 netty 的问题,关于代理和请求转发

  •  
  •   sdenvi · 2023-07-03 15:41:45 +08:00 · 1110 次点击
    这是一个创建于 512 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近做一个 netty 项目,使用 TCP 协议通信,要求是服务端接收到从客户端发出的请求后,将请求的 client IP 和 remote IP 重新修改后转发给远程接口,因为远程接口进行了 client IP 的强校验,另外每个 client IP 对应的远程接口 remote IP 也不一定一样,请问使用 netty 可以达到这种目的吗,或者说有没有什么别的方式可以实现?

    12 条回复    2023-07-03 22:48:11 +08:00
    assiadamo
        1
    assiadamo  
       2023-07-03 15:57:46 +08:00
    netty 自带 proxy 的例子看看行不行
    https://github.com/netty/netty/tree/4.1/example/src/main/java/io/netty/example/proxy
    这种小功能感觉用 java 太重,可以用 golang 试试
    assiadamo
        2
    assiadamo  
       2023-07-03 16:05:14 +08:00
    golang 随便抄了一个,就这么简单、
    ```
    package main

    import (
    "flag"
    "fmt"
    "io"
    "log"
    "net"
    )

    var localAddr *string = flag.String("l", "localhost:9999", "local address")
    var remoteAddr *string = flag.String("r", "localhost:80", "remote address")

    func main() {
    flag.Parse()
    fmt.Printf("Listening: %v\nProxying: %v\n\n", *localAddr, *remoteAddr)

    listener, err := net.Listen("tcp", *localAddr)
    if err != nil {
    panic(err)
    }
    for {
    conn, err := listener.Accept()
    log.Println("New connection", conn.RemoteAddr())
    if err != nil {
    log.Println("error accepting connection", err)
    continue
    }
    go func() {
    defer conn.Close()
    conn2, err := net.Dial("tcp", *remoteAddr)
    if err != nil {
    log.Println("error dialing remote addr", err)
    return
    }
    defer conn2.Close()
    closer := make(chan struct{}, 2)
    go copy(closer, conn2, conn)
    go copy(closer, conn, conn2)
    <-closer
    log.Println("Connection complete", conn.RemoteAddr())
    }()
    }
    }

    func copy(closer chan struct{}, dst io.Writer, src io.Reader) {
    _, _ = io.Copy(dst, src)
    closer <- struct{}{} // connection is closed, send signal to stop proxy
    }
    ```
    sdenvi
        3
    sdenvi  
    OP
       2023-07-03 16:06:01 +08:00
    @assiadamo 感谢回复,研究一下看看行不行,在做调研时候也是考虑使用 golang , 被否了,公司要求不能使用 Java 之外的语言
    zzzkkk
        4
    zzzkkk  
       2023-07-03 16:34:53 +08:00 via Android
    @assiadamo
    <-closer 收到一个 struct 就会退出
    虽然 close 分配了两个空间
    是不是有问题?
    assiadamo
        5
    assiadamo  
       2023-07-03 18:33:00 +08:00
    @zzzkkk
    在这种情况下,`<-closer` 将会阻塞,直到从 `closer` 通道接收到一个值。

    在 `make(chan struct{}, 2)` 中,我们创建了一个带有缓冲大小为 2 的无缓冲通道。这意味着可以向 `closer` 通道发送两个值,而不会立即阻塞。只有当 `closer` 通道的缓冲区已满时,发送操作才会阻塞。

    在 `closer <- struct{}{}` 这一行中,我们向 `closer` 通道发送一个值。如果缓冲区仍有空间,该操作会成功并立即返回。如果缓冲区已满,发送操作将会阻塞,直到有空间可用。

    在 `<-closer` 这一行中,我们从 `closer` 通道接收一个值。如果通道中有值可用,该操作会成功并立即返回。如果通道为空,接收操作将会阻塞,直到有值可用。

    因此,在你提供的代码中,`<-closer` 将会阻塞,直到从 `closer` 通道接收到一个值。这可以用于等待某些操作完成或信号传递。

    希望这解答了你的疑问。如果还有进一步的问题,请随时提问。
    oldshensheep
        6
    oldshensheep  
       2023-07-03 18:41:14 +08:00
    vertx 非常简单,几行代码吧,
    public class TcpProxyServer extends AbstractVerticle {

    private static final int LOCAL_PORT = 8888;
    private static final String REMOTE_HOST = "1.1.1.1";
    private static final int REMOTE_PORT = 80;

    @Override
    public void start() {
    NetServer server = vertx.createNetServer();

    server.connectHandler(clientSocket -> {

    vertx.createNetClient().connect(REMOTE_PORT, REMOTE_HOST, targetSocket -> {
    if (!targetSocket.succeeded()) {
    // ?
    }
    var remoteSocket = targetSocket.result();
    remoteSocket.pipeTo(clientSocket);
    clientSocket.pipeTo(clientSocket);
    });
    });
    server.listen(LOCAL_PORT);
    }
    }
    oldshensheep
        7
    oldshensheep  
       2023-07-03 18:45:41 +08:00
    修正一下应该是这样的:
    remoteSocket.pipeTo(clientSocket);
    clientSocket.pipeTo(remoteSocket);
    其他的错误处理,连接关闭代码我没写
    zzzkkk
        8
    zzzkkk  
       2023-07-03 18:55:31 +08:00 via Android
    @assiadamo
    需要两次接收才对
    因为只接受一次的话 主程序就退出了 另一个 go 携程可能还没执行完
    sdenvi
        9
    sdenvi  
    OP
       2023-07-03 21:47:29 +08:00
    @oldshensheep 没有具体使用过,多问一句,看代码是直接指定了 local IP 和 remote IP ,能够在客户端发送请求的时候从数据流里面拦截到信息之后,通过 ID 从数据库里面读取到 local IP 和 remote IP 重新建立 TCP 请求发送到后端服务器吗?
    sdenvi
        10
    sdenvi  
    OP
       2023-07-03 21:55:10 +08:00
    @oldshensheep 类似 netty ,道连接成功触发 channelActive ,数据传输时触发 channelRead
    oldshensheep
        11
    oldshensheep  
       2023-07-03 22:12:33 +08:00
    可以的,在客户端建立连接后,客户端发个 ID ,然后服务端读取 ID 查数据库得到 remote IP ,再建立连接也行。
    大概这样吧
    public void start() {
    NetServer server = vertx.createNetServer();

    server.connectHandler(clientSocket -> {
    String[] remote = { "" };

    clientSocket.handler(b -> {
    var id = b.getBytes(0, 1);
    // 查数据库的到 remote IP
    remote[0] = "1.1.1.1";
    });

    vertx.createNetClient().connect(REMOTE_PORT, remote[0], targetSocket -> {
    if (!targetSocket.succeeded()) {
    // ?
    }
    var remoteSocket = targetSocket.result();
    remoteSocket.pipeTo(clientSocket);
    clientSocket.pipeTo(remoteSocket);
    });
    });
    server.listen(LOCAL_PORT);
    }
    sdenvi
        12
    sdenvi  
    OP
       2023-07-03 22:48:11 +08:00
    @oldshensheep 想最终达到的目的就是类似 nat 的功能:客户端 ---> server <---> client ---> remote app 这样的效果,客户端和 TCP 的 server 端建立连接后读取数据流,从数据流里面获取到用户 ID ,拿到 ID 后发送给 TCP 的 client 端,client 端根据 ID 查询到保存到数据库的 client IP 和 remote IP 后重新进行 socket bind ,达到让 remote app 通过 IP 校验。如果只是 proxy 的话直接做转发就行,不用额外的处理,涉及到重新绑定四元组,一时想不到好的解决方案了
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   5486 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 29ms · UTC 07:40 · PVG 15:40 · LAX 23:40 · JFK 02:40
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.