網路程式設計之Socket程式碼例項
網路程式設計之Socket程式碼例項
一、基本Socket例子
Server端:
# Echo server program import socket HOST = '' # Symbolic name meaning all available interfaces PORT = 50007 # Arbitrary non-privileged port sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock_server.bind((HOST, PORT)) sock_server.listen(1) #開始監聽,1代表在允許有一個連線排隊,更多的新連線連進來時就會被拒絕 conn, addr = sock_server.accept() #阻塞直到有連線為止,有了一個新連線進來後,就會為這個請求生成一個連線物件 with conn: print('Connected by', addr) while True: data = conn.recv(1024) #接收1024個位元組 if not data: break #收不到資料,就break conn.sendall(data) #把收到的資料再全部返回給客戶端
Client端:
# Echo client program import socket HOST = 'localhost' # The remote host PORT = 50007 # The same port as used by the server client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client.connect((HOST, PORT)) client.sendall(b'Hello, world') data = client.recv(1024) print('Received',data)
先啟動Server端,再啟動Client端,結果如下:
二、迴圈收發資料
第一次接觸就這麼交待了,之說了一句話,感覺不夠過癮,如何實現更多的互動呢?簡單,只需要讓客戶端不斷的發,服務端不斷的收就可以了,寫個迴圈搞定。
Server端:
# Echo server program import socket HOST = '' # Symbolic name meaning all available interfaces PORT = 50007 # Arbitrary non-privileged port sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) sock_server.bind((HOST, PORT)) sock_server.listen(1) #開始監聽,1代表在允許有一個連線排隊,更多的新連線連進來時就會被拒絕 conn, addr = sock_server.accept() #阻塞直到有連線為止,有了一個新連線進來後,就會為這個請求生成一個連線物件 with conn: print('Connected by', addr) while True: data = conn.recv(1024) #接收1024個位元組 print("server recv:",conn.getpeername(), data.decode()) if not data: break #收不到資料,就break conn.sendall(data) #把收到的資料再全部返回給客戶端
Client端:
# Echo client program
import socket
HOST = 'localhost' # The remote host
PORT = 50007 # The same port as used by the server
client = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
client.connect((HOST, PORT))
while True:
msg = input(">>>:").strip()
if len(msg) == 0:continue
client.sendall(msg.encode()) #傳送使用者輸入的資料,必須是bytes模式
data = client.recv(1024)
print('Received',data.decode()) #收到伺服器的響應後,decode一下
三、簡單聊天軟體
上面的例子,服務端只是將客戶端發來的再發送給客戶端,這哪叫聊天啊,這種事需要雙方配合,得讓服務端也能說話。
Server端:
import socket
HOST = '' # Symbolic name meaning all available interfaces
PORT = 50007 # Arbitrary non-privileged port
sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_server.bind((HOST, PORT))
sock_server.listen(1) #開始監聽,1代表在允許有一個連線排隊,更多的新連線連進來時就會被拒絕
conn, addr = sock_server.accept() #阻塞直到有連線為止,有了一個新連線進來後,就會為這個請求生成一個連線物件
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024) #接收1024個位元組
print("recv from Alex:",conn.getpeername(), data.decode())
if not data: break #收不到資料,就break
response = input(">>>").strip()
conn.send(response.encode())
print("send to alex:",response)
Client不需要做更改,直接看結果:
以上的例子還是有bug,雙方只能一來一往的說話,如果你想來納許發2句話是不行的,會卡住。這是因為你發了一條訊息後,就去呼叫recv方法接收伺服器的響應了,再伺服器端返回訊息之前,這個recv(1024)方法是阻塞的,如果想允許此時還能再發訊息給伺服器端,就需要再單獨啟動一個執行緒,只負責發訊息。
四、聊天軟體升級版
剛才在聊天的時候,服務端在服務客戶端的時候,其它人如果也想跟服務端連線是處於排隊狀態,然後等正在被服務的客戶端完事並斷開後,下一個人就跟上,但實際情況是客戶端一斷開,服務端也跟著斷了。
為什麼會斷呢?引文服務端以下程式碼的意思是,如果收不到資料,就跳出迴圈,就斷開了。
conn, addr = sock_server.accept() #阻塞直到有連線為止,有了一個新連線進來後,就會為這個請求生成一個連線物件
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024) #接收1024個位元組
print("recv from Alex:",conn.getpeername(), data.decode())
if not data: break #收不到資料,就break , 就是它乾的
response = input(">>>").strip()
conn.send(response.encode())
print("send to alex:",response)
想實現一個客戶端斷開後,可以立刻接入另外一個客戶端的話,怎麼辦呢?只需要再在外層加個迴圈。
while True: #最外層loop
conn, addr = sock_server.accept() #阻塞直到有連線為止,有了一個新連線進來後,就會為這個請求生成一個連線物件
#為何把上面這句話也包含在迴圈裡?
print("來了個新客人",conn.getpeername() )
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024) #接收1024個位元組
print("recv from :",conn.getpeername(), data.decode())
if not data: break #收不到資料,就break
conn.send(data.upper())
print("send to alex:",data)
break 跳出後就回到大while那層:
但是,有的人在重啟服務端時可能會遇到:
這是由於你的服務端仍然存在4次揮手的time_wait狀態,在佔用地址(如果不懂,請深入研究:1、tcp三次握手,四次揮手。2、sun洪水攻擊。3、伺服器高併發情況下會有大量的time_wait狀態的優化方法)
解決方法1:
sock_server = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock_server.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #一行程式碼搞定,寫在bind之前
sock_server.bind((HOST, PORT))
解決方法2(用於Linux系統):
發現系統存在大量TIME_WAIT狀態的連線,通過調整linux核心引數解決,
vi /etc/sysctl.conf
編輯檔案,加入以下內容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
然後執行 /sbin/sysctl -p 讓引數生效。
net.ipv4.tcp_syncookies = 1 表示開啟SYN Cookies。當出現SYN等待佇列溢位時,啟用cookies來處理,可防範少量SYN攻擊,預設為0,表示關閉;
net.ipv4.tcp_tw_reuse = 1 表示開啟重用。允許將TIME-WAIT sockets重新用於新的TCP連線,預設為0,表示關閉;
net.ipv4.tcp_tw_recycle = 1 表示開啟TCP連線中TIME-WAIT sockets的快速回收,預設為0,表示關閉。
net.ipv4.tcp_fin_timeout 修改系統預設的 TIMEOUT 時間
五、UDP例項
UDP不需要經過3次握手和4次揮手,不需要提前建立連線,直接發資料就行。
Server端:
import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #udp型別
udp_server_client.bind(ip_port)
while True:
msg,addr=udp_server_client.recvfrom(BUFSIZE)
print("recv ",msg,addr)
udp_server_client.sendto(msg.upper(),addr)
Client端:
import socket
ip_port = ('127.0.0.1',9000)
BUFSIZE = 1024
udp_server_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
msg=input('>>: ').strip()
if not msg:continue
udp_server_client.sendto(msg.encode('utf-8'),ip_port)
back_msg,addr = udp_server_client.recvfrom(BUFSIZE)
print(back_msg.decode('utf-8'),addr)
結果:
六、TCP VS UDP
1、TCP基於連結通訊
- 基於連結,則需要listen(backlog),指定連線池的大小。
- 基於連結,必須先執行服務端,然後再由客戶端發起連結請求。
- 對於mac系統:如果一端斷開了連結,那另外一端的連結也跟著完蛋,recv將不會阻塞,接收到的是空(解決方法:服務端通訊迴圈內加異常處理,捕捉到異常後就break通訊迴圈)
- 對於windows/Linux系統:如果一端斷開了連結,那另外一端的連結也跟著完蛋,recv將不會阻塞,收到的是空(解決方法:服務端通訊迴圈內加異常處理,捕捉到異常後就break通訊迴圈)
2、UDP無連結
- 無連結,因而無需listen(backlog),更加沒有什麼連線池之說了。
- 無連結,UDP的sendinto不用管是否有一個正在執行的服務端,可以己端一個勁地發訊息,只不過資料會丟失。
- recvfrom收的資料小於sendinto傳送的資料時,在mac和Linux系統上資料直接丟失,在windows系統則會直接報錯。
- 只有sendinto傳送資料沒有recvfrom收資料,則資料丟失。