Num01-->TCP通信模型
Test01-->TCP客戶端案例
#! /usr/bin/env python3
# -*- coding:utf-8 -*-
from socket import *
def main():
# 1.創(chuàng)建socket
client_socket = socket(AF_INET, SOCK_STREAM)
# 2.指定服務(wù)器的地址和端口號
server_addr = ('192.168.105.125',8080)
client_socket.connect(server_addr)
print('connect %s success' % str(server_addr))
while True:
# 3.給用戶提示,讓用戶輸入要檢索的資料
send_data = input('>>')
# 退出
if send_data == 'quit':
break
# 向服務(wù)器請求數(shù)據(jù)
client_socket.send(send_data.encode())
client_socket.close()
if __name__ == "__main__":
main()
Test02-->TCP服務(wù)器端案例
TCP服務(wù)器端創(chuàng)建流程如下:
1,socket創(chuàng)建一個套接字
2,bind綁定ip和port
3,listen使套接字變?yōu)榭梢员粍渔溄?br> 4,accept等待客戶端的鏈接
5,recv/send接收/發(fā)送數(shù)據(jù)
#! /usr/bin/env python3
# -*- coding:utf-8 -*-
from socket import *
import time
def main():
# 1.創(chuàng)建socket,stream流式套接字,對應(yīng)tcp
listen_socket = socket(AF_INET, SOCK_STREAM)
# 設(shè)置允許復(fù)用地址,當(dāng)建立連接之后服務(wù)器先關(guān)閉,設(shè)置地址復(fù)用
# 設(shè)置socket層屬性 復(fù)用地址 允許
listen_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 2.綁定端口號
my_addr = ('192.168.105.125', 8080)
#shift + insert
listen_socket.bind(my_addr)
# 3.接聽狀態(tài)
#listen中的black表示已經(jīng)建立鏈接和半鏈接的總數(shù)
#如果當(dāng)前已建立鏈接數(shù)和半鏈接數(shù)以達(dá)到設(shè)定值,那么新客戶端就不會connect成功,而是等待服務(wù)器。直到有鏈接退出。
listen_socket.listen(4)
print('listening...')
# 4.等待客戶端來請求服務(wù)器
while True:
# 接受連接請求,創(chuàng)建新的連接套接字,用于客戶端連通信
connect_socket, client_addr = listen_socket.accept()
# accept默認(rèn)會引起阻塞
# 新創(chuàng)建連接用的socket, 客戶端的地址
# print(connect_socket)
# print(client_addr)
while True:
# tcp recv() 只會返回接收到的數(shù)據(jù)
# 1024表示接受的數(shù)據(jù)長度
recv_data = connect_socket.recv(1024)
if len(recv_data) == 0:
#發(fā)送方關(guān)閉tcp的連接,recv()不會阻塞,而是直接返回''
print('client %s close' % str(client_addr))
time.sleep(5)
break
print('recv: %s' % recv_data.decode('gbk'))
# 用完之后,關(guān)閉新創(chuàng)建的那個connect_socket
connect_socket.close()
if __name__ == "__main__":
main()
Num02-->TCP協(xié)議三次握手
Num03-->TCP協(xié)議四次揮手
Num04-->TCP協(xié)議十種狀態(tài)
當(dāng)一端收到一個FIN,內(nèi)核讓read返回0來通知應(yīng)用層另一端已經(jīng)終止了向本端的數(shù)據(jù)傳送
發(fā)送FIN通常是應(yīng)用層對socket進(jìn)行關(guān)閉的結(jié)果
Num05-->TCP協(xié)議的2MSL問題
加以說明:
1,2MSL即兩倍的MSL,TCP的TIME_WAIT狀態(tài)也稱為2MSL等待狀態(tài)。
2,當(dāng)TCP的一端發(fā)起主動關(guān)閉,在發(fā)出最后一個ACK包后,
3,即第3次握 手完成后發(fā)送了第四次握手的ACK包后就進(jìn)入了TIME_WAIT狀態(tài),
4,必須在此狀態(tài)上停留兩倍的MSL時間,
5,等待2MSL時間主要目的是怕最后一個 ACK包對方?jīng)]收到,
6,那么對方在超時后將重發(fā)第三次握手的FIN包,
7,主動關(guān)閉端接到重發(fā)的FIN包后可以再發(fā)一個ACK應(yīng)答包。
8,在TIME_WAIT狀態(tài) 時兩端的端口不能使用,要等到2MSL時間結(jié)束才可繼續(xù)使用。
9,當(dāng)連接處于2MSL等待階段時任何遲到的報(bào)文段都將被丟棄。
10,不過在實(shí)際應(yīng)用中可以通過設(shè)置 SO_REUSEADDR選項(xiàng)達(dá)到不必等待2MSL時間結(jié)束再使用此端口。
Num06-->TCP協(xié)議長鏈接和短鏈接
TCP在真正的讀寫操作之前,server與client之間必須建立一個連接,
當(dāng)讀寫操作完成后,雙方不再需要這個連接時它們可以釋放這個連接,
連接的建立通過三次握手,釋放則需要四次握手,
所以說每個連接的建立都是需要資源消耗和時間消耗的。
Test01-->長鏈接
1, client 向 server 發(fā)起連接
2,server 接到請求,雙方建立連接
3,client 向 server 發(fā)送消息
4,server 回應(yīng) client
5,一次讀寫完成,連接不關(guān)閉
6,后續(xù)讀寫操作...
7,長時間操作之后client發(fā)起關(guān)閉請求
Test02-->短鏈接
1,client 向 server 發(fā)起連接請求
2,server 接到請求,雙方建立連接
3,client 向 server 發(fā)送消息
4,server 回應(yīng) client
5,一次讀寫完成,此時雙方任何一個都可以發(fā)起 close 操作
Test03-->長鏈接和短鏈接的區(qū)別
長鏈接可以省去較多的TCP建立和關(guān)閉的操作,減少浪費(fèi),節(jié)約時間。
對于頻繁請求資源的客戶來說,較適用長連接。
client與server之間的連接如果一直不關(guān)閉的話,會存在一個問題,
隨著客戶端連接越來越多,server早晚有扛不住的時候,這時候server端需要采取一些策略,
如關(guān)閉一些長時間沒有讀寫事件發(fā)生的連接,這樣可以避免一些惡意連接導(dǎo)致server端服務(wù)受損;
如果條件再允許就可以以客戶端機(jī)器為顆粒度,限制每個客戶端的最大長連接數(shù),這樣可以完全避免某個蛋疼的客戶端連累后端服務(wù)。
短鏈接對于服務(wù)器來說管理較為簡單,存在的連接都是有用的連接,不需要額外的控制手段。但如果客戶請求頻繁,將在TCP的建立和關(guān)閉操作上浪費(fèi)時間和帶寬。
Test04-->TCP長/短鏈接的應(yīng)用場景
長鏈接多用于操作頻繁,點(diǎn)對點(diǎn)的通訊,而且連接數(shù)不能太多情況。
每個TCP連接都需要三次握手,這需要時間,如果每個操作都是先連接,
再操作的話那么處理速度會降低很多,所以每個操作完后都不斷開,
再次處理時直接發(fā)送數(shù)據(jù)包就OK了,不用建立TCP連接。
例如:數(shù)據(jù)庫的連接用長連接,如果用短連接頻繁的通信會造成socket錯誤,而且頻繁的socket 創(chuàng)建也是對資源的浪費(fèi)。
像WEB網(wǎng)站的HTTP服務(wù)一般都用短鏈接,因?yàn)殚L連接對于服務(wù)端來說會耗費(fèi)一定的資源。
像WEB網(wǎng)站這么頻繁的成千上萬甚至上億客戶端的連接,用短連接會更省一些資源;如果用長連接,而且同時有成千上萬的用戶,如果每個用戶都占用一個連接的話,那可想而知吧。雖然并發(fā)量大,但每個用戶無需頻繁操作情況下需用短連好。
Num07-->TCP并發(fā)服務(wù)器--多進(jìn)程實(shí)現(xiàn)
通過為每個客戶端創(chuàng)建一個進(jìn)程的方式,能夠同時為多個客戶端進(jìn)行服務(wù)。當(dāng)客戶端不是特別多的時候,這種方式還行,如果有幾百上千個,就不可取了,因?yàn)槊看蝿?chuàng)建進(jìn)程等過程需要好較大的資源。
#! /usr/bin/env python3
# -*- coding:utf-8 -*-
# @Author : xiaoke
from multiprocessing import Process
from socket import *
# 需要為客戶端提供服務(wù)
def do_service(connect_socket):
while True:
recv_data = connect_socket.recv(1024)
if len(recv_data) == 0:
# 發(fā)送方關(guān)閉tcp的連接,recv()不會阻塞,而是直接返回''
# print('client %s close' % str(client_addr))
# s.getpeername() s.getsockname()
print('client %s close' % str(connect_socket.getpeername()))
break
print('recv: %s' % recv_data.decode('gbk'))
def main():
# 1.創(chuàng)建socket
listen_socket = socket(AF_INET, SOCK_STREAM)
# stream流式套接字,對應(yīng)tcp
# 設(shè)置允許復(fù)用地址,當(dāng)建立連接之后服務(wù)器先關(guān)閉,設(shè)置地址復(fù)用
# 設(shè)置socket層屬性 復(fù)用地址,不用等2msl, 允許
listen_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 2.綁定端口
my_addr = ('192.168.105.125', 8080)
listen_socket.bind(my_addr)
# 3,接聽狀態(tài)
listen_socket.listen(4) # 設(shè)置套接字成監(jiān)聽,4表示一個己連接隊(duì)列長度
print('listening...')
# 4.等待客戶端來請求
# 父進(jìn)程只專注接受連接請求
while True:
# 接受連接請求,創(chuàng)建連接套接字,用于客戶端間通信
connect_socket, client_addr = listen_socket.accept() # accept默認(rèn)會引起阻塞
# 新創(chuàng)建連接用的socket, 客戶端的地址
# print(connect_socket)
print(client_addr)
# 每當(dāng)來新的客戶端連接,創(chuàng)建子進(jìn)程,由子進(jìn)程和客戶端通信
process_do_service = Process(target=do_service, args=(connect_socket,))
process_do_service.start()
# 父進(jìn)程,關(guān)閉connect_socket
connect_socket.close()
if __name__ == "__main__":
main()
Num08-->TCP并發(fā)服務(wù)器--多線程實(shí)現(xiàn)
#! /usr/bin/env python3
# -*- coding:utf-8 -*-
# @Author : xiaoke
from socket import *
from threading import Thread
# 需要為客戶端提供服務(wù)
def do_service(connect_socket):
while True:
recv_data = connect_socket.recv(1024)
if len(recv_data) == 0:
# 發(fā)送方關(guān)閉tcp的連接,recv()不會阻塞,而是直接返回''
# print('client %s close' % str(client_addr))
# s.getpeername() s.getsockname()
print('client %s close' % str(connect_socket.getpeername()))
break
print('recv: %s' % recv_data.decode('gbk'))
def main():
# 1.創(chuàng)建socket
listen_socket = socket(AF_INET, SOCK_STREAM)
# 設(shè)置允許復(fù)用地址,當(dāng)建立連接之后服務(wù)器先關(guān)閉,設(shè)置地址復(fù)用
# 設(shè)置socket層屬性 復(fù)用地址,不用等2msl 允許
listen_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 2.綁定端口
my_addr = ('192.168.105.125', 8080)
# shift + insert
listen_socket.bind(my_addr)
# 3.接聽狀態(tài)
listen_socket.listen(4) # 設(shè)置套接字成監(jiān)聽,4表示一個己連接隊(duì)列長度
print('listening...')
# 4.等待來電話
# 主線程只專注接受連接請求
while True:
# 接受連接請求,創(chuàng)建連接套接字,用于客戶端連通信
connect_socket, client_addr = listen_socket.accept() # accept默認(rèn)會引起阻塞
# 新創(chuàng)建連接用的socket, 客戶端的地址
# print(connect_socket)
print(client_addr)
# 每當(dāng)來新的客戶端連接,創(chuàng)建子線程,由子線程和客戶端通信
thread_do_service = Thread(target=do_service, args=(connect_socket,))
thread_do_service.start()
# 主線程,不能關(guān)閉connect_socket,多個線程共享打開的文件
# connect_socket.close()
if __name__ == "__main__":
main()
Num09-->TCP單進(jìn)程阻塞服務(wù)器實(shí)現(xiàn)
#! /usr/bin/env python3
# -*- coding:utf-8 -*-
# @Author : xiaoke
import time
from socket import *
def main():
# 1.創(chuàng)建socket
listen_socket = socket(AF_INET, SOCK_STREAM)
# 設(shè)置允許復(fù)用地址,當(dāng)建立連接之后服務(wù)器先關(guān)閉,設(shè)置地址復(fù)用
# 設(shè)置socket層屬性 復(fù)用地址 允許
listen_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 2.綁定端口
my_addr = ('192.168.105.125', 8080)
listen_socket.bind(my_addr)
# 3.接聽狀態(tài)
listen_socket.listen(4)
print('listening...')
# 4.等待客戶端發(fā)起請求
while True:
# 接受連接請求,創(chuàng)建連接套接字,用于客戶端間通信
connect_socket, client_addr = listen_socket.accept() # accept默認(rèn)會引起阻塞
# 新創(chuàng)建連接用的socket, 客戶端的地址
# print(connect_socket)
print(client_addr)
while True:
# tcp recv() 只會返回接收到的數(shù)據(jù)
recv_data = connect_socket.recv(1024)
if len(recv_data) == 0:
# 發(fā)送方關(guān)閉tcp的連接,recv()不會阻塞,而是直接返回''
print('client %s close' % str(client_addr))
time.sleep(5)
break
print('recv: %s' % recv_data.decode('gbk'))
# 用完之后,關(guān)閉connect_socket
connect_socket.close()
if __name__ == "__main__":
main()
Num10-->TCP單進(jìn)程非阻塞服務(wù)器實(shí)現(xiàn)
#! /usr/bin/env python3
# -*- coding:utf-8 -*-
# @Author : xiaoke
import time
from socket import *
def main():
# 1.創(chuàng)建socket
listen_socket = socket(AF_INET, SOCK_STREAM)
# 設(shè)置允許復(fù)用地址,當(dāng)建立連接之后服務(wù)器先關(guān)閉,設(shè)置地址復(fù)用
# 設(shè)置socket層屬性 復(fù)用地址 允許
listen_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 設(shè)置listen_socket為非阻塞方式
listen_socket.setblocking(False) # 設(shè)置非阻塞
# 2.綁定端口
my_addr = ('192.168.105.125', 8080)
listen_socket.bind(my_addr)
# 3.接聽狀態(tài)
listen_socket.listen(4)
print('listening...')
# 4.等待客戶端發(fā)起請求
# 創(chuàng)建一個列表,保存已連接socket
connect_socket_list = []
while True:
print(connect_socket_list)
try:
# 接受連接請求,創(chuàng)建連接套接字,用于客戶端連通信
connect_socket, client_addr = listen_socket.accept() # accept默認(rèn)會引起阻塞
except Exception as e: # 還沒有客戶端連接
# print(e)
# time.sleep(1)
pass
else: # 此時有連接請求
# 新創(chuàng)建連接用的socket, 客戶端的地址
print('有新的客戶端連接 %s' % str(client_addr))
# 將新socket 設(shè)成非阻塞
connect_socket.setblocking(False)
# 將新的socket添加到列表中,以便后續(xù)循環(huán)讀數(shù)據(jù)
connect_socket_list.append(connect_socket)
# 保存刪除socket列表
need_delete_socket_list = []
# 遍歷已連接的socket分別讀數(shù)據(jù)
for new_socket in connect_socket_list:
try:
recv_data = new_socket.recv(1024)
except:
pass
else:
# 如果對方關(guān)閉
if len(recv_data) == 0:
print('%s close' % (str(new_socket.getpeername())))
new_socket.close()
# 從connect_socket_list列表中刪除,單獨(dú)使用列表保存要刪除socket
need_delete_socket_list.append(new_socket)
continue
print('from %s : %s' %
(str(new_socket.getpeername()), recv_data.decode('gbk')))
# 從connect_socket_list刪除已關(guān)閉soccket
for s in need_delete_socket_list:
connect_socket_list.remove(s)
time.sleep(1)
if __name__ == "__main__":
main()
Num11-->select版--TCP服務(wù)器實(shí)現(xiàn)
Test01-->select 原理
在多路復(fù)用的模型中,比較常用的有select模型和epoll模型。這兩個都是系統(tǒng)接口,由操作系統(tǒng)提供。當(dāng)然,Python的select模塊進(jìn)行了更高級的封裝。
網(wǎng)絡(luò)通信被Unix系統(tǒng)抽象為文件的讀寫,通常是一個設(shè)備,由設(shè)備驅(qū)動程序提供,驅(qū)動可以知道自身的數(shù)據(jù)是否可用。支持阻塞操作的設(shè)備驅(qū)動通常會實(shí)現(xiàn)一組自身的等待隊(duì)列,如讀/寫等待隊(duì)列用于支持上層(用戶層)所需的block或non-block操作。設(shè)備的文件的資源如果可用(可讀或者可寫)則會通知進(jìn)程,反之則會讓進(jìn)程睡眠,等到數(shù)據(jù)到來可用的時候,再喚醒進(jìn)程。
這些設(shè)備的文件描述符被放在一個數(shù)組中,然后select調(diào)用的時候遍歷這個數(shù)組,如果對于文件描述符可讀則會返回該文件描述符。當(dāng)遍歷結(jié)束之后,如果仍然沒有一個可用設(shè)備文件描述符,select則讓用戶進(jìn)程睡眠,直到等待資源可用的時候再喚醒,喚醒之后遍歷之前那個監(jiān)視的數(shù)組。每次遍歷都是依次進(jìn)行判斷的。
Test02-->select的優(yōu)缺點(diǎn)
優(yōu)點(diǎn):select目前幾乎在所有的平臺上支持,其良好跨平臺支持也是它的一個優(yōu)點(diǎn)。
缺點(diǎn):select的一個缺點(diǎn)在于單個進(jìn)程能夠監(jiān)視的文件描述符的數(shù)量存在最大限制,在Linux上一般為1024,可以通過修改宏定義甚至重新編譯內(nèi)核的方式提升這一限制,但是這樣也會造成效率的降低。
一般來說這個數(shù)目和系統(tǒng)內(nèi)存關(guān)系很大,具體數(shù)目可以cat /proc/sys/fs/file-max察看。32位機(jī)默認(rèn)是1024個。64位機(jī)默認(rèn)是2048.
對socket進(jìn)行掃描時是依次掃描的,即采用輪詢的方法,效率較低。
當(dāng)套接字比較多的時候,每次select()都要通過遍歷FD_SETSIZE個Socket來完成調(diào)度,不管哪個Socket是活躍的,都遍歷一遍。這會浪費(fèi)很多CPU時間。
Test03-->案例的實(shí)現(xiàn)代碼
#! /usr/bin/env python3
# -*- coding:utf-8 -*-
# @Author : xiaoke
import select
import sys # sys.stdin 代表鍵盤設(shè)備的文件對象
from socket import *
def main():
# 1.創(chuàng)建socket
listen_socket = socket(AF_INET, SOCK_STREAM)
# 設(shè)置允許復(fù)用地址,當(dāng)建立連接之后服務(wù)器先關(guān)閉,設(shè)置地址復(fù)用
# 設(shè)置socket層屬性 復(fù)用地址 允許
listen_socket.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
# 2.綁定端口
my_addr = ('192.168.105.125', 8080)
listen_socket.bind(my_addr)
# 3.接聽狀態(tài)
listen_socket.listen(4)
print('listening...')
# 指定select關(guān)心的哪些路(socket,或文件)數(shù)據(jù)
rlist = [listen_socket, sys.stdin] # 要讀的文件對象列表,包括listen socket
wlist = [] # 要寫的文件對象列表
xlist = [] # 出現(xiàn)異常的文件對象列表
while True:
print(rlist)
# select 會阻塞等待三個列表中文件對象就緒,如果沒就緒,select一直阻塞;只有任意文件就緒,select返回
# 就緒的文件列表
read_ready_list, wready, excplist = select.select(rlist, wlist, xlist)
# 指定seclet關(guān)注讀,寫,異常文件列表
# 如果select返回,一定有客戶端連接服務(wù)器
# 循環(huán)判斷是哪個關(guān)注的文件,讀就緒了
for fobj in read_ready_list:
# 如果fobj是listen_socket對象,一定有客戶端連接服務(wù)器
if fobj == listen_socket:
new_socket, peer_addr = fobj.accept() # 此時accept調(diào)用一定不會阻塞
print(peer_addr)
# 將新的socket添加至rlist,也要進(jìn)行關(guān)注
rlist.append(new_socket)
elif fobj == sys.stdin: # 鍵盤有數(shù)據(jù)輸入
data = sys.stdin.readline() # input()
print('input %s' % data)
if data == 'quit\n':
exit()
else: # 已連接socket有數(shù)據(jù)可讀
recv_data = fobj.recv(1024)
if len(recv_data) > 0:
print('from %s : %s' % (str(fobj.getpeername()), recv_data.decode('gbk')))
else: # 客戶端關(guān)閉socket
print('%s close' % str(fobj.getpeername()))
fobj.close() # 將關(guān)閉socket從rlist列表中刪除,表示不再關(guān)注這個socket
rlist.remove(fobj)
if __name__ == "__main__":
main()
Num12-->epoll版--TCP服務(wù)器實(shí)現(xiàn)
Test01-->epoll的優(yōu)點(diǎn):
沒有最大并發(fā)連接的限制,能打開的FD(指的是文件描述符,通俗的理解就是套接字對應(yīng)的數(shù)字編號)的上限遠(yuǎn)大于1024
效率提升,不是輪詢的方式,不會隨著FD數(shù)目的增加效率下降。只有活躍可用的FD才會調(diào)用callback函數(shù);即epoll最大的優(yōu)點(diǎn)就在于它只管你“活躍”的連接,而跟連接總數(shù)無關(guān),因此在實(shí)際的網(wǎng)絡(luò)環(huán)境中,epoll的效率就會遠(yuǎn)遠(yuǎn)高于select和poll。
Test02-->一些術(shù)語
EPOLLIN (可讀)
EPOLLOUT (可寫)
EPOLLET (ET模式)
epoll對文件描述符的操作有兩種模式:LT(level trigger)和ET(edge trigger)。
LT模式是默認(rèn)模式,LT模式與ET模式的區(qū)別如下:
LT模式:當(dāng)epoll檢測到描述符事件發(fā)生并將此事件通知應(yīng)用程序,應(yīng)用程序可以不立即處理該事件。下次調(diào)用epoll時,會再次響應(yīng)應(yīng)用程序并通知此事件。
ET模式:當(dāng)epoll檢測到描述符事件發(fā)生并將此事件通知應(yīng)用程序,應(yīng)用程序必須立即處理該事件。如果不處理,下次調(diào)用epoll時,不會再次響應(yīng)應(yīng)用程序并通知此事件。
Test03-->案例的實(shí)現(xiàn)代碼
#! /usr/bin/env python3
# -*- coding:utf-8 -*-
# @Author : xiaoke
import socket
import select
# 創(chuàng)建套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 設(shè)置可以重復(fù)使用綁定的信息
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
# 綁定本機(jī)信息
s.bind(("", 8080))
# 變?yōu)楸粍?s.listen(10)
# 創(chuàng)建一個epoll對象
epoll = select.epoll()
# 測試,用來打印套接字對應(yīng)的文件描述符
# print s.fileno()
# print select.EPOLLIN|select.EPOLLET
# 注冊事件到epoll中
# epoll.register(fd[, eventmask])
# 注意,如果fd已經(jīng)注冊過,則會發(fā)生異常
# 將創(chuàng)建的套接字添加到epoll的事件監(jiān)聽中
epoll.register(s.fileno(), select.EPOLLIN | select.EPOLLET)
connections = {}
addresses = {}
# 循環(huán)等待客戶端的到來或者對方發(fā)送數(shù)據(jù)
while True:
# epoll 進(jìn)行 fd 掃描的地方 -- 未指定超時時間則為阻塞等待
epoll_list = epoll.poll()
# 對事件進(jìn)行判斷
for fd, events in epoll_list:
# print fd
# print events
# 如果是socket創(chuàng)建的套接字被激活
if fd == s.fileno():
conn, addr = s.accept()
print('有新的客戶端到來%s' % str(addr))
# 將 conn 和 addr 信息分別保存起來
connections[conn.fileno()] = conn
addresses[conn.fileno()] = addr
# 向 epoll 中注冊 連接 socket 的 可讀 事件
epoll.register(conn.fileno(), select.EPOLLIN | select.EPOLLET)
elif events == select.EPOLLIN:
# 從激活 fd 上接收
recvData = connections[fd].recv(1024)
if len(recvData) > 0:
print('recv:%s' % recvData)
else:
# 從 epoll 中移除該 連接 fd
epoll.unregister(fd)
# server 側(cè)主動關(guān)閉該 連接 fd
connections[fd].close()
print("%s---offline---" % str(addresses[fd]))