【问题标题】:How does the GO socket API correlate to the POSIX socket APIGO 套接字 API 与 POSIX 套接字 API 的关系如何
【发布时间】:2022-09-27 20:40:33
【问题描述】:

我正在学习套接字,我有点困惑为什么 API 与已建立的套接字 API 如此不同(例如在 C 中)。我知道如何在 C 中轻松使用套接字,并希望利用我在 GO 中的一些知识。

具体来说,我想在我的应用程序中使用连接的 UDP 套接字。连接 UDP 套接字的额外好处是将来自已连接客户端的所有传入流量重定向到那个特定的套接字.

如果它有助于理解,这里是我想在 GO 中完成的“流程”(过去曾在 C 和 C# 中完成):

  1. 客户端发送一个UDP数据包到一个已知的IP:PORT
  2. 当在此套接字上接收到数据包时,就知道客户端尚未建立 UDP“连接”,因为数据包将到达“已连接”套接字(连接是一个实现细节我的软件)
  3. 使用同一个端口作为原来的插座。为此,需要 SO_REUSEADDR。
  4. 来自客户端的所有未来数据包都在新创建的插座。

    这种方法的优点是:

    1. 客户端只需要与一个 IP:PORT 通信
    2. 无需在每个 UDP 数据包中包含“会话 ID”或类似机制
    3. 操作系统负责将数据报分派到正确的套接字,从而允许应用程序依赖操作系统。

      所以在 GO(过去几天我越来越喜欢的一种语言)中,套接字 API 与我的预期完全不同。我已经能够通过 UDP 侦听 dgram,但是当客户端首次与服务器通信时尝试创建新套接字 (UDPConn??) 时出现了困难。

      我的代码:

      buf := make([]byte, 32)
      laddr, _ := net.ResolveUDPAddr(\"udp\", ep.address)
      var lc = net.ListenConfig{
                  Control: func(network, address string, c syscall.RawConn) error {
                      var opErr error
                      if err := c.Control(func(fd uintptr) {
                          opErr = unix.SetsockoptInt(int(fd), unix.SOL_SOCKET, unix.SO_REUSEADDR, 1)
                      }); err != nil {
                          return err
                      }
                      return opErr
                  },
              }
      conn, err := lc.ListenPacket(context.Background(), \"udp\", ep.address)
      if err != nil {
          //TODO log error
      }
      for {
          buf := make([]byte, 32)
          l, raddr, _ := conn.ReadFrom(buf)
          laddr, _ := net.ResolveUDPAddr(\"udp\", ep.address)
          <-- NEED TO SET SO_REUSEADDR BEFORE DIAL/CONNECT -->
          net.DialUDP(\"udp\", laddr, raddr.(*net.UDPAddr))
          fmt.Println(\"Message from: \", raddr.String(), \"Reads: \", string(buf[0:l]))
      }
      

      所以我的想法是dial 等价于connect()。我不确定我是否正确,但无论如何,现在的问题是我在调用dial 时无法设置套接字选项。我可能错误地认为dial()connect(),但我没有看到任何其他方式:

      1. 创建 UDP 套接字
      2. 绑定它
      3. 将其连接到远程客户端

        运行上述程序时来自strace 的输出:

        socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 3
        setsockopt(3, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
        setsockopt(3, SOL_SOCKET, SO_REUSEADDR, [1], 4) = 0
        bind(3, {sa_family=AF_INET, sin_port=htons(6555), sin_addr=inet_addr(\"127.0.0.1\")}, 16) = 0
        getsockname(3, {sa_family=AF_INET, sin_port=htons(6555), sin_addr=inet_addr(\"127.0.0.1\")}, [112->16]) = 0
        recvfrom(3, 0xc000118040, 32, 0, 0xc00007f6f8, [112]) = -1 EAGAIN (Resource temporarily unavailable)
        recvfrom(3, \"hi\\n\", 32, 0, {sa_family=AF_INET, sin_port=htons(36682), sin_addr=inet_addr(\"127.0.0.1\")}, [112->16]) = 3
        socket(AF_INET, SOCK_DGRAM|SOCK_CLOEXEC|SOCK_NONBLOCK, IPPROTO_IP) = 7
        setsockopt(7, SOL_SOCKET, SO_BROADCAST, [1], 4) = 0
        bind(7, {sa_family=AF_INET, sin_port=htons(6555), sin_addr=inet_addr(\"127.0.0.1\")}, 16) = -1 EADDRINUSE (Address already in use)
        

        如您所见,第二个套接字永远不会被创建,因为我必须设置SO_RESUSEADDR

        我的问题: 如何创建一个套接字,设置SO_REUSEADDRbind()connect() 到特定端点。

        谢谢!

    标签: sockets go networking udp


    【解决方案1】:

    我认为 go api 应该非常适合您的用例,而无需手动管理所有内容。

    我认为你需要一个 net.ListenUDP 调用来打开你的监听套接字。

    收到数据包后,调用 net.DialUDP 创建客户端连接。

    【讨论】:

    • 很高兴听到!那么调用dial()时如何设置socket选项呢?请记住,我必须在初始设置 SO_REUSEADDR 以及所有后续套接字。
    • 我相信我已经想通了。我没有使用dial(),而是创建了一个新的Dialer 并设置了controllocalAddress 属性。在control 我可以设置套接字选项。有点复杂,但我相信我会习惯的。谢谢你的帮助!
    【解决方案2】:

    如何创建一个套接字,设置 SO_REUSEADDR,绑定()它并连接()它到一个特定的端点。

    syscallSocket 可能是一种选择。

    示例代码

        import (
           . "syscall"
        )
    
        var clientsock int
        var serveraddr SockaddrInet4
        var err error
    
        if clientsock, err = Socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); err != nil {
            fmt.Println("socket error:", err.Error())
            return
        }
        if err := SetsockoptInt(clientsock, SOL_SOCKET, SO_REUSEADDR, 1); err != nil {
            fmt.Printf("reuse addr error: %v\n", err)
            return
        }
    
        defer Shutdown(clientsock, SHUT_RDWR)
    
        if err := Bind(clientsock, &SockaddrInet4{Port: 8888}); err != nil {
            fmt.Printf("bind error: %v\n", err)
            return
        }
    
        serveraddr.Addr = [4]byte{127, 0, 0, 1}
    
        if err = Connect(clientsock, &serveraddr); err != nil {
            fmt.Println("connect error:", err.Error())
            return
        }
    

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2014-02-10
      • 1970-01-01
      • 1970-01-01
      • 2011-04-30
      • 2010-12-28
      • 1970-01-01
      • 2017-05-14
      • 1970-01-01
      相关资源
      最近更新 更多