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()
上面這段代碼惑折,運(yùn)行,client訪問多次后關(guān)閉氛琢,再啟動(dòng)产艾,會(huì)報(bào)一個(gè)socket.error: [Errno 48] Address already in use
的錯(cuò)疤剑。lsof -i:20001
沒能找到任何進(jìn)程占用端口,一個(gè)乍一看很迷的錯(cuò)誤闷堡,記錄下隘膘,怕以后忘掉。
解決方案很簡(jiǎn)單杠览,增加TCPServer.allow_reuse_address = True
弯菊。具體起作用的為socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
這。
原因如下踱阿。操作系統(tǒng)的網(wǎng)絡(luò)棧會(huì)非常謹(jǐn)慎的處理連接的關(guān)閉管钳,僅僅用于監(jiān)聽的服務(wù)器套接字是可以立即關(guān)閉并操作系統(tǒng)忽略的钦铁,但是對(duì)于實(shí)際與客戶端進(jìn)行通信的連接套接字就不行了。即使客戶端和服務(wù)器都關(guān)閉了連接并向?qū)Ψ桨l(fā)從了FIN數(shù)據(jù)包才漆,連接套接字也無法立即取消牛曹。為什么呢?因?yàn)榧词咕W(wǎng)絡(luò)棧發(fā)送了最后一個(gè)數(shù)據(jù)包將套接字關(guān)閉醇滥,也還是無法確認(rèn)該數(shù)據(jù)包是否可以被接收黎比。如果數(shù)據(jù)包正好被網(wǎng)絡(luò)丟棄了,那么另一方無法得知該數(shù)據(jù)包長時(shí)間無法傳達(dá)的原因鸳玩,可能會(huì)重新發(fā)送FIN數(shù)據(jù)包阅虫,希望能收到響應(yīng)。
操作系統(tǒng)對(duì)上述問題的解決方案為不跟,一個(gè)應(yīng)用程序任務(wù)某個(gè)TCP連接最終關(guān)閉了颓帝,操作系統(tǒng)的網(wǎng)絡(luò)棧實(shí)際上會(huì)在一個(gè)等待狀態(tài)中將該連接的記錄保存最多4分鐘。RFC將這些狀態(tài)命名為CLOSE-WAIT 和TIME-WAIT躬拢,當(dāng)關(guān)閉的套接字還處于其中某一狀態(tài)時(shí)躲履,任何最終的FIN數(shù)據(jù)包都是可以得到適當(dāng)響應(yīng)的。
因此聊闯,當(dāng)服務(wù)器試圖聲明某個(gè)幾分鐘前運(yùn)行的連接所使用的端口時(shí),實(shí)際上是在試圖聲明從某種意義上仍在使用的端口米诉。所以就報(bào)錯(cuò)了~
而SO_REUSEADDR可以指明應(yīng)用程序能夠使用一些網(wǎng)絡(luò)客戶端之前的連接正在關(guān)閉的端口菱蔬。