التسمية التلقائية لمآخذ توصيل مخطط البيانات المحلية AF_UNIX؟
سؤال
أقوم بتنفيذ خدمة بسيطة باستخدام مخططات البيانات عبر مآخذ توصيل يونكس المحلية (عائلة عناوين AF_UNIX، أي. ليس UDP).يرتبط الخادم بعنوان عام، ويتلقى الطلبات بشكل جيد.لسوء الحظ، عندما يتعلق الأمر بالرد، sendto
يفشل إلا إذا كان العميل ملزمًا أيضًا.(الخطأ الشائع هو Transport endpoint is not connected
).
يعمل الارتباط ببعض الأسماء العشوائية (المعتمدة على نظام الملفات أو الملخص).لكني أود تجنب ذلك:من أنا حتى أضمن أن الأسماء التي اخترتها لن تتصادم؟
تخبرنا وثائق وضع الدفق الخاصة بمآخذ توصيل يونكس أنه سيتم تعيين اسم مجرد لها في connect
الوقت إذا لم يكن لديهم واحدة بالفعل.هل هذه الميزة متاحة للمآخذ الموجهة نحو مخطط البيانات؟
المحلول
أفترض أنك تستخدم نظام التشغيل Linux؛لا أعرف ما إذا كانت هذه النصيحة تنطبق على SunOS أو أي نظام UNIX.
أولا الجواب :بعد المقبس () وقبل الاتصال () أو الإرسال الأول () حاول إضافة هذا الكود:
struct sockaddr_un me;
me.sun_family = AF_UNIX;
int result = bind(fd, (void*)&me, sizeof(short));
والآن الشرح:ولل يونيكس(7) صفحة الرجل تقول هذا:
عند توصيل المقبس ولا يحتوي بالفعل على عنوان محلي ، سيتم إنشاء عنوان فريد في مساحة الاسم المجردة تلقائيًا.
للأسف، صفحة الرجل تكمن.
فحص كود مصدر لينكس, ، نرى أن unix_dgram_connect() يستدعي unix_autobind() فقط إذا تم تعيين SOCK_PASSCRED في إشارات المقبس.نظرًا لأنني لا أعرف ما هو SOCK_PASSCRED، والساعة الآن 1:00 صباحًا، أحتاج إلى البحث عن حل آخر.
فحص unix_bind, ، لاحظت أن unix_bind يستدعي unix_autobind إذا كان الحجم الذي تم تمريره يساوي "sizeof(short)".وهكذا الحل أعلاه.
حظا سعيدا، وصباح الخير.
روب
نصائح أخرى
ال يونيكس(7) تحتوي صفحة الدليل التي أشرت إليها على هذه المعلومات حول مآخذ توصيل UNIX التلقائية:
إذا حدد استدعاء bind(2) addrlen كـ sizeof(sa_family_t)، أو تم تحديد خيار مأخذ التوصيل SO_PASSCRED لمأخذ توصيل لم يكن مرتبطًا بشكل صريح بعنوان، فسيتم ربط المقبس تلقائيًا بعنوان مجرد.
ولهذا السبب تتحقق نواة Linux من أن طول العنوان يساوي sizeof(short) لأن sa_family_t قصير.تقول صفحة دليل unix(7) الأخرى التي تمت الإشارة إليها بواسطة إجابة Rob الرائعة أن مآخذ توصيل العميل تكون دائمًا مرتبطة تلقائيًا عند الاتصال، ولكن نظرًا لأن مآخذ توصيل SOCK_DGRAM غير متصلة (على الرغم من الاتصال بالاتصال عليها) أعتقد أن هذا ينطبق فقط على مآخذ توصيل 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).
رد متأخر بعض الشيء، ولكن لمن يجد هذا باستخدام جوجل كما فعلت.ساعدتني إجابة Rob Adam في الحصول على الإجابة "الحقيقية" لهذا:ببساطة استخدم set (level 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