it-swarm.asia

Python: Binding Socket: "العنوان قيد الاستخدام بالفعل"

لدي سؤال بخصوص مقبس العميل على شبكة TCP/IP. دعنا نقول أنا استخدامها

try:

    comSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    comSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

except socket.error, msg:

    sys.stderr.write("[ERROR] %s\n" % msg[1])
    sys.exit(1)

try:
    comSocket.bind(('', 5555))

    comSocket.connect()

except socket.error, msg:

    sys.stderr.write("[ERROR] %s\n" % msg[1])

    sys.exit(2)

سيتم ربط مأخذ التوصيل المنفذ 5555. المشكلة هي أنه بعد إنهاء الاتصال

comSocket.shutdown(1)
comSocket.close()

باستخدام wireshark ، أرى المقبس مغلقًا بـ FIN و ACK و ACK من كلا الجانبين ، ولا يمكنني استخدام المنفذ مرة أخرى. أحصل على الخطأ التالية:

[ERROR] Address already in use

أتساءل كيف يمكنني مسح المنفذ على الفور بحيث لا يزال يمكنني استخدام نفس المنفذ في المرة القادمة.

comSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

setsockopt لا يبدو أن تكون قادرة على حل المشكلة شكرا لك!

63
Tu Hoang

حاول استخدام خيار مأخذ التوصيل SO_REUSEADDR قبل ربط المقبس.

comSocket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

تحرير: أرى أنك لا تزال تواجه مشكلة في هذا. هناك حالة حيث SO_REUSEADDR لن تعمل. إذا حاولت ربط مأخذ توصيل وإعادة الاتصال بنفس الوجهة (مع تمكين SO_REUSEADDR) ، فسيظل TIME_WAIT ساري المفعول. ومع ذلك ، سيسمح لك بالاتصال بمضيف مضيف مختلف.

اثنين من الحلول تتبادر إلى الذهن. يمكنك الاستمرار في إعادة المحاولة حتى تتمكن من الحصول على اتصال مرة أخرى. أو إذا بدأ العميل إغلاق المقبس (وليس الخادم) ، فيجب أن يعمل بطريقة سحرية.

101
Bryan

إليك الرمز الكامل الذي قمت باختباره ولا يمنحني على الإطلاق خطأ "العنوان قيد الاستخدام بالفعل". يمكنك حفظ هذا في ملف وتشغيل الملف من داخل الدليل الأساسي لملفات HTML التي تريد عرضها. بالإضافة إلى ذلك ، يمكنك تغيير الدلائل برمجياً قبل بدء تشغيل الخادم

import socket
import SimpleHTTPServer
import SocketServer
# import os # uncomment if you want to change directories within the program

PORT = 8000

# Absolutely essential!  This ensures that socket resuse is setup BEFORE
# it is bound.  Will avoid the TIME_WAIT issue

class MyTCPServer(SocketServer.TCPServer):
    def server_bind(self):
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind(self.server_address)

Handler = SimpleHTTPServer.SimpleHTTPRequestHandler

httpd = MyTCPServer(("", PORT), Handler)

# os.chdir("/My/Webpages/Live/here.html")

httpd.serve_forever()

# httpd.shutdown() # If you want to programmatically shut off the server
23
user2543899

في الواقع ، يمكن أن تؤدي علامة SO_REUSEADDR إلى عواقب أكبر بكثير: تسمح لك SO_REUSADDR باستخدام منفذ عالق في TIME_WAIT ، لكن لا يزال يتعذر عليك استخدام هذا المنفذ لتأسيس اتصال بالمكان الأخير المتصل به. ماذا؟ افترض أنني اخترت المنفذ المحلي 1010 ، وقمت بالاتصال بـ foobar.com ، المنفذ 300 ، ثم أغلقه محليًا ، تاركًا ذلك المنفذ في TIME_WAIT. يمكنني إعادة استخدام المنفذ المحلي 1010 على الفور للاتصال بأي مكان باستثناء منفذ foobar.com 300.

ومع ذلك ، يمكنك تجنب حالة TIME_WAIT تمامًا من خلال ضمان أن الطرف البعيد يبدأ الإغلاق (حدث قريب). لذلك يمكن أن يتجنب الخادم المشاكل عن طريق ترك العميل يغلق أولاً. يجب تصميم بروتوكول التطبيق حتى يعرف العميل موعد إغلاقه. يمكن أن يغلق الخادم بأمان ردًا على EOF من العميل ، ومع ذلك فسوف يحتاج أيضًا إلى تعيين مهلة عندما يتوقع EOF في حالة ترك العميل الشبكة دون جدوى . في كثير من الحالات ، يكون الانتظار لبضع ثوانٍ قبل إغلاق الخادم مناسبًا.

كما أنصحك بمعرفة المزيد عن الشبكات وبرمجة الشبكات. يجب عليك الآن على الأقل كيفية عمل بروتوكول tcp. البروتوكول تافه للغاية وصغير وبالتالي قد يوفر لك الكثير من الوقت في المستقبل.

باستخدام أمر netstat ، يمكنك بسهولة معرفة أي البرامج ((program_name ، pid) Tuple) ترتبط بأي منافذ وما هي حالة مأخذ التوصيل الحالية: TIME_WAIT ، CLOSING ، FIN_WAIT وما إلى ذلك.

يمكن العثور على شرح جيد لتكوينات شبكة لينكس https://serverfault.com/questions/212093/how-to-reduce-number-of-sockets-in-time-wait .

12
Rustem K

تحتاج إلى تعيين allow_reuse_address قبل الربط. بدلاً من SimpleHTTPServer ، قم بتشغيل هذا المقتطف:

Handler = SimpleHTTPServer.SimpleHTTPRequestHandler
httpd = SocketServer.TCPServer(("", PORT), Handler, bind_and_activate=False)
httpd.allow_reuse_address = True
httpd.server_bind()
httpd.server_activate()
httpd.serve_forever()

هذا يمنع الخادم من الربط قبل أن نحصل على فرصة لتعيين العلامات.

6
Vukasin Toroman

في حال واجهتك المشكلة باستخدام TCPServer أو SimpleHTTPServer ، تجاوز SocketServer.TCPServer.allow_reuse_address (python 2.7.x) أو socketserver.TCPServer.allow_reuse_address (python 3.x)

class MyServer(SocketServer.TCPServer):
    allow_reuse_address = True

server = MyServer((Host, PORT), MyHandler)
server.serve_forever()
6
Andrei

كما ذكر Felipe Cruze ، يجب عليك تعيين SO_REUSEADDR قبل الربط. لقد وجدت حلاً في موقع آخر - حل في موقع آخر ، مستنسخ أدناه

المشكلة هي أنه يجب تعيين خيار مأخذ توصيل SO_REUSEADDR قبل ربط العنوان بالمأخذ. يمكن القيام بذلك عن طريق التصنيف الفرعي ThreadingTCPServer وتجاوز طريقة server_bind كما يلي:

 استيراد SocketServer ، مأخذ التوصيل 
 
 فئة MyThreadingTCPServer (SocketServer.ThreadingTCPServer): 
 def server_bind (self): 
 self.socket.setsockopt (socket) .SOL_SOCKET ، socket.O_REUSEADDR ، 1) 
 self.socket.bind (self.server_address) 
 
3
Maurice Flanagan

يجب تشغيل socket.socket() قبل socket.bind() واستخدام REUSEADDR كما قال

2
Felipe Cruz

بالنسبة لي كان الحل الأفضل هو التالي. نظرًا لأن مبادرة إغلاق الاتصال تم تنفيذها بواسطة الخادم ، فإن setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) لم يكن له أي تأثير وكان TIME_WAIT يتجنب اتصال جديد على نفس المنفذ مع وجود خطأ:

[Errno 10048]: Address already in use. Only one usage of each socket address (protocol/IP address/port) is normally permitted 

لقد استخدمت الحل أخيرًا للسماح لنظام التشغيل باختيار المنفذ نفسه ، ثم يتم استخدام منفذ آخر إذا كانت السابقة لا تزال في TIME_WAIT.

أنا محل:

self._socket.bind((guest, port))

مع:

self._socket.bind((guest, 0))

كما هو موضح في وثائق مأخذ التوصيل بيثون لعنوان tcp:

إذا تم توفيره ، يجب أن يكون source_address عبارة عن مكون من طبقتين (مضيف ، منفذ) للمقبس لربطه كعنوان مصدر له قبل الاتصال. إذا كان المضيف أو المنفذ "أو" 0 على التوالي ، فسيتم استخدام السلوك الافتراضي لنظام التشغيل.

2
user2455528

حل آخر ، في بيئة التطوير بالطبع ، هو قتل عملية استخدامه ، على سبيل المثال

def serve():
    server = HTTPServer(('', PORT_NUMBER), BaseHTTPRequestHandler)
    print 'Started httpserver on port ' , PORT_NUMBER
    server.serve_forever()
try:
    serve()
except Exception, e:
    print "probably port is used. killing processes using given port %d, %s"%(PORT_NUMBER,e)
    os.system("xterm -e 'Sudo fuser -kuv %d/tcp'" % PORT_NUMBER)
    serve()
    raise e
1
test30

أعلم أنك قبلت بالفعل إجابة ولكني أعتقد أن المشكلة تتعلق بالاتصال bind () على مقبس العميل. قد يكون هذا جيدًا ولكن الربط () والإغلاق () لا يبدو أنه يعمل جيدًا معًا. أيضًا ، يتم استخدام SO_REUSEADDR عمومًا مع مآخذ الاستماع. أي من جانب الخادم.

يجب أن تمر و ip/port للاتصال (). مثله:

comSocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
comSocket.connect(('', 5555))

لا تتصل bind () ، لا تقم بتعيين SO_REUSEADDR.

1
jcoffland

لقد وجدت سببًا آخر لهذا الاستثناء. عند تشغيل التطبيق من Spyder IDE (في حالتي كان Spyder3 على Raspbian) وتم إنهاء البرنامج بواسطة ^ C أو استثناء ، كان المقبس نشطًا:

Sudo netstat -ap | grep 31416
tcp  0  0 0.0.0.0:31416  0.0.0.0:*    LISTEN      13210/python3

تشغيل البرنامج مرة أخرى وجدت "العنوان قيد الاستخدام بالفعل" ؛ يبدو أن IDE يبدأ "التشغيل" الجديد كعملية منفصلة تعثر على مأخذ التوصيل المستخدم من قبل "التشغيل" السابق.

socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)

لم يساعد.

قتل عملية 13210 ساعد. بدء البرنامج النصي بيثون من سطر الأوامر مثل

python3 <app-name>.py

تعمل دائمًا بشكل جيد عندما تم ضبط SO_REUSEADDR على "صحيح". لم يواجه Thonny IDE أو Idle3 IDE هذه المشكلة.

1
peets

أعتقد أن أفضل طريقة هي فقط إنهاء العملية على ذلك المنفذ ، عن طريق الكتابة في الجهاز الطرفي fuser -k [PORT NUMBER]/tcp ، على سبيل المثال fuser -k 5001/tcp.

0
Kiblawi_Rabee