Python: Binding Socket: “Địa chỉ đã được sử dụng”


81

Tôi có một câu hỏi liên quan đến ổ cắm máy khách trên mạng TCP / IP. Giả sử tôi sử dụng

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)

Ổ cắm được tạo sẽ liên kết với cổng 5555. Vấn đề là sau khi kết thúc kết nối

comSocket.shutdown(1)
comSocket.close()

Đang dùng wirehark thì thấy socket đóng FIN, ACK và ACK từ 2 phía, không sử dụng lại được cổng. Tôi nhận được lỗi sau đây:

[ERROR] Address already in use

Tôi tự hỏi làm thế nào tôi có thể xóa cổng ngay lập tức để lần sau tôi vẫn có thể sử dụng cùng cổng đó.

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

setsockopt dường như không thể giải quyết được sự cố Cảm ơn bạn!


1
Tại sao khách hàng cần một cổng cụ thể?
AJ.

2
Bởi vì tôi phải đặt nó vào một máy chủ sản xuất, và trong máy chủ đó, tất cả các kết nối gửi đi đều bị chặn. Tôi cần chỉ định một cổng cụ thể cho ổ cắm để chúng có thể thiết lập quy tắc trên tường lửa cho phép kết nối đi qua.
Tu Hoang

3
Quản trị viên mạng của bạn nên hiểu rằng lưu lượng ra ngoài có thể được kiểm soát bởi cổng đích .
AJ.

7
điều này có đủ thông tin. có một cơ hội 99% vấn đề là do TIME_WAITtình trạng ổ cắm, mà câu trả lời dưới đây có một giải pháp cho :)
lunixbochs

1
hệ điều hành của bạn là gì? bạn thường có thể sử dụng netstat để xem trạng thái của một ổ cắm (nhìn cho số cổng để xác định các ổ cắm)
lunixbochs

Câu trả lời:


122

Hãy thử sử dụng SO_REUSEADDRtùy chọn ổ cắm trước khi gắn ổ cắm.

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

Chỉnh sửa: Tôi thấy bạn vẫn gặp sự cố với điều này. Có một trường hợp SO_REUSEADDRsẽ không hoạt động. Nếu bạn cố gắng liên kết một ổ cắm và kết nối lại với cùng một đích ( SO_REUSEADDRđã bật), thì TIME_WAITnó vẫn có hiệu lực. Tuy nhiên, nó sẽ cho phép bạn kết nối với một máy chủ khác: cổng.

Một số giải pháp được đưa ra trong tâm trí. Bạn có thể tiếp tục thử lại cho đến khi bạn có thể kết nối lại. Hoặc nếu máy khách bắt đầu đóng socket (không phải máy chủ), thì nó sẽ hoạt động một cách kỳ diệu.


1
Vẫn không thể sử dụng lại nó. Thời gian tôi phải chờ trước khi có thể sử dụng lại cùng một cổng là 1 phút 30 giây :(
Tu Hoang

5
bạn đã gọi setsockopttrước đây bind? ổ cắm đầu tiên được tạo bằng SO_REUSEADDRhay chỉ ổ cắm bị lỗi? ổ cắm chờ phải SO_REUSEADDRthiết lập cho việc này đến công việc
lunixbochs

Có, tôi đã bao gồm comSocket.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) nhưng vẫn không hoạt động.
Tu Hoang

1
tcp 0 0 98c5-9-134-71-1: freeciv mobile-166-132-02: 2345 TIME_WAIT
Tu Hoang

1
@jcoffland Tôi đồng ý rằng anh ấy không nên sử dụng bind(), nhưng SO_REUSEADDR dành cho tất cả các ổ cắm bao gồm không chỉ ổ cắm máy chủ TCP mà còn cả ổ cắm máy khách TCP và ổ cắm UDP. Đừng đăng thông tin sai lệch ở đây.
Marquis of Lorne,

26

Đây là mã hoàn chỉnh mà tôi đã kiểm tra và hoàn toàn KHÔNG cung cấp cho tôi lỗi "địa chỉ đã được sử dụng". Bạn có thể lưu tệp này vào một tệp và chạy tệp từ trong thư mục cơ sở của tệp HTML mà bạn muốn phân phát. Ngoài ra, bạn có thể lập trình thay đổi các thư mục trước khi khởi động máy chủ

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

Ngay cả sau httpd.shutdown (), bạn vẫn muốn gọi httpd.server_close () để giải phóng toàn bộ tài nguyên.
Androbin

Ngoài ra, hãy xem xét sử dụng mô-đun atexit nếu có bất kỳ tồn tại bất ngờ nào.
Androbin


13

Theo liên kết này

Trên thực tế, cờ SO_REUSEADDR có thể dẫn đến hậu quả lớn hơn nhiều: SO_REUSADDR cho phép bạn sử dụng một cổng bị kẹt trong TIME_WAIT, nhưng bạn vẫn không thể sử dụng cổng đó để thiết lập kết nối đến nơi cuối cùng mà nó kết nối. Gì? Giả sử tôi chọn cổng cục bộ 1010 và kết nối với cổng 300 của foobar.com, rồi đóng cục bộ, rời khỏi cổng đó sau TIME_WAIT. Tôi có thể sử dụng lại cổng cục bộ 1010 ngay lập tức để kết nối với mọi nơi ngoại trừ cổng 300 của foobar.com.

Tuy nhiên, bạn hoàn toàn có thể tránh trạng thái TIME_WAIT bằng cách đảm bảo rằng đầu cuối từ xa bắt đầu quá trình đóng (sự kiện đóng). Vì vậy, máy chủ có thể tránh sự cố bằng cách để máy khách đóng trước. Giao thức ứng dụng phải được thiết kế để máy khách biết khi nào cần đóng. Máy chủ có thể đóng một cách an toàn để đáp ứng với EOF từ máy khách, tuy nhiên nó cũng sẽ cần đặt thời gian chờ khi nó đang mong đợi EOF trong trường hợp máy khách đã rời khỏi mạng một cách vô duyên. Trong nhiều trường hợp, chỉ cần đợi một vài giây trước khi máy chủ đóng là đủ.

Tôi cũng khuyên bạn nên tìm hiểu thêm về mạng và lập trình mạng. Bây giờ bạn nên biết ít nhất cách thức hoạt động của giao thức tcp. Giao thức này khá nhỏ và nhỏ, do đó, có thể giúp bạn tiết kiệm rất nhiều thời gian trong tương lai.

Với netstatlệnh, bạn có thể dễ dàng xem chương trình nào ((tên chương trình, pid) tuple) được liên kết với cổng nào và trạng thái hiện tại của ổ cắm là gì: TIME_WAIT, CLOSING, FIN_WAIT, v.v.

Bạn có thể tìm thấy giải thích thực sự tốt về các cấu hình mạng linux /server/212093/how-to-reduce-number-of-sockets-in-time-wait .


Ngoài ra, bạn nên cẩn thận với mã của mình. Nếu mã của bạn vẫn đang được phát triển và một số ngoại lệ xảy ra, kết nối có thể không được đóng đúng cách, đặc biệt là từ phía máy chủ.
Rustem K

8
Bạn nên công bằng và trích dẫn những câu bạn sao chép và dán từ Hướng dẫn mạng của Tom . Vui lòng sửa câu trả lời của bạn.
HelloWorld

8

Bạn cần đặt allow_reuse_address trước khi ràng buộc. Thay vì SimpleHTTPServer, hãy chạy đoạn mã này:

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()

Điều này ngăn không cho máy chủ liên kết trước khi chúng tôi có cơ hội đặt cờ.


Có vẻ dễ dàng hơn nhiều để phân lớp TCPServer và ghi đè thuộc tính, hãy xem câu trả lời của tôi chẳng hạn
Andrei

3

Như Felipe Cruze đã đề cập, bạn phải đặt SO_REUSEADDR trước khi ràng buộc. Tôi đã tìm thấy giải pháp trên một trang web khác - giải pháp trên trang web khác, được sao chép bên dưới

Vấn đề là tùy chọn ổ cắm SO_REUSEADDR phải được đặt trước khi địa chỉ được liên kết với ổ cắm. Điều này có thể được thực hiện bằng cách phân lớp con ThreadingTCPServer và ghi đè phương thức server_bind như sau:

nhập SocketServer, socket

lớp MyThreadingTCPServer (SocketServer.ThreadingTCPServer):
    def server_bind (self):
        self.socket.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind (self.server_address)


3

Tôi tìm thấy một lý do khác cho ngoại lệ này. Khi chạy ứng dụng từ Spyder IDE (trong trường hợp của tôi, đó là Spyder3 trên Raspbian) và chương trình bị ^ C hoặc một ngoại lệ kết thúc, ổ cắm vẫn hoạt động:

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

Chạy chương trình lại thấy thông báo "Địa chỉ đã được sử dụng"; IDE dường như bắt đầu "lần chạy" mới như một quá trình riêng biệt tìm ra ổ cắm được sử dụng bởi "lần chạy" trước đó.

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

đã không giúp được gì.

Quá trình giết chết 13210 đã hữu ích. Bắt đầu tập lệnh python từ dòng lệnh như

python3 <app-name>.py

luôn hoạt động tốt khi SO_REUSEADDR được đặt thành true. Thonny IDE hoặc Idle3 IDE mới không gặp sự cố này.



1

Đối với tôi, giải pháp tốt hơn là sau đây. Vì chủ động đóng kết nối được thực hiện bởi máy chủ, điều setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)này không có tác dụng và TIME_WAIT đang tránh kết nối mới trên cùng một cổng bị lỗi:

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

Cuối cùng tôi đã sử dụng giải pháp để hệ điều hành tự chọn cổng, sau đó cổng khác được sử dụng nếu tiền lệ vẫn là TIME_WAIT.

Tôi đã thay thế:

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

với:

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

Như nó đã được chỉ ra trong tài liệu ổ cắm python của địa chỉ tcp:

Nếu được cung cấp, địa chỉ source_address phải là một bộ 2 (máy chủ, cổng) để socket liên kết làm địa chỉ nguồn trước khi kết nối. Nếu máy chủ hoặc cổng tương ứng là '' hoặc 0, hành vi mặc định của hệ điều hành sẽ được sử dụng.


1
Bạn cũng có thể bỏ qua ràng buộc, nhưng OP nói rằng anh ta phải sử dụng cổng 5555 và câu trả lời này không.
Marquis of Lorne,

1

một giải pháp khác, tất nhiên, trong môi trường phát triển, đang giết chết quá trình sử dụng nó, chẳng hạn

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

Quá trình giết không ảnh hưởng đến TIME_WAIT theo bất kỳ cách nào.
Marquis of Lorne,

0

Tôi biết bạn đã chấp nhận một câu trả lời nhưng tôi tin rằng vấn đề liên quan đến việc gọi bind () trên một ổ cắm máy khách. Điều này có thể ổn nhưng bind () và shutdown () dường như không hoạt động tốt với nhau. Ngoài ra, SO_REUSEADDR thường được sử dụng với các ổ cắm lắng nghe. tức là ở phía máy chủ.

Bạn nên chuyển và ip / cổng để kết nối (). Như thế này:

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

Không gọi bind (), không đặt SO_REUSEADDR.


2
bind()shutdown()hoàn toàn không liên quan gì đến nhau, và gợi ý rằng 'họ có vẻ không chơi tốt với nhau' là vô căn cứ. Bạn đã bỏ lỡ phần mà anh ấy cần sử dụng cổng địa phương 5555.
Marquis of Lorne,

0

Tôi nghĩ rằng cách tốt nhất là chỉ cần loại bỏ quá trình trên cổng đó, bằng cách nhập vào thiết bị đầu cuối fuser -k [PORT NUMBER]/tcp, ví dụ fuser -k 5001/tcp.


1
Trừ khi tiến trình đã bị tắt và nó chỉ để cổng mở để được hệ điều hành dọn dẹp.
Chris Merck

1
Quá trình giết không ảnh hưởng đến TIME_WAIT theo bất kỳ cách nào.
Marquis of Lorne,
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.