Python : 바인딩 소켓 :“이미 사용중인 주소”


83

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가 문제를 해결할 수없는 것 같습니다. 감사합니다!


1
클라이언트에 특정 포트가 필요한 이유는 무엇입니까?
AJ.

2
프로덕션 서버에 넣어야하기 때문에 해당 서버에서 모든 나가는 연결이 차단됩니다. 연결이 통과 할 수 있도록 방화벽에 규칙을 설정할 수 있도록 소켓에 대한 특정 포트를 지정해야합니다.
Tu Hoang 2011 년

3
네트워크 관리자는 아웃 바운드 트래픽이 대상 포트 로 제어 될 수 있음을 이해해야합니다 .
AJ.

7
이것은 충분한 정보를 가지고 있습니다. 문제가 TIME_WAIT소켓 상태로 인해 발생할 가능성이 99 %입니다. 아래 답변에 대한 해결책이 있습니다. :)
lunixbochs

1
운영 체제는 무엇입니까? 일반적으로 netstat를 사용하여 소켓의 상태를 볼 수 있습니다 (소켓을 식별하기 위해 포트 번호를
찾으십시오

답변:


124

SO_REUSEADDR소켓을 바인딩하기 전에 소켓 옵션을 사용해보십시오 .

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

편집 : 여전히 문제가있는 것 같습니다. SO_REUSEADDR작동하지 않는 경우 가 있습니다. 소켓을 바인드하고 동일한 대상 ( SO_REUSEADDR활성화 된 상태)에 다시 연결하려고하면TIME_WAIT 하면 여전히 유효합니다. 그러나 다른 호스트 : 포트에 연결할 수 있습니다.

몇 가지 해결책이 떠 오릅니다. 다시 연결할 수있을 때까지 계속 재 시도 할 수 있습니다. 또는 클라이언트가 (서버가 아닌) 소켓 닫기를 시작하면 마술처럼 작동합니다.


1
여전히 재사용 할 수 없습니다. 동일한 포트를 재사용하기 전에 기다려야하는 시간은 1 분 30 초입니다. (
Tu Hoang

5
setsockopt전에 전화 했어 bind? 로 만든 첫 번째 소켓 SO_REUSEADDR입니까, 아니면 실패한 소켓 입니까? SO_REUSEADDR이것이 작동 하려면 대기 소켓이 설정되어 있어야합니다.
lunixbochs 2011 년

예, comSocket.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)을 포함했지만 여전히 작동하지 않습니다.
Tu Hoang 2011 년

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

1
@jcoffland 나는 그가 사용해서는 안된다는 데 동의 bind()하지만 SO_REUSEADDR은 TCP 서버 소켓뿐만 아니라 TCP 클라이언트 소켓 및 UDP 소켓을 포함한 모든 소켓에 적용됩니다. 여기에 잘못된 정보를 게시하지 마십시오.
user207421

26

다음은 내가 테스트 한 완전한 코드이며 절대적으로 "이미 사용중인 주소"오류를 표시하지 않습니다. 이를 파일에 저장하고 제공하려는 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

httpd.shutdown () 후에도 모든 리소스를 완전히 해제하려면 httpd.server_close ()를 호출하고 싶습니다.
Androbin 2018-01-05

또한 예상치 못한 존재에 대해 atexit 모듈 사용을 고려하십시오.
Androbin 2018 년


13

이 링크 에 따르면

실제로 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) 튜플)이 어떤 포트에 바인딩되어 있으며 소켓 현재 상태는 무엇인지 쉽게 확인할 수 있습니다 : TIME_WAIT, CLOSING, FIN_WAIT 등.

Linux 네트워크 구성에 대한 정말 좋은 설명은 /server/212093/how-to-reduce-number-of-sockets-in-time-wait 에서 찾을 수 있습니다 .


또한 코드에주의해야합니다. 코드가 아직 개발 중이고 일부 예외가 발생하면 특히 서버 측에서 연결이 제대로 닫히지 않을 수 있습니다.
Rustem K

8
Tom 's Network Guide 에서 복사하여 붙여 넣은 문장을 공평하게 인용해야합니다 . 답을 정정하십시오.
HelloWorld

8

바인딩하기 전에 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()

이것은 플래그를 설정하기 전에 서버가 바인딩되는 것을 방지합니다.


TCPServer를 하위 클래스로 만들고 속성을 재정의하는 것이 훨씬 쉬운 것 같습니다. 예를 들어 내 대답 을 참조하십시오
Andrei

3

Felipe Cruze가 언급했듯이 바인딩하기 전에 SO_REUSEADDR을 설정해야합니다. 다른 사이트에서 솔루션을 찾았습니다-다른 사이트의 솔루션, 아래에 재현 됨

문제는 주소가 소켓에 바인딩되기 전에 SO_REUSEADDR 소켓 옵션을 설정해야한다는 것입니다. ThreadingTCPServer를 서브 클래 싱하고 다음과 같이 server_bind 메서드를 재정 의하여이를 수행 할 수 있습니다.

import SocketServer, 소켓

클래스 MyThreadingTCPServer (SocketServer.ThreadingTCPServer) :
    def server_bind (self) :
        self.socket.setsockopt (socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.bind (self.server_address)


3

이 예외에 대한 또 다른 이유를 찾았습니다. Spyder IDE (제 경우에는 Raspbian의 Spyder3)에서 응용 프로그램을 실행하고 프로그램이 ^ 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이 true로 설정되면 항상 잘 작동했습니다. 새로운 Thonny IDE 또는 Idle3 IDE에는이 문제가 없었습니다.



1

나에게 더 나은 해결책은 다음과 같습니다. 연결 종료 이니셔티브는 서버에 의해 수행 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 

마침내 솔루션을 사용하여 OS가 포트 자체를 선택하도록 한 다음 선례가 여전히 TIME_WAIT에 있으면 다른 포트가 사용됩니다.

나는 교체했다 :

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

와:

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

tcp 주소 의 파이썬 소켓 문서 에 표시된대로 :

제공된 경우 source_address는 소켓이 연결하기 전에 소스 주소로 바인드 할 2- 튜플 (호스트, 포트)이어야합니다. 호스트 또는 포트가 각각 ''또는 0이면 OS 기본 동작이 사용됩니다.


1
바인드를 생략 할 수도 있지만 OP는 포트 5555를 사용해야한다고 명시하지만이 답변은 그렇지 않습니다.
user207421

1

물론 개발 환경에서 또 다른 해결책은이를 사용하는 프로세스를 죽이는 것입니다.

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

프로세스를 종료해도 TIME_WAIT에는 영향을주지 않습니다.
user207421

0

이미 답변을 수락했지만 문제는 클라이언트 소켓에서 bind () 호출과 관련이 있다고 생각합니다. 이것은 괜찮을 수 있지만 bind ()와 shutdown ()은 함께 잘 작동하지 않는 것 같습니다. 또한 SO_REUSEADDR은 일반적으로 청취 소켓과 함께 사용됩니다. 즉, 서버 측에서.

connect ()에 ip / port를 전달해야합니다. 이렇게 :

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

bind ()를 호출하지 말고 SO_REUSEADDR을 설정하지 마십시오.


2
bind()서로와 shutdown()는 전혀 관련이 없으며 '함께 잘 어울리지 않는 것 같다'는 제안은 근거가 없다. 당신은 그가 로컬 포트 5555를 사용하는 데 필요한 부분을 놓친
user207421

0

나는 가장 좋은 방법은 터미널에 입력하여, 해당 포트에서 프로세스를 종료하는 단지 생각 fuser -k [PORT NUMBER]/tcp, 예를 fuser -k 5001/tcp.


1
프로세스가 이미 종료되고 OS에 의해 정리되도록 포트를 열어 두지 않는 한.
Chris Merck

1
프로세스를 종료해도 TIME_WAIT에는 영향을주지 않습니다.
user207421
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.