来自 TCP 套接字的 HTTP 服务器(在 Go 中)

HTTP server from TCP socket (in Go)

我正在尝试在 Go 中创建一个 TCP 套接字,将其绑定到 VRF interface 并在该特定接口中建立一个 HTTP 服务器。 VRF 绑定工作正常,但启动 HTTP 服务器 returns 时出现错误 "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:])
}

任何解决此问题的建议都将不胜感激。谢谢!

正如 C Han 在评论中提到的那样:您需要倾听。 创建服务器的顺序是创建套接字,绑定到 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)
}

您可以使用 net.ListenConfig 在调用 syscall.Bind 之前注入您想要的套接字选项。这也确保套接字设置正确完成,并且与 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
}