Python——socket網路程式設計
應用程式架構的分類:
C/S 客戶端/伺服器
B/S 瀏覽器/伺服器
C/S架構和B/S架構的區別
C/S架構的優點:
- 個性化更容易實現
- 更安全
- 佔用網路資源少
B/S結構的優點:
- 更新方便
- 使用方便
- 幾乎不佔用本地資源
@
目錄前言
- C/S架構與socket的關係:我們學習socket的目的是為了實現C/S架構;
- 還有一種架構是基於socket來實現的,寫一個伺服器端軟體,客戶端軟體不用寫,客戶端就是瀏覽器(瀏覽器的本質是套接字寫的客戶端);
一、什麼是網路
在一臺計算機上由硬體,硬體之上是作業系統,作業系統之上是應用程式(為客戶端軟體)
在另一臺計算機上硬體之上是作業系統,在作業系統之上是應用程式(伺服器端軟體),這兩臺計算機是基於網路通訊的
什麼是網路:
-
物理介質:網線;
-
協議(就像兩個人打電話,物理介質是電話線,兩個人處於不同的地區,兩個人說的話彼此聽不懂,怎麼解決?定義一個標準,稱之為網際網路協議)
總結:
在計算機通訊中,計算機除了物理連結介質外,想要通訊,計算機必須有一個標準,稱之為網際網路協議
即(網路就是物理連結介質和網際網路協議)
接下來寫一個客戶端軟體和伺服器軟體,兩者之間基於網路通訊(網路包含:物理連結介質,(是網路工程師乾的);我們要乾的事情是客戶端軟體給伺服器軟體發訊息,必須讓伺服器軟體和客戶端軟體遵循網際網路協議)
二、網際網路協議按照功能不同分為osi七層或tcp/ip五層或tcp/ip四層
(我們主要學習五層)
資料的封裝與解封裝詳情見:https://www.cnblogs.com/huoxc/p/13611394.html
在網路層:IP協議
傳輸層:TCP/UDP協議
資料鏈路層:乙太網協議
應用層:http協議,DNS協議,ftp協議
三:什麼是socket
- 應用層軟體是我們寫的,想要發資料傳給傳輸層,應用層規定好的協議其他層控制不了,是客戶端和伺服器端規定好的,沒人管,但是往外發資料,必須按照UDP或者TCP協議;如果用TCP協議必須按照TCP的協議來,必須把TCP協議學會以及IP協議等;那麼效率比較低;
- 怎麼解決:對於這種情況把TCP,IP協議等瘋漲起來,對於我來說TCP以下我都不打交道了,我只跟應用層打交道,socket就是在應用層和傳輸層之間的,傳輸層一下的都封裝到socket中,對於我來遵循套接字的標準,那麼我們基於socket來開發的;針對這種協議,python中有一個模組,socket
四:套接字的工作流程
(TCP協議的套接字)
首先:
1:伺服器端先拿到一個套接字物件
2:接下來繫結IP和埠
3:接下來監聽
4:等待客戶端的連結
其次:
1:客戶端拿到一個套接字物件
2:客戶端和伺服器建立連結(即3次握手,雙向連結)
3:客戶端向伺服器傳送資料
4:伺服器對請求進行處理,處理完畢後再響應給客戶端
5:關閉客戶端
先從伺服器端說起。伺服器端先初始化Socket,然後與埠繫結(bind),對埠進行監聽(listen),呼叫accept阻塞,等待客戶端連線。在這時如果有個客戶端初始化一個Socket,然後連線伺服器(connect),如果連線成功,這時客戶端與伺服器端的連線就建立了。客戶端傳送資料請求,伺服器端接收請求並處理請求,然後把迴應資料傳送給客戶端,客戶端讀取資料,最後關閉連線,一次互動結束
服務端
import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024 #收發訊息的大小
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #其中socket.AF_INET:基於網路通訊的套接字;
#socket.SOCK_STREAM:流式套接字又稱之為TCP協議
s.bind(ip_port) #繫結的IP和埠(IP為伺服器的IP)
s.listen(5) #監聽,如打電話,在跟別人通話的時候,還可能來多個電話,這個連結處於掛起的狀態,如果當前的連結以結束,立馬就跟剛剛掛起的電話通訊,在TCP協議中被稱之為連線池的大小
conn,addr=s.accept() #手機接電話(accept是一種阻塞操作,什麼時候變成非阻塞狀態,有人鏈接就會變成非阻塞狀態)返回一個連結物件,客戶端的IP地址和埠
# print(conn)
# print(addr)
print('接到來自%s的電話' %addr[0])
msg=conn.recv(BUFSIZE) #接受訊息
print(msg,type(msg))
conn.send(msg.upper()) #發訊息,
conn.close() #關閉連結
s.close() #關閉套接字
客戶端:
import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(ip_port) #建立連結
s.send('lheiehei '.encode('utf-8')) #發訊息(基於網路傳送的是二進位制格式)
feedback=s.recv(BUFSIZE) #收訊息
print(feedback.decode('utf-8'))
s.close() #關閉連結
有可能還會出現以下的情況:
怎麼解決:
這個是由於你的服務端仍然存在四次揮手的time_wait狀態在佔用地址(如果不懂,請深入研究1.tcp三次握手,四次揮手 2.syn洪水攻擊 3.伺服器高併發情況下會有大量的time_wait狀態的優化方法)
#加入一條socket配置,重用ip和埠 或者重新寫一個非著名埠
phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))
如果在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 時間
加上鍊接迴圈與通訊迴圈:
服務端:
import socket
ip_port=('127.0.0.1',9000) #電話卡
BUFSIZE=1024 #收發訊息的尺寸
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機
print(s) #s是套接字物件:這個套接字物件主要用來建立三次握手
s.bind(ip_port) #手機插卡
s.listen(5) #手機待機
conn,addr=s.accept() #手機接電話
# print(conn)
# print(addr)
while True:
msg=conn.recv(BUFSIZE) #聽訊息,聽話(這種套接字主要用來收發訊息,跟一個客戶端收發訊息,因為跟一個客戶端建號的三次握手)
conn.send(msg.upper()) #發訊息,說話
conn.close() #掛電話
s.close()
客戶端:
import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(ip_port) #撥電話
while True:
s.send('linhaifeng nb'.encode('utf-8')) #發訊息,說話(只能傳送位元組型別)
feedback=s.recv(BUFSIZE) #收訊息,聽話
print(feedback.decode('utf-8'))
s.close() #掛電話
總結:
服務端套接字有幾種形式:
- conn這種套接字(這種套接字主要用來收發訊息,跟一個客戶端收發訊息,因為跟一個客戶端建號的三次握手)
如果再換一個客戶端連結,就是一個新的conn物件; - 剛開始的時候,拿到的s套接字物件,這個套接字物件主要用來建立三次握手;
如果客戶端發的是空,則直接卡住了,在客戶端沒有卡住,在伺服器端卡住了,服務端一直在等著,怎麼解決?,在客戶端解決;
import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
s.connect(ip_port) #撥電話
while True:
msg=input('>>>:').strip()
if not msg:continue #當為空的時候,繼續讓使用者輸入
s.close() #掛電話
粘包:
須知:只有TCP有粘包現象,UDP永遠不會粘包,首先需要掌握一個socket收發訊息的原理