Python進階之網路程式設計
網路通訊
使用網路的目的
把多方連結在一起,進行資料傳遞;
網路程式設計就是,讓不同電腦上的軟體進行資料傳遞,即程序間通訊;
ip地址
ip地址概念和作用
IP地址是什麼:比如192.168.1.1 這樣的一些數字;
ip地址的作用:用來在電腦中 標識唯一一臺電腦,比如192.168.1.1;在本地區域網是唯一的。
網絡卡資訊
檢視網絡卡資訊
Linux:ifconfig
windows:ipconfig
- ensxx:用來與外部進行通訊的網絡卡;
- lo:環回網絡卡,用來進行本地通訊的;
linux關閉/開啟網絡卡:sudo ifconfig ensxx down/up
ip和ip地址的分類
ip分為ipv4和ipv6
ip地址分為:
- A類地址
- B類地址
- C類地址
- D類地址--用於多播
- E類地址--保留地址,因ipv6誕生,已無用
- 私有ip
單播--一對一
多播--一對多
廣播--多對多
埠
ip:標識電腦;
埠:標識電腦上的程序(正在執行的程式);
ip和埠一起使用,唯一標識主機中的應用程式,進行統一軟體的通訊;
埠分類
知名埠
固定分配給特定程序的埠號,其他程序一般無法使用這個埠號;
小於1024的,大部分都是知名埠;
範圍從0~1023;
動態埠
不固定分配,動態分配,使用後釋放的埠號;
範圍1024~65535;
socket
socket的概念
socket是程序間通訊的一種方式,能實現不同主機間的程序間通訊,即socket是用來網路通訊必備的東西;
建立socket
建立套接字:
import socket
soc = socket.socket(AddressFamily, Type)
函式socket.socket建立一個socket,該函式有兩個引數:
Address Family:可選 AF_INET(用於internet程序間通訊)和AF_UNIX(用於同一臺機器程序間通訊);
Type:套接字型別,可選 SOCK_STREAM(流式套接字,主用於TCP協議)/SOCK_DGRAM(資料報套接字,主用於UDP套接字);
建立tcp套接字
import socket soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM) ... soc.close()
建立udp套接字
import socket
soc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
...
soc.close()
udp
udp使用socket傳送資料
在同一區域網內發訊息;
如果用虛擬機器和windows,要用橋接模式,確保在同一區域網內;
import socket
def main():
# 建立一個udp套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 使用套接字收發資料
udp_socket.sendto(b"hahaha", ("193.168.77.1", 8080))
# 關閉套接字
udp_socket.close()
if __name__ == "__main__":
main()
udp傳送資料的幾種情況:
- 在固定資料的引號前加b,不能使用於使用者自定義資料;
- 使用者自定義資料,並進行傳送,使用.encode("utf-8")進行encode編碼
- 使用者迴圈傳送資料
- 使用者迴圈傳送資料並可以退出
只貼出最後一種情況,即完整程式碼
import socket
def main():
# 建立一個udp套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
while 1:
# 從鍵盤獲取要傳送的資料
send_data = input("請輸入你要傳送的資料:")
if send_data == "exit":
break
# 使用套接字收發資料
udp_socket.sendto(send_data.encode("utf-8"), ("193.168.77.1", 8080))
# 關閉套接字
udp_socket.close()
if __name__ == "__main__":
main()
udp接收資料
接收到的資料是一個元組,元組第一部分是傳送方傳送的內容,元組第二部分是傳送方的ip地址和埠號;
import socket
def main():
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
localaddr = ('', 8080)
udp_socket.bind(localaddr) # 必須繫結自己電腦的ip和埠
# 接收資料
recv_data = udp_socket.recvfrom(1024)
# recv_data這個變數儲存的是一個元組,例如 (b'hahaha', ('192.168.77.1', 8888))
recv_msg = recv_data[0]
send_addr = recv_data[1]
# print("%s 傳送了:%s" % (str(send_addr), recv_msg.decode("utf-8"))) # linux傳送的資料用utf8解碼
print("%s 傳送了:%s" % (str(send_addr), recv_msg.decode("gbk"))) # windows傳送的資料用gbk解碼
udp_socket.close()
if __name__ == "__main__":
main()
udp接發資料總結
傳送資料的流程:
- 建立套接字
- 傳送資料
- 關閉套接字
接收資料的流程:
- 建立套接字
- 繫結本地自己的資訊,ip和埠
- 接收資料
- 關閉套接字
埠繫結的問題
- 如果在你傳送資料時,還沒有繫結埠,那麼作業系統就會隨機給你分配一個埠,迴圈傳送時用的是同一個埠;
- 也可以先繫結埠,再發送資料。
udp傳送訊息時自己繫結埠示例
import socket
def main():
# 建立一個udp套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 繫結埠
udp_socket.bind(('192.168.13.1', 8080))
while 1:
# 從鍵盤獲取要傳送的資料
send_data = input("請輸入你要傳送的資料:")
if send_data == "exit":
break
# 使用套接字收發資料
udp_socket.sendto(send_data.encode("utf-8"), ("193.168.77.1", 8080))
# 關閉套接字
udp_socket.close() # 按ctrl+c退出
if __name__ == "__main__":
main()
但應注意,同一埠在同一時間不能被兩個不同的程式同時使用;
單工,半雙工,全雙工
單工半雙工全雙工的理解
單工:
只能單向傳送資訊,別人接收,別人不能回覆訊息,比如廣播;
半雙工:
兩個人都能發訊息,但是在同一時間只能有一個人發訊息,比如對講機;
全雙工:
兩個人都能發訊息,能同時發,比如打電話;
udp使用同一套接字收且發資料
"""socket套接字是全雙工"""
import socket
def main():
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
udp_socket.bind(('192.168.13.1', 8080))
# 讓使用者輸入要傳送的ip地址和埠
dest_ip = input("請輸入你要傳送資料的ip地址:")
dest_port = int(input("請輸入你要傳送資料的埠號:"))
# 從鍵盤獲取要傳送的資料
send_data = input("請輸入你要傳送的資料:")
# 使用套接字收發資料
udp_socket.sendto(send_data.encode("utf-8"), (dest_ip, dest_port))
# 套接字可以同時 收發資料;
recv_data = udp_socket.recvfrom(1024)
print(recv_data)
# 關閉套接字
udp_socket.close() # 按ctrl+c退出
if __name__ == "__main__":
main()
在這裡體現不出來socket是全雙工,因為現在直譯器只能按照流程,一步一步走下去,後面學習了程序執行緒協程就可以做到了。
tcp
tcp-可靠傳輸
tcp採取的機制
- 採用傳送應答機制
- 超時重傳
- 錯誤校驗
- 流量控制和阻塞管理
tcp與udp的區別
- tcp更安全可靠,udp相對沒那麼安全可靠;
- 面向連線
- 有序資料傳輸
- 重發丟失的資料
- 捨棄重複的資料包
- 無差錯的資料傳輸
- 阻塞/流量控制
tcp,udp應用場景
tcp應用場景:下載,傳送訊息
udp應用場景:電話,視訊直播等
tcp客戶端
tcp客戶端傳送資料
import socket
def main():
# 1.建立tcp的套接字
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.連結伺服器
tcp_socket.connect(('193.168.11.1', 8080))
# 3.傳送/接收訊息
send_data = input("請輸入你要傳送的訊息:")
tcp_socket.send(send_data.encode("utf-8"))
# 4.關閉套接字
tcp_socket.close()
if __name__ == "__main__":
main()
tcp伺服器
監聽套接字,專門用來監聽的;
accept會對應新建立的套接字,當監聽套接字收到一個請求後,將該請求分配給新套接字,由此監聽套接字可以繼續去監聽了,而新套接字則為該胡克段服務。
import socket
def main():
# 建立tcp套接字
tcp_service_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
tcp_service_socket.bind(('', 8080))
# 讓預設的套接字由主動變為被動
tcp_service_socket.listen(128)
# 等待客戶端的連結
new_client_socket, client_addr = tcp_service_socket.accept()
print("連結的客戶端地址為:", client_addr)
# 接收客戶端傳送過來的請求
recv_data = new_client_socket.recvfrom(1024)
print(recv_data)
# 給客戶端回送訊息
new_client_socket.send("hahahah".encode("utf-8"))
new_client_socket.close()
tcp_service_socket.close()
if __name__ == '__main__':
main()
listen裡面的引數,表示同時只允許128個連結訪問。
QQ不繫結埠的執行原理-擴充套件
udp和tcp並用;
使用QQ,先登入,登入後告訴騰訊伺服器此QQ執行的埠,發訊息時,通過騰訊伺服器轉發給另一個QQ;
不繫結埠也有一個好處,就是允許多開,即一個電腦上可以執行多個QQ;
recv和recvfrom的區別
recvfrom裡面不僅有發過來的資料,還有發過來資料的人的資訊;
recv裡面就只有資料;
tcp客戶端服務端流程梳理
tcp伺服器流程梳理
- 建立伺服器套接字
- 繫結本地資訊
- 讓預設的套接字由主動變為被動
- 等待客戶端的連結,堵塞
- 被客戶端連結後,建立一個新的客服套接字為客戶端服務;
- 接收客戶端傳送的訊息,堵塞
- 接收客戶端傳送的訊息後,給客戶端回訊息
- 關閉客服套接字,關閉服務端套接字
tcp注意點
- tcp伺服器一般情況下都需要繫結,否則客戶端找不到這個伺服器。
- tcp客戶端一般不繫結,因為是主動連結伺服器,所以只要確定好伺服器的ip, port等資訊就好,本地客戶端可以隨機。
- tcp伺服器通過listen可以將socket創建出來的主動套接字變為被動的,這是做tcp伺服器時必須要做的。
當客戶端需要連結伺服器時,就需要使用connect進行連結, udp是不需要連結的而是直接傳送,但是tcp必須先連結,只有連結成功才能通訊。
當一個tcp客戶端連線伺服器時,伺服器端會有1個新的套接字,這個套接字用來標記這個客戶端,單獨為這個客戶端服務。
liston後的套接字是被動套接字,用來接收新的客戶端的連結請求的,而accept返回的新套接字是標記這個新客戶端的。
關閉isten後的套接字意味著被動套接字關閉了,會導致新的客戶端不能夠連結伺服器,但是之前已經連結成功的客戶端正常通訊。
關閉accept返回的套接字意味著這個客戶端已經服務完畢。
9.當客戶端的套接字呼叫close後.伺服器端會recv解堵塞,並且返回的長度為0,因此伺服器可以通過 返回資料的長度來區別客戶端是否已經下線。
tcp應用案例
示例1-為一個使用者辦理一次業務:
"""可以理解為銀行一個客服為排隊的人員辦理業務"""
import socket
def main():
# 1.建立tcp套接字
tcp_service_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.繫結本地資訊
tcp_service_socket.bind(('', 8080))
# 3.讓預設的套接字由主動變為被動
tcp_service_socket.listen(128)
while 1:
# 4.等待客戶端的連結
new_client_socket, client_addr = tcp_service_socket.accept()
print("連結的客戶端地址為:", client_addr)
# 接收客戶端傳送過來的請求
recv_data = new_client_socket.recvfrom(1024)
print(recv_data)
# 給客戶端回送訊息
new_client_socket.send("hahahah".encode("utf-8"))
# 關閉套接字
new_client_socket.close()
tcp_service_socket.close()
if __name__ == '__main__':
main()
示例2-為同一使用者服務多次並判斷一個使用者是否服務完畢:
"""可以理解為銀行一個客服為排隊的人員辦理業務"""
import socket
def main():
# 1.建立tcp套接字
tcp_service_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.繫結本地資訊
tcp_service_socket.bind(('', 8080))
# 3.讓預設的套接字由主動變為被動
tcp_service_socket.listen(128)
while 1:
# 4.等待客戶端的連結
new_client_socket, client_addr = tcp_service_socket.accept()
print("連結的客戶端地址為:", client_addr)
# 迴圈目的:為同一個客戶服務多次
while 1:
# 接收客戶端傳送過來的請求
recv_data = new_client_socket.recvfrom(1024)
print(recv_data)
# 如果recv解堵塞,那麼有兩種方式
# 1.客戶端發了資料過來
# 2.客戶端呼叫了close
if recv_data:
# 給客戶端回送訊息
new_client_socket.send("hahahah".encode("utf-8"))
else:
break
# 關閉套接字
new_client_socket.close()
tcp_service_socket.close()
if __name__ == '__main__':
main()
示例3-tcp檔案下載客戶端和服務端:
檔案下載客戶端
import socket
def main():
# 1.建立套接字
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.獲取伺服器的ip,port
dest_ip = input("請輸入你要連結的伺服器ip:")
dest_port = input("請輸入你要連結的埠:")
# 3.連結伺服器
tcp_socket.connect((dest_ip, dest_port))
# 4.獲取下載的檔名字
want_file = input("請輸入你要下載的檔案:")
# 5.將檔名字傳送到伺服器
tcp_socket.send(want_file.encode("utf-8"))
# 6.接收要下載的檔案
file_data = tcp_socket.recv(1024)
# 7.將接收檔案的資料寫入一個檔案中
if file_data:
with open("[復件]" + want_file, "wb") as f:
f.write(file_data)
# 8.關閉套接字
tcp_socket.close()
pass
if __name__ == '__main__':
main()
檔案下載服務端
import socket
def send_file2client(new_socket, client_addr):
# 1.接受客戶端傳送過來的 要下載的檔名
want_file = new_socket.recv(1024).decode("utf-8")
print("客戶端 %s 要接收的檔案為:%s" % (str(client_addr), want_file))
# 2.讀取檔案資料
file_data = None
try:
f = open(want_file, "rb")
file_data = f.read()
f.close()
except Exception as e:
print("你要下載的檔案 %s 不存在" % want_file)
# 3.傳送檔案的資料給客戶端
if file_data:
new_socket.send(file_data)
def main():
# 1.建立套接字
tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 2.繫結本地資訊
tcp_socket.bind(('', 8080))
# 3.套接字被動接受 listen
tcp_socket.listen(128)
while 1:
# 4.等待客戶端的連結 accept
new_socket, client_addr = tcp_socket.accept()
# 5.呼叫函式傳送檔案到客戶端
send_file2client(new_socket, client_addr)
# 7.關閉套接字
new_socket.close()
tcp_socket.close()
if __name__ == '__main__':
main()