英文:
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#中已经实现过):
- 客户端向已知的
IP:PORT
发送UDP数据包。 - 当在该套接字上接收到数据包时,可以确定客户端尚未建立UDP“连接”,因为数据包将会到达“已连接”套接字(连接是我的软件的实现细节)。
- 创建一个新的套接字,使用与原始套接字相同的端口,通过
bind()
和connect()
将其连接到客户端的远程端点。为此,需要使用SO_REUSEADDR
选项。 - 以后来自客户端的所有数据包都将在新创建的套接字中接收。
这种方法的优点是:
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_REUSEADDR
,bind()
它,并将其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):
- Client sends a UDP packet to a known
IP:PORT
- 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)
- A new socket is created,
bind()
, andconnect()
to the client's remote endpoint using the same port as the original socket. For this, SO_REUSEADDR is required. - All future packets from the client are received in the newly created socket.
The advantages to this approach are:
- The client only ever needs to communicate with one IP:PORT
- No need to include a "session ID" or similar mechanism in each UDP packet
- 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("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]))
}
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:
- Creating a UDP socket
- Binding it
- 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("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)
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)到特定的端点。
示例代码
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 (
. "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
}
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论