Автоматическое присвоение имен локальным сокетам дейтаграмм AF_UNIX?
Вопрос
Я реализую простой сервис, использующий дейтаграммы через локальные сокеты unix (семейство адресов AF_UNIX, т.е. не UDP).Сервер привязан к общедоступному адресу, и он отлично принимает запросы.К сожалению, когда дело доходит до ответа, sendto
завершается неудачей, если только клиент тоже не привязан.(распространенной ошибкой является Transport endpoint is not connected
).
Работает привязка к какому-нибудь случайному имени (основанному на файловой системе или абстрактному).Но я бы хотел избежать этого:кто я такой, чтобы гарантировать, что выбранные мной имена не будут пересекаться?
Документация по потоковому режиму сокетов unix сообщает нам, что абстрактное имя будет присвоено им в connect
время, если у них его еще нет.Доступна ли такая функция для сокетов, ориентированных на дейтаграммы?
Решение
Я предполагаю, что вы используете Linux;Я не знаю, применим ли этот совет к SunOS или любому другому UNIX.
Во-первых, ответ:после socket() и перед connect() или first sendto() попробуйте добавить этот код:
struct sockaddr_un me;
me.sun_family = AF_UNIX;
int result = bind(fd, (void*)&me, sizeof(short));
Теперь объяснение:тот самый unix(7) на странице руководства написано следующее:
Когда сокет подключен и у него еще нет локального адреса, уникальный адрес в абстрактном пространстве имен будет сгенерирован автоматически.
К сожалению, справочная страница лжет.
Изучая Исходный код Linux, мы видим, что unix_dgram_connect() вызывает unix_autobind() только в том случае, если во флагах сокета установлен SOCK_PASSCRED.Поскольку я не знаю, что такое SOCK_PASSCRED, а сейчас 1:00 ночи, мне нужно поискать другое решение.
Исследуя unix_bind (привязка), я замечаю, что unix_bind вызывает unix_autobind, если переданный размер равен "sizeof(short)".Таким образом, решение приведено выше.
Удачи и доброго утра.
Роб
Другие советы
Тот Самый unix(7) справочная страница, на которую я ссылался, содержала следующую информацию об autobind UNIX-сокетах:
Если вызов bind(2) указывает addrlen как sizeof(sa_family_t), или параметр сокета SO_PASSCRED был указан для сокета, который явно не был привязан к адресу, то сокет автоматически привязывается к абстрактному адресу.
Вот почему ядро Linux проверяет, что длина адреса равна sizeof(short), потому что sa_family_t - это short .На другой справочной странице unix (7), на которую ссылается отличный ответ Роба, говорится, что клиентские сокеты всегда автоматически привязаны к connect, но поскольку сокеты SOCK_DGRAM не имеют соединения (несмотря на вызов connect на них) Я считаю, что это применимо только к сокетам SOCK_STREAM.
Также обратите внимание, что при указании ваших собственных абстрактных имен сокетов пространства имен адрес сокета в этом пространстве имен задается дополнительными байтами в sun_path, которые покрываются указанной длиной структуры адресов.
struct sockaddr_un me;
const char name[] = "\0myabstractsocket";
me.sun_family = AF_UNIX;
// size-1 because abstract socket names are not null terminated
memcpy(me.sun_path, name, sizeof(name) - 1);
int result = bind(fd, (void*)&me, sizeof(me.sun_family) + sizeof(name) - 1);
функция sendto() также должна ограничивать длину адреса и не передавать sizeof(sockaddr_un).
Немного запоздалый ответ, но для тех, кто найдет это с помощью Google, как это сделал я.Ответ Роба Адама помог мне получить "реальный" ответ на этот вопрос:просто используйте set (уровень SO_SOCKET
, видеть man 7 unix
) установить SO_PASSCRED
до 1.Нет необходимости в глупой привязке.
Я использовал это в PHP, но в нем нет SO_PASSCRED
определенный (дурацкий PHP).Однако это все еще работает, если вы определяете это сами.На моем компьютере он имеет значение 16, и я считаю, что он будет работать вполне переносимо.
Я не уверен, что полностью понял ваш вопрос, но вот реализация дейтаграммы эхо-сервера, которую я только что написал.Вы можете видеть, что сервер отвечает клиенту по тому же IP / ПОРТУ, с которого он был отправлен.
Вот код
Во-первых, сервер (прослушиватель)
from socket import *
import time
class Listener:
def __init__(self, port):
self.port = port
self.buffer = 102400
def listen(self):
sock = socket(AF_INET, SOCK_DGRAM)
sock.bind(('', self.port))
while 1:
data, addr = sock.recvfrom(self.buffer)
print "Received: " + data
print "sending to %s" % addr[0]
print "sending data %s" % data
time.sleep(0.25)
#print addr # will tell you what IP address the request came from and port
sock.sendto(data, (addr[0], addr[1]))
print "sent"
sock.close()
if __name__ == "__main__":
l = Listener(1975)
l.listen()
И теперь Клиент (отправитель), который получает ответ от Слушателя
from socket import *
from time import sleep
class Sender:
def __init__(self, server):
self.port = 1975
self.server = server
self.buffer = 102400
def sendPacket(self, packet):
sock = socket(AF_INET, SOCK_DGRAM)
sock.settimeout(10.75)
sock.sendto(packet, (self.server, int(self.port)))
while 1:
print "waiting for response"
data, addr = sock.recvfrom(self.buffer)
sock.close()
return data
if __name__ == "__main__":
s = Sender("127.0.0.1")
response = s.sendPacket("Hello, world!")
print response