GO套接字API与POSIX套接字API之间有何关联?

huangapple go评论66阅读模式
英文:

How does the GO socket API correlate to the POSIX socket API

问题

我正在学习使用Go语言中的套接字(sockets),但是我对于为什么Go的API与已有的套接字API(例如C语言)如此不同感到有些困惑。我对C语言中的套接字非常熟悉,并希望能够在Go中利用一些我的知识。

具体而言,我希望在我的应用程序中使用已连接的UDP套接字。连接UDP套接字的好处是将来自连接客户端的所有传入流量重定向到特定的套接字

如果有助于理解,这是我希望在Go中实现的“流程”(我过去在C和C#中已经实现过):

  1. 客户端向已知的IP:PORT发送UDP数据包。
  2. 当在该套接字上接收到数据包时,可以确定客户端尚未建立UDP“连接”,因为数据包将会到达“已连接”套接字(连接是我的软件的实现细节)。
  3. 创建一个新的套接字,使用与原始套接字相同的端口,通过bind()connect()将其连接到客户端的远程端点。为此,需要使用SO_REUSEADDR选项。
  4. 以后来自客户端的所有数据包都将在新创建的套接字中接收。

这种方法的优点是:
1)客户端只需要与一个IP:PORT通信。
2)不需要在每个UDP数据包中包含“会话ID”或类似的机制。
3)操作系统负责将数据报分派到正确的套接字,使应用程序能够依赖于操作系统。

因此,在Go语言中(我在过去几天中已经开始喜欢的一种语言),套接字API与我预期的相当不同。我已经能够监听UDP数据报,但是当客户端首次与服务器通信时,我在创建新套接字(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)
    < -- 在拨号/连接之前需要设置SO_REUSEADDR -- >
    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_REUSEADDR

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

谢谢!

英文:

I am learning sockets in go, and I am a bit confused as to why the API is so different from the established socket api (in C for example). I know how to use sockets in C quite comfortably, and was hoping to leverage some of my knowledge in GO.

Specifically, I am wanting to use connected UDP sockets in my application. Connecting a UDP socket has the added benefit of redirecting all incoming traffic from the connected client to that specific socket.

If it helps to understand, here is the "flow" of what I'd like to accomplish in GO (and have accomplished in C and C# in the past):

  1. Client sends a UDP packet to a known IP:PORT
  2. When a packet is received on this socket, it is known that the client has not established a UDP "connection" because the packet would have arrived at the "connected" socket instead (the connection is an implementation detail of my software)
  3. A new socket is created, bind(), and connect() to the client's remote endpoint using the same port as the original socket. For this, SO_REUSEADDR is required.
  4. All future packets from the client are received in the newly created socket.

The advantages to this approach are:

  1. The client only ever needs to communicate with one IP:PORT
  2. No need to include a "session ID" or similar mechanism in each UDP packet
  3. The OS takes care of dispatching datagrams to the correct socket allowing the application to rely on the OS.

So in GO (a language that I have grown to love over the last few days) the socket API is quite different from what I was expecting. I have been able to listen for dgrams over UDP, but my difficulty arises when trying to create a new socket (UDPConn??) when a client first communicates with the server.

My code:

buf := make([]byte, 32)
laddr, _ := net.ResolveUDPAddr(&quot;udp&quot;, 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(), &quot;udp&quot;, ep.address)
if err != nil {
    //TODO log error
}
for {
    buf := make([]byte, 32)
    l, raddr, _ := conn.ReadFrom(buf)
    laddr, _ := net.ResolveUDPAddr(&quot;udp&quot;, ep.address)
    &lt;-- NEED TO SET SO_REUSEADDR BEFORE DIAL/CONNECT --&gt;
    net.DialUDP(&quot;udp&quot;, laddr, raddr.(*net.UDPAddr))
    fmt.Println(&quot;Message from: &quot;, raddr.String(), &quot;Reads: &quot;, string(buf[0:l]))
}

So my thinking was that dial is equivalent to connect(). I'm not sure I'm correct, but regardless, the issue now is that I can't set socket options when calling dial. I've probably wrongly assumed that dial() will connect(), but I don't see any other way of:

  1. Creating a UDP socket
  2. Binding it
  3. Connecting it to a remote client

Output from strace when running the above:

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(&quot;127.0.0.1&quot;)}, 16) = 0
getsockname(3, {sa_family=AF_INET, sin_port=htons(6555), sin_addr=inet_addr(&quot;127.0.0.1&quot;)}, [112-&gt;16]) = 0
recvfrom(3, 0xc000118040, 32, 0, 0xc00007f6f8, [112]) = -1 EAGAIN (Resource temporarily unavailable)
recvfrom(3, &quot;hi\n&quot;, 32, 0, {sa_family=AF_INET, sin_port=htons(36682), sin_addr=inet_addr(&quot;127.0.0.1&quot;)}, [112-&gt;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(&quot;127.0.0.1&quot;)}, 16) = -1 EADDRINUSE (Address already in use)

As you can see, the second socket never gets created because I have to set SO_RESUSEADDR.

My question:
How can one create a socket, set SO_REUSEADDR, bind() it and connect() it to a specific endpoint.

Thanks!

答案1

得分: 1

我认为Go API应该能很好地适应你的用例,而无需手动管理一切。

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

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

英文:

I think the go api should fit nicely with your use case without the need to manually manage everything.

I think you need a net.ListenUDP call to open your listening socket.

Upon receipt of a packet, call net.DialUDP to create the client connection.

答案2

得分: 0

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

syscall中的Socket可能是一个选项。

示例代码

    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
	}

希望这能帮到你!

英文:

> How can one create a socket, set SO_REUSEADDR, bind() it and connect() it to a specific endpoint.

The Socket of syscall could be one option.

Sample codes

    import (
       . &quot;syscall&quot;
    )

	var clientsock int
	var serveraddr SockaddrInet4
	var err error

	if clientsock, err = Socket(AF_INET, SOCK_DGRAM, IPPROTO_IP); err != nil {
		fmt.Println(&quot;socket error:&quot;, err.Error())
		return
	}
	if err := SetsockoptInt(clientsock, SOL_SOCKET, SO_REUSEADDR, 1); err != nil {
		fmt.Printf(&quot;reuse addr error: %v\n&quot;, err)
		return
	}

	defer Shutdown(clientsock, SHUT_RDWR)

	if err := Bind(clientsock, &amp;SockaddrInet4{Port: 8888}); err != nil {
		fmt.Printf(&quot;bind error: %v\n&quot;, err)
		return
	}

	serveraddr.Addr = [4]byte{127, 0, 0, 1}

	if err = Connect(clientsock, &amp;serveraddr); err != nil {
		fmt.Println(&quot;connect error:&quot;, err.Error())
		return
	}

huangapple
  • 本文由 发表于 2022年9月27日 05:32:58
  • 转载请务必保留本文链接:https://go.coder-hub.com/73860172.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定