Python網絡編程——UDP與TCP
<<<CSDN排版不美觀,想看排版美觀的進網絡編程——UDP與TCP>>>
1、用戶數據報協議UDP(User Datagram Protocol)
①用戶數據報協議UDP(User Datagram Protocol)
UDP協議全稱是用戶數據報協議,在網絡中它與TCP協議一樣用於處理數據包,是一種無連接的協議。
②IP(網絡之間互連的協議)
互聯網協議地址(英語:Internet Protocol Address,又譯爲網際協議地址),縮寫爲IP地址(英語:IP Address),是分配給用戶上網使用的網際協議(英語:Internet Protocol, IP)的設備的數字標籤。常見的IP地址分爲IPv4與IPv6兩大類,但是也有其他不常用的小分類。
查看IP信息
Linux在終端輸入 ifconfig ,可以看到 IP 信息
windows在cmd中輸入 ipconfig,可以看到 IP信息
sudo ifconfig ensXX down 關閉ensXX網卡
sudo ifconfig ensXX up 開啓ensXX網卡
私有IP
國際規定有一部分IP地址是用於我們的局域網使用,也就是屬於私網IP,不在公網中使用的,它們的範圍是:
10.0.0.0~10.255.255.255
172.16.0.0~172.31.255.255
192.168.0.0~192.168.255.255
IP地址127.0.0.1~127.255.255.255用於迴路測試,
如:127.0.0.1可以代表本機IP地址,用http://127.0.0.1就可以測試本機中配置的Web服務器。
③端口
"端口"是英文port的意譯,可以認爲是設備與外界通訊交流的出口。端口可分爲虛擬端口和物理端口,其中虛擬端口指計算機內部或交換機路由器內的端口,不可見。例如計算機中的80端口、21端口、23端口等。物理端口又稱爲接口,是可見端口,計算機背板的RJ45網口,交換機路由器集線器等RJ45端口。
IP可以看成是一間房子,而端口可以看成是房子裏的門
端口號只有整數,範圍是從0到65535
知名端口
知名端口號(well-known port numbers)就是那些由互聯網名稱與數字地址分配機構(ICANN)預留給傳輸控制協議(TCP)和用戶數據包協議(UDP)使用的端口號。範圍是0到1023。
比如:80端口分配給HTTP服務,21端口是分配給FTP服務的
知名端口可以理解成,比如電話號碼,中國110,119,120等等,這些電話都已經按規定分配好了
動態端口
動態端口號(dynamic port numbers),即私人端口號(private port numbers),是可用於任意軟件與任何其他的軟件通信的端口數,使用因特網的傳輸控制協議,或用戶傳輸協議。動態端口的範圍是從1024到65535
查看端口號
用“netstat -an”查看端口狀態
lsof -i [tcp/udp]:2425
④socket套接字
Socket的英文原義是“孔”或“插座”。作爲BSD UNIX的進程通信機制,取後一種意思。通常也稱作"套接字",用於描述IP地址和端口,是一個通信鏈的句柄,可以用來實現不同虛擬機或不同計算機之間的通信。
網絡上的兩個程序通過一個雙向的通信連接實現數據的交換,這個連接的一端稱爲一個socket。建立網絡通信連接至少要一對端口號(socket)。socket本質是編程接口(API),對TCP/IP的封裝,TCP/IP也要提供可供程序員做網絡開發所用的接口,這就是Socket編程接口;HTTP是轎車,提供了封裝或者顯示數據的具體形式;Socket是發動機,提供了網絡通信的能力。
Python創建socket
import socket
socket.socket(AddressFamily, Type)
Address Family:可以選擇 AF_INET(用於 Internet 進程間通信) 或者 AF_UNIX(用於同一臺機器進程間通信),實際工作中常用AF_INET
Type:套接字類型,可以是 SOCK_STREAM(流式套接字,主要用於 TCP 協議)或者 SOCK_DGRAM(數據報套接字,主要用於 UDP 協議)
套接字使用流程 與 文件的使用流程很類似
創建套接字
使用套接字收/發數據
關閉套接字
創建一個tcp socket(tcp套接字)
import socket
創建tcp的套接字
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
…這裏是使用套接字的功能(省略)…
不用的時候,關閉套接字
s.close()
創建一個udp socket(udp套接字)
import socket
創建udp的套接字
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
…這裏是使用套接字的功能(省略)… # 不用的時候,關閉套接字
s.close()
socket練習
#coding=utf-8
from socket import *
- 創建udp套接字
udp_socket = socket(AF_INET, SOCK_DGRAM)
- 準備接收方的地址
'192.168.1.103’表示目的ip地址
8080表示目的端口
dest_addr = (‘192.168.1.103’, 8080) # 注意 是元組,ip是字符串,端口是數字
- 從鍵盤獲取數據
send_data = input(“請輸入要發送的數據:”)
- 發送數據到指定的電腦上的指定程序中
udp_socket.sendto(send_data.encode(‘utf-8’), dest_addr)
- 關閉套接字
udp_socket.close()
udp網絡程序-發送、接收數據
#coding=utf-8
from socket import *
- 創建udp套接字
udp_socket = socket(AF_INET, SOCK_DGRAM)
- 準備接收方的地址
dest_addr = (‘192.168.236.129’, 8080)
- 從鍵盤獲取數據
send_data = input(“請輸入要發送的數據:”)
- 發送數據到指定的電腦上
udp_socket.sendto(send_data.encode(‘utf-8’), dest_addr)
- 等待接收對方發送的數據
recv_data = udp_socket.recvfrom(1024) # 1024表示本次接收的最大字節數
- 顯示對方發送的數據
接收到的數據recv_data是一個元組
第1個元素是對方發送的數據
第2個元素是對方的ip和端口
print(recv_data[0].decode(‘gbk’))
print(recv_data[1])
- 關閉套接字
udp_socket.close()
編碼與解碼
編碼
str.encode()
解碼
bytes.decode()
綁定端口
#coding=utf-8
from socket import *
- 創建套接字
udp_socket = socket(AF_INET, SOCK_DGRAM)
2. 綁定本地的相關信息,如果一個網絡程序不綁定,則系統會隨機分配
local_addr = (’’, 7788) # ip地址和端口號,ip一般不用寫,表示本機的任何一個ip
udp_socket.bind(local_addr)
3. 等待接收對方發送的數據
recv_data = udp_socket.recvfrom(1024) # 1024表示本次接收的最大字節數
4. 顯示接收到的數據
print(recv_data[0].decode(‘gbk’))
5. 關閉套接字
udp_socket.close()
發送端可以不綁定端口號,防止佔用其他的端口,發送時採用隨機分配端口的策略;而接收端(服務端)必須綁定端口號
UDP聊天案例
import socket
def send_msg(udp_socket):
“”“獲取鍵盤數據,並將其發送給對方”""
# 1. 從鍵盤輸入數據
msg = input("\n請輸入要發送的數據:")
# 2. 輸入對方的ip地址
dest_ip = input("\n請輸入對方的ip地址:")
# 3. 輸入對方的port
dest_port = int(input("\n請輸入對方的port:"))
# 4. 發送數據
udp_socket.sendto(msg.encode(“utf-8”), (dest_ip, dest_port))
def recv_msg(udp_socket):
“”“接收數據並顯示”""
# 1. 接收數據
recv_msg = udp_socket.recvfrom(1024)
# 2. 解碼
recv_ip = recv_msg[1]
recv_msg = recv_msg[0].decode(“utf-8”)
# 3. 顯示接收到的數據
print(">>>%s:%s" % (str(recv_ip), recv_msg))
def main():
# 1. 創建套接字
udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
# 2. 綁定本地信息
udp_socket.bind(("", 7890))
while True:
# 3. 選擇功能
print("="*30)
print(“1:發送消息”)
print(“2:接收消息”)
print("="*30)
op_num = input(“請輸入要操作的功能序號:”)
# 4. 根據選擇調用相應的函數
if op_num == “1”:
send_msg(udp_socket)
elif op_num == “2”:
recv_msg(udp_socket)
else:
print(“輸入有誤,請重新輸入…”)
if name == “main”:
main()
⑤瞭解:
單工(Simplex Communication)
單工(Simplex Communication)模式的數據傳輸是單向的。通信雙方中,一方固定爲發送端,一方則固定爲接收端。信息只能沿一個方向傳輸,使用一根傳輸線
半雙工(Half Duplex)
半雙工(Half Duplex)數據傳輸指數據可以在一個信號載體的兩個方向上傳輸,但是不能同時傳輸。例如,在一個局域網上使用具有半雙工傳輸的技術,一個工作站可以在線上發送數據,然後立即在線上接收數據,這些數據來自數據剛剛傳輸的方向。像全雙工傳輸一樣,半雙工包含一個雙向線路(線路可以在兩個方向上傳遞數據)
全雙工(Full Duplex)
全雙工(Full Duplex)是通訊傳輸的一個術語。通信允許數據在兩個方向上同時傳輸,它在能力上相當於兩個單工通信方式的結合。全雙工指可以同時(瞬時)進行信號的雙向傳輸(A→B且B→A)。指A→B的同時B→A,是瞬時同步的
2、傳輸控制協議TCP(Transmission Control Protocol)
①TCP(Transmission Control Protocol 傳輸控制協議)
TCP(Transmission Control Protocol 傳輸控制協議)是一種面向連接的、可靠的、基於字節流的傳輸層通信協議,由IETF的RFC 793定義。在簡化的計算機網絡OSI模型中,它完成第四層傳輸層所指定的功能,用戶數據報協議(UDP)是同一層內另一個重要的傳輸協議。
TCP通信需要經過 創建連接、傳輸數據、終止連接
這種連接是一對一的,因此TCP不適用於廣播的應用程序,基於廣播的應用程序請使用UDP協議
可靠傳輸機制
1)TCP採用發送應答機制
TCP發送的每個報文段都必須得到接收方的應答才認爲這個TCP報文段傳輸成功
2)超時重傳
發送端發出一個報文段之後就啓動定時器,如果在定時時間內沒有收到應答就重新發送這個報文段。
TCP爲了保證不發生丟包,就給每個包一個序號,同時序號也保證了傳送到接收端實體的包的按序接收。然後接收端實體對已成功收到的包發回一個相應的確認(ACK);如果發送端實體在合理的往返時延(RTT)內未收到確認,那麼對應的數據包就被假設爲已丟失將會被進行重傳
3)錯誤校驗
TCP用一個校驗和函數來檢驗數據是否有錯誤;在發送和接收時都要計算校驗和
- 流量控制和阻塞管理
流量控制用來避免主機發送得過快而使接收方來不及完全收下
TCP客戶端
from socket import *
創建socket
tcp_client_socket = socket(AF_INET, SOCK_STREAM)
目的信息
server_ip = input(“請輸入服務器ip:”)
server_port = int(input(“請輸入服務器port:”))
鏈接服務器
tcp_client_socket.connect((server_ip, server_port))
提示用戶輸入數據
send_data = input(“請輸入要發送的數據:”)
tcp_client_socket.send(send_data.encode(“gbk”))
接收對方發送過來的數據,最大接收1024個字節
recvData = tcp_client_socket.recv(1024)
print(‘接收到的數據爲:’, recvData.decode(‘gbk’))
關閉套接字
tcp_client_socket.close()
TCP服務端
流程:
socket創建套接字
bind綁定IP和Port
listen使套接字變爲可以被動鏈接狀態
accept等待客戶端的鏈接
recv/send接收發送數據
from socket import *
創建socket
tcp_server_socket = socket(AF_INET, SOCK_STREAM)
本地信息
address = (’’, 7788)
綁定
tcp_server_socket.bind(address)
使用socket創建的套接字默認的屬性是主動的,使用listen將其變爲被動的,這樣就可以接收別人的鏈接了
tcp_server_socket.listen(128)
如果有新的客戶端來鏈接服務器,那麼就產生一個新的套接字專門爲這個客戶端服務
client_socket用來爲這個客戶端服務
tcp_server_socket就可以省下來專門等待其他新客戶端的鏈接
client_socket, clientAddr = tcp_server_socket.accept()
接收對方發送過來的數據
recv_data = client_socket.recv(1024) # 接收1024個字節
print(‘接收到的數據爲:’, recv_data.decode(‘gbk’))
發送一些數據到客戶端
client_socket.send(“thank you !”.encode(‘gbk’))
關閉爲這個客戶端服務的套接字,只要關閉了,就意味着爲不能再爲這個客戶端服務了,如果還需要服務,只能再次重新連接
client_socket.close()
文件下載器
服務器
from socket import *
import sys
def get_file_content(file_name):
“”“獲取文件的內容”""
try:
with open(file_name, “rb”) as f:
content = f.read()
return content
except:
print(“沒有下載的文件:%s” % file_name)
def main():
if len(sys.argv) != 2:
print(“請按照如下方式運行:python3 xxx.py 7890”)
return
else:
# 運行方式爲python3 xxx.py 7890
port = int(sys.argv[1])
# 創建socket
tcp_server_socket = socket(AF_INET, SOCK_STREAM)
# 本地信息
address = (’’, port)
# 綁定本地信息
tcp_server_socket.bind(address)
# 將主動套接字變爲被動套接字
tcp_server_socket.listen(128)
while True:
# 等待客戶端的鏈接,即爲這個客戶端發送文件
client_socket, clientAddr = tcp_server_socket.accept()
# 接收對方發送過來的數據
recv_data = client_socket.recv(1024) # 接收1024個字節
file_name = recv_data.decode(“utf-8”)
print(“對方請求下載的文件名爲:%s” % file_name)
file_content = get_file_content(file_name)
# 發送文件的數據給客戶端
# 因爲獲取打開文件時是以rb方式打開,所以file_content中的數據已經是二進制的格式,因此不需要encode編碼
if file_content:
client_socket.send(file_content)
# 關閉這個套接字
client_socket.close()
# 關閉監聽套接字
tcp_server_socket.close()
if name == “main”:
main()
客戶端
from socket import *
def main():
# 創建socket
tcp_client_socket = socket(AF_INET, SOCK_STREAM)
# 目的信息
server_ip = input(“請輸入服務器ip:”)
server_port = int(input(“請輸入服務器port:”))
# 鏈接服務器
tcp_client_socket.connect((server_ip, server_port))
# 輸入需要下載的文件名
file_name = input(“請輸入要下載的文件名:”)
# 發送文件下載請求
tcp_client_socket.send(file_name.encode(“utf-8”))
# 接收對方發送過來的數據,最大接收1024個字節(1K)
recv_data = tcp_client_socket.recv(1024)
# print(‘接收到的數據爲:’, recv_data.decode(‘utf-8’))
# 如果接收到數據再創建文件,否則不創建
if recv_data:
with open("[接收]"+file_name, “wb”) as f:
f.write(recv_data)
# 關閉套接字
tcp_client_socket.close()
if name == “main”:
main()