已连接的Unix SOCK_DGRAM套接字

时间:2018-09-07 02:59:29

标签: sockets datagram unix-socket

我已经按照this answer中的代码创建了一对程序,它们通过Unix套接字发送和接收数据报。

这很尴尬:在创建第一个套接字的那一侧(即“服务器”),我不能使用对sendrecvread或{ {1}},因为未设置目标(这些呼叫失败,并显示“需要目标地址”错误。

我尝试通过向write添加初始调用并使用从那里返回的地址来解决此问题,但是它从来没有正确的值(至少在OSX上)。由于我们不知道客户地址,因此使用recvfrom也不起作用。

我的工作方式大致遵循以下过程:

  1. 启动服务器程序,其中:
    1. 调用sendtosocket创建服务器套接字。
    2. 它在这里等。
  2. 启动客户端程序,该程序:
    1. 调用bindsocket创建客户端套接字。
    2. 它知道服务器套接字的路径,并调用bind
    3. 此面现已正确设置。
  3. 服务器程序:
    1. 通过stdin接受客户端套接字的路径
    2. 将路径复制到connect并使用它来调用struct sockaddr_un(如链接的答案中一样)。

这很尴尬!如果我使用connect套接字进行此操作,则可以使用SOCK_STREAMlisten;无需服务器知道客户端的套接字路径,流就更直接了。

是否有一种更优雅的方式来连接这些插座?

3 个答案:

答案 0 :(得分:0)

SOCK_DGRAM(UDP)套接字是“无连接”的,因此您不能“连接”这两个套接字。它们仅将数据包发送到指定的目标地址,而客户端仅捕获它。因此,您首先要决定要使用SOCK_DGRAM(UDP)还是SOCK_STREAM(TCP)。

如果您使用的是UDP套接字,则客户端套接字不需要connect,只需在创建和绑定后sendto即可访问目标地址(在本例中为Server)。

因此,如果需要专用的连接,最好使用TCP套接字。或者,如果您是通过互联网使用的,则可以找到的最接近UDP的是Hole punching

答案 1 :(得分:0)

解决问题的一种方法:

您的消息可能具有相同的标头。 在标题中添加发件人的地址信息。

然后您的服务器可以使用sendto来响应正确的客户端。

伪示例:

void handle_my_message(const my_message_t *msg)
{
   struct sockaddr_un client_address = msg->header.sender;
   my_message_response_t response_msg;  

   ... handle the message and fill the response...

   // Send response message
   sendto(fd, &response_msg, sizeof(response_msg), 0,
          (struct sockaddr*)&client_address, sizeof(client_address));   
}

通过这种方式,您的服务器程序无需保留连接记录。

您可能应该使用更小更便携的格式,而不是标题中的struct sockaddr_un,可以将其转换为struct sockaddr_un

答案 2 :(得分:0)

您还应该bind将客户端套接字插入地址。如果客户端套接字已绑定(即具有其自己的名称),则不需要带外机制将客户端的地址传达给服务器。操作系统将其与每个数据报一起发送。

客户端的示例代码(在python中,因为它快速且易于创建原型-应该易于转换为等效的C):

#!/usr/bin/env python3
import os
import socket

server_addr = "/tmp/ux_server"
client_addr = "/tmp/ux_client"
if os.path.exists(client_addr):
    os.remove(client_addr)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.bind(client_addr)
for n in range(5):
    data = "Hello " + str(n)
    data = data.encode()
    print("Sent '{}' to {}".format(data, server_addr))
    sock.sendto(data, server_addr)
    data, addr = sock.recvfrom(16000)
    print("Got '{}' back from {}".format(data, addr))

此外,您可以在客户端执行connect。由于它是一个数据报套接字,因此实际上并没有在两者之间建立连接,但是它确实修复了服务器端点的地址,从而使您无需在每次发送时都提供服务器地址(即您可以使用简单的send而不是sendto)。

为完整起见,这是与上述内容相对应的回显服务器:

#!/usr/bin/env python3
import os
import socket

server_addr = "/tmp/ux_server"
if os.path.exists(server_addr):
    # Bind will fail if endpoint exists
    os.remove(server_addr)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.bind(server_addr)
while True:
    data, addr = sock.recvfrom(16000)
    print("Got '{}' from {}".format(data, addr))
    sock.sendto(data, addr)

编辑

嗯...我现在看到您说您已经bind正在客户端套接字,然后connect正在服务器端。但这意味着您只需要让服务器最初使用recvfrom 一次即可获得客户的地址。操作系统随同发送地址,而您无需使用带外机制。

连接套接字的不利之处在于,如果客户端发生故障,除非服务器尝试发送,否则服务器将不会知道,但是客户端将无法重新连接,因为服务器的套接字已连接。这就是为什么数据报服务器通常对所有消息都使用recvfromsendto

更新后的服务器,其初始recvfrom后跟connect

#!/usr/bin/env python3
import os
import socket

server_addr = "/tmp/ux_server"
if os.path.exists(server_addr):
    # Bind will fail if endpoint exists
    os.remove(server_addr)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.bind(server_addr)
client_addr = None
while True:
    if client_addr:
        data = sock.recv(16000)
    else:
        data, client_addr = sock.recvfrom(16000)
        sock.connect(client_addr)
    print("Got '{}' from {}".format(data, client_addr))
    sock.send(data)

具有连接的套接字的更新的客户端。

#!/usr/bin/env python3
import os
import socket

server_addr = "/tmp/ux_server"
client_addr = "/tmp/ux_client"
if os.path.exists(client_addr):
    os.remove(client_addr)
sock = socket.socket(socket.AF_UNIX, socket.SOCK_DGRAM)
sock.bind(client_addr)
sock.connect(server_addr)
for n in range(5):
    data = ("Hello " + str(n)).encode()
    print("Sent '{}'".format(data))
    sock.send(data)
    data = sock.recv(16000)
    print("Got '{}' back".format(data))