class MyHttpHandler(BaseHTTPRequestHandler):
def do_GET(self):
print "do get"
self.send_response(code=200)
self.end_headers()
self.wfile.write("hello world")
if __name__ == '__main__':
serv = TCPServer(('', 20001), MyHttpHandler)
serv.serve_forever()
上面這段代碼,運行,client訪問多次后關閉,再啟動,會報一個socket.error: [Errno 48] Address already in use的錯。lsof -i:20001 沒能找到任何進程占用端口,一個乍一看很迷的錯誤,記錄下,怕以后忘掉。
解決方案很簡單,增加TCPServer.allow_reuse_address = True。具體起作用的為socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)這。
原因如下。操作系統的網絡棧會非常謹慎的處理連接的關閉,僅僅用于監(jiān)聽的服務器套接字是可以立即關閉并操作系統忽略的,但是對于實際與客戶端進行通信的連接套接字就不行了。即使客戶端和服務器都關閉了連接并向對方發(fā)從了FIN數據包,連接套接字也無法立即取消。為什么呢?因為即使網絡棧發(fā)送了最后一個數據包將套接字關閉,也還是無法確認該數據包是否可以被接收。如果數據包正好被網絡丟棄了,那么另一方無法得知該數據包長時間無法傳達的原因,可能會重新發(fā)送FIN數據包,希望能收到響應。
操作系統對上述問題的解決方案為,一個應用程序任務某個TCP連接最終關閉了,操作系統的網絡棧實際上會在一個等待狀態(tài)中將該連接的記錄保存最多4分鐘。RFC將這些狀態(tài)命名為CLOSE-WAIT 和TIME-WAIT,當關閉的套接字還處于其中某一狀態(tài)時,任何最終的FIN數據包都是可以得到適當響應的。
因此,當服務器試圖聲明某個幾分鐘前運行的連接所使用的端口時,實際上是在試圖聲明從某種意義上仍在使用的端口。所以就報錯了~
而SO_REUSEADDR可以指明應用程序能夠使用一些網絡客戶端之前的連接正在關閉的端口。