【问题标题】:HTTP server from TCP socket (in Go)来自 TCP 套接字的 HTTP 服务器(在 Go 中)
【发布时间】:2020-02-21 12:51:24
【问题描述】:

我正在尝试在 Go 中创建一个 TCP 套接字,将其绑定到 VRF interface 并在该特定接口中建立一个 HTTP 服务器。 VRF 绑定可以正常工作,但启动 HTTP 服务器会返回错误声明 “accept tcp 127.0.0.1:80:accept: invalid argument”。我是否正确地假设套接字有某种缺陷并且我创建它是错误的?

以下是重现问题的简化版本。 VRF 部分已被注释掉,因为它不会影响实际问题,但我将其留在这里,因为我试图避免人们告诉我只使用 net.Listen 而不是套接字。 VRF 需要先绑定,然后才能使用,所以很遗憾,net.Listen 不是一个选项。

package main

import (
    "fmt"
    "net"
    "net/http"
    "os"
    "syscall"
)

func main() {
    fd, err := syscall.Socket(syscall.AF_INET, syscall.SOCK_STREAM, syscall.IPPROTO_TCP)
    if err != nil {
        fmt.Printf("Error creating socket: %v", err)
        os.Exit(1)
    }

    // if err = syscall.SetsockoptString(fd, syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, "vrfiface"); err != nil {
    //  fmt.Printf("Error binding to vrf: %v", err)
    //  os.Exit(1)
    // }

    sockAddr := &syscall.SockaddrInet4{
        Port: 80,
        Addr: [4]byte{127, 0, 0, 1},
    }

    if err = syscall.Bind(fd, sockAddr); err != nil {
        fmt.Printf("Error binding to IP and port: %v", err)
        os.Exit(1)
    }

    file := os.NewFile(uintptr(fd), "socketfile")
    if file == nil {
        fmt.Println("Error creating file")
        os.Exit(1)
    }

    listener, err := net.FileListener(file)
    if err != nil {
        fmt.Printf("Error creating a listener: %v", err)
        os.Exit(1)
    }

    http.HandleFunc("/", TestServer)
    if err = http.Serve(listener, nil); err != nil {
        fmt.Printf("Error serving HTTP requests: %v", err)
    }
}

func TestServer(w http.ResponseWriter, r *http.Request) {
    fmt.Fprintf(w, "Test, %s!", r.URL.Path[1:])
}

任何有关解决此问题的指针将不胜感激。谢谢!

【问题讨论】:

  • 现有的网络框架有什么问题可以让你在一分钟内开始?
  • 通常情况下,您需要listen 来处理传入流量。
  • 您确定需要绑定到特定接口吗?在 Linux 中,您很少这样做,因为接口是在路由期间选择的。您可能还应该使用net.ListenConfig 来控制连接设置
  • @JimB:VRF 的重点是每个 VRF 设备都有不同的路由表。甚至可能在同一台机器上的不同虚拟设备上具有相同的 IP 地址,关键是要显式绑定到应该使用的接口,而不是让操作系统随意选择某个接口。
  • 感谢@SteffenUllrich。我假设 ListenConfig 可能是在绑定设备后避免其余套接字设置的方法

标签: linux sockets go network-programming


【解决方案1】:

在调用syscall.Bind 之前,您可以使用net.ListenConfig 来注入您想要的套接字选项。这还可以确保正确完成套接字设置,并且以与 net 包所期望的方式相同的方式完成。

ListenConfig.Control 函数为您提供了一个 syscall.RawConn,您可以在其上使用闭包调用 Control,您可以在其中访问在套接字设置期间使用的原始文件描述符。

func main() {
    lc := net.ListenConfig{Control: controlOnConnSetup}

    ln, err := lc.Listen(context.Background(), "tcp", "127.0.0.1:80")
    if err != nil {
        log.Fatal(err)
    }
    ln.Close()
}

func controlOnConnSetup(network string, address string, c syscall.RawConn) error {
    var operr error
    fn := func(fd uintptr) {
        operr = syscall.SetsockoptString(int(fd), syscall.SOL_SOCKET, syscall.SO_BINDTODEVICE, "vrfiface")
    }
    if err := c.Control(fn); err != nil {
        return err
    }
    if operr != nil {
        return operr
    }
    return nil
}

【讨论】:

  • 我也尝试过类似的方法,但得到 “无法在 syscall.SetsockoptString 的参数中使用 fd (type uintptr) 作为 int 类型”。我的代码也出现此错误,即无法为我编译。
  • @SteffenUllrich:抱歉,忘记了其中的类型转换,我在 macOS 上进行测试,所以使用不同的系统调用。现在应该运行。
  • 谢谢,事后看来,将其转换为 int 是显而易见的。
  • @JimB:顺便说一句,有没有办法将上下文传递给控制函数?我很想从那里通过 context.WithValue 或类似的方式传递 VRF 接口名称。
  • @AjMyyra,这里没有上下文,但您可以围绕任何需要的值生成闭包。
【解决方案2】:

正如C Han 已经在评论中提到的那样:您需要倾听。 创建服务器的顺序是创建socket,绑定IP和端口,调用listen,然后接受传入的连接。如果您忘记listen,那么您将看到您看到的错误。因此:

if err = syscall.Bind(fd, sockAddr); err != nil {
     ...
}

if err = syscall.Listen(fd, 10); err != nil {
    fmt.Printf("Error listening %v", err)
    os.Exit(1)
}

【讨论】:

  • 谢谢!我的错误是假设听众会为我解决这个问题。现在完美运行!
  • @AjMyyra:不过我还是建议您改用approach from JimB,因为它可以简化任务,让您在使用 Go 的所有舒适性的同时仍然能够绑定到设备。
  • 我确实做到了。我标记了您的回复,因为它是对原始问题最清晰的解决方案,但 JimB 的方法绝对是处理此问题的更好方法。
猜你喜欢
  • 1970-01-01
  • 2014-08-27
  • 2014-01-13
  • 1970-01-01
  • 2017-01-27
  • 1970-01-01
  • 2012-06-13
  • 1970-01-01
  • 2019-03-11
相关资源
最近更新 更多