一、前言
我們現在已經學習了python基礎編程,我們知道在本機中,可以編寫兩個程序,通過讀寫同一主機磁盤中固定內存,可以實現兩個程序之間的通信,單機程序通信過程如下:
而如果兩台主機之間要實現通信,就需要網絡編程。
網絡通信就像寄信件,是信息與數據的交換,而在生活中我們寄信件,信件也不是從我們手里瞬間到收件人手里,每一次信件通信,都會經歷這樣幾個固定流程:寫信、裝信封、投到郵箱、郵局取件、運輸到目的地郵局、目的地郵局根據詳細地址派送、收件人收件、拆信封、讀信。
網絡通信也是同樣的道理,數據的傳輸總有一定的流程:發送端程序將數據打包,給數據包印上目標地址,將數據包交給網關,通過路由轉發到達目的網絡,目的網絡網關在根據詳細地址分發、目的主機接收數據、拆包、讀數據。
二.軟件開發的架構
我們了解的涉及到兩個程序之間通訊的應用大致可以分為兩種:
第一種是應用類:qq、微信、網盤、優酷這一類是屬於需要安裝的桌面應用
第二種是web類:比如百度、知乎、博客園等使用瀏覽器訪問就可以直接使用的應用
這些應用的本質其實都是兩個程序之間的通訊。而這兩個分類又對應了兩個軟件開發的架構~
1.C/S架構
C/S即:Client與Server ,中文意思:客戶端與服務器端架構,這種架構也是從用戶層面(也可以是物理層面)來划分的。
這里的客戶端一般泛指客戶端應用程序EXE,程序需要先安裝后,才能運行在用戶的電腦上,對用戶的電腦操作系統環境依賴較大。
2.B/S架構
B/S即:Browser與Server,中文意思:瀏覽器端與服務器端架構,這種架構是從用戶層面來划分的。
Browser瀏覽器,其實也是一種Client客戶端,只是這個客戶端不需要大家去安裝什么應用程序,只需在瀏覽器上通過HTTP請求服務器端相關的資源(網頁資源),客戶端Browser瀏覽器就能進行增刪改查。

三、網絡編程理論基礎
網絡協議處理 + ip 地址標注 + 端口限制 ——>>>形成成熟的互聯網計算機系統
3.1 網絡概述
3.1.1 網絡的由來
早期的計算機都是獨立的一台一台以數據運算為主的機器,隨着時代的發展,運算的數據的共享需求變得迫切,為了解決這一個問題,就有了網絡的產生,通過物理路徑(有線網或無線網)將多台計算機連接起來組成一個互聯網計算機平台,實現多台計算機之間特定的數據交互模式。
3.1.2 IP地址的由來
和作用在計算機可以進行數據交互的基礎上,又出現了新問題,如果主機要與某一台主機進行通信,如果所連接的主機數量少的情況下,可以通過對主機進行編號來識別要發送給哪一台主機,但是如果連接數量過大時,就是出現數據溢出或錯誤的情況的,給數據交互帶來很大的麻煩。為了解決這個問題,能夠在眾多計算機中找到特定的計算機,int cerf 在實驗室模擬階段使用了 32 位標記的網絡地址協議[internet protocal],用於標識網絡上唯一的一台計算機,也就是后來的 ip 地址;
根據 ip 地址,按照網絡主機數量【限制】,網絡可以分為:
1. 局域網
2. 城域網
3. 廣域網—>>全球網絡
3.1.3 端口port的由來
和作用在通過 IP 地址確定了網絡上的某個具體主機之后,具體的數據通信主要是通過工作在計算機中的軟件執行的,不同的軟件通信很容易造成問題,那么問題又來了,隨着計算行業的發展,計算機中會安裝各種各樣的應用軟件,當主機A向主機B發送信息時,具體會由主機B上的哪個程序接收呢?由此計算機中出現了端口port的概念,端口port主要用於區別不同軟件的通信渠道,用於正確的將數據通過指定的端口渠道傳輸給對應的軟件!
3.1.4 網絡協議
網絡協議的發展過程中,比較混亂,所以有一個非盈利性質的民間組織推出了網絡協議公共標准,任何計算機的廠商,制造的計算機必須符合這樣的標准才允許上市。
這個組織稱為:國際標准化組織/歐洲計算機制造協會聯盟組織/ISO。
ISO組織推出了網絡協議標准模型:開放互聯系統模型(OSI/RM模型)
網絡協議處理 + ip 地址標注 + 端口限制 ——>>>形成成熟的互聯網計算機系統
3.2 IP&PORT
首先,程序必須要啟動,其次,必須有這台機器的地址,我們都知道我們人的地址大概就是國家\省\市\區\街道\樓\門牌號這樣字。那么每一台聯網的機器在網絡上也有自己的地址,它的地址是怎么表示的呢?
3.2.1 ip地址
IP 地址根據使用的用戶性質,主要分為 5 類 IP 地址(A類、B類、C類、D類、E類)
IP地址由兩部分組成,即網絡地址和主機地址。網絡地址表示其屬於互聯網的哪一個網絡,主機地址表示其屬於該網絡中的哪一台主機。二者是主從關系。網絡地址和主機地址的區分主要通過子網掩碼進行划分
IP地址的四大類型標識的是網絡中的某台主機。IPv4的地址長度為32位,共4個字節,但實際中我們用點分十進制記法。
圖 3.1 五類互聯網地址
IP地址根據網絡號和主機號來分,分為A、B、C三類及特殊地址D、E。 全0和全1的都保留不用。
A類:(1.0.0.0-126.0.0.0)(默認子網掩碼:255.0.0.0或 0xFF000000)第一個字節為網絡號,后三個字節為主機號。該類IP地址的最前面為“0”,所以地址的網絡號取值於1~126之間。一般用於大型網絡。
B類:(128.0.0.0-191.255.0.0)(默認子網掩碼:255.255.0.0或0xFFFF0000)前兩個字節為網絡號,后兩個字節為主機號。該類IP地址的最前面為“10”,所以地址的網絡號取值於128~191之間。一般用於中等規模網絡。
C類:(192.0.0.0-223.255.255.0)(子網掩碼:255.255.255.0或 0xFFFFFF00)前三個字節為網絡號,最后一個字節為主機號。該類IP地址的最前面為“110”,所以地址的網絡號取值於192~223之間。一般用於小型網絡。
D類:是多播地址。該類IP地址的最前面為“1110”,所以地址的網絡號取值於224~239之間。一般用於多路廣播用戶 。
E類:是保留地址。該類IP地址的最前面為“1111”,所以地址的網絡號取值於240~255之間。
在IP地址3種主要類型里,各保留了3個區域作為私有地址,其地址范圍如下:
A類地址:10.0.0.0~10.255.255.255
B類地址:172.16.0.0~172.31.255.255
C類地址:192.168.0.0~192.168.255.255
回送地址:127.0.0.1。 也是本機地址,等效於localhost或本機IP。一般用於測試使用。例如:ping 127.0.0.1來測試本機TCP/IP是否正常。
IP地址和子網掩碼求與運算就可以獲得網絡地址,確定該子網在互聯網中的身份。主機地址可以確定該子網中具體的主機。
3.2.2 "端口"是英文port的意譯,可以認為是設備與外界通訊交流的出口
在通過 IP 地址確定了網絡上的某個具體主機之后,具體的數據通信主要是通過工作在計算機中的軟件執行的,不同的軟件通信很容易造成問題。
不同的端口號分類
計算機中的端口號的范圍是 0 ~ 65535 之間
端口好根據使用場景,一般區分為公用端口/動態端口/保留端口
公用端口:0 ~ 1023
動態端口:1024 ~ 65535
保留端口:一般是UNIX系統中超級用戶進程分配保留端口號
3.3 協議
3.3.1 協議
如果你要跟別人一起玩,那你就需要上網了,什么是互聯網?
互聯網的核心就是由一堆協議組成,協議就是標准,比如全世界人通信的標准是英語,如果把計算機比作人,互聯網協議就是計算機界的英語。所有的計算機都學會了互聯網協議,那所有的計算機都就可以按照統一的標准去收發信息從而完成通信了。
3.3.2 osi七層模型
人們按照分工不同把互聯網協議從邏輯上划分了層級:

分層:
應用層 (Application): 網絡服務與最終用戶的一個接口。
協議有:HTTP FTP TFTP SMTP SNMP DNS TELNET HTTPS POP3 DHCP
表示層(Presentation Layer): 數據的表示、安全、壓縮。(在五層模型里面已經合並到了應用層)
格式有,JPEG、ASCll、DECOIC、加密格式等
會話層(Session Layer): 建立、管理、終止會話。(在五層模型里面已經合並到了應用層)
對應主機進程,指本地主機與遠程主機正在進行的會話
傳輸層 (Transport): 定義傳輸數據的協議端口號,以及流控和差錯校驗。
協議有:TCP UDP,數據包一旦離開網卡即進入網絡傳輸層
網絡層 (Network): 進行邏輯地址尋址,實現不同網絡之間的路徑選擇。
協議有:ICMP IGMP IP(IPV4 IPV6) ARP RARP
數據鏈路層 (Link): 建立邏輯連接、進行硬件地址尋址、差錯校驗等功能。(由底層網絡定義協議)
將比特組合成字節進而組合成幀,用MAC地址訪問介質,錯誤發現但不能糾正。
物理層(Physical Layer): 建立、維護、斷開物理連接。(由底層網絡定義協議)
3.3.3 協議簇
協議通常指代單獨的一個協議,協議族通常指代互相關聯的一組協議,協議棧指代某一組互相關聯的協議和他們所屬的 OSI 模型的層級結構。
四、socket編程
4.1 socket層

圖 4.1 socket層是介於應用層和傳輸層之間
理解socket:Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。所以,我們無需深入理解tcp/udp協議,socket已經為我們封裝好了,我們只需要遵循socket的規定去編程,寫出的程序自然就是遵循tcp/udp標准的。
注意:
1 將socket說成ip+port,ip是用來標識互聯網中的一台主機的位置,而port是用來標識這台機器上的一個應用程序,ip地址是配置到網卡上的,而port是應用程序開啟的,ip與port的綁定就標識了互聯網中獨一無二的一個應用程序
2 而程序的pid是同一台機器上不同進程或者線程的標識(Google Chrome會有多個PID)
4.2 套接字
4.2.1 套接字的發展歷史和分類
套接字家族的名字:AF_UNIX
unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,可以通過訪問同一個文件系統間接完成通信
基於網絡類型的套接字家族
套接字家族的名字:AF_INET
(還有AF_INET6被用於ipv6,還有一些其他的地址家族,不過,他們要么是只用於某個平台,要么就是已經被廢棄,或者是很少被使用,或者是根本沒有實現,所有地址家族中,AF_INET是使用最廣泛的一個,python支持很多種地址家族,但是由於我們只關心網絡編程,所以大部分時候我們只使用AF_INET)
4.2.2 語法
套接字模型對象,為了能明確的表示網絡中一台數據交互的主機,需要通過 IP 地址尋址確定主機位置,通過 PORT 端口號確定主機交互接口.
在網絡套接字交互過程中,出現了兩種類型的套接字模型:
a.面向連接的套接字模型
b.面向無連接的套接字模型
python 中提供的網絡套接字,主要包含在 socket 模塊中,socket模塊提供了socket函數創建套接字。
基本語法結構:
基本語法:
socket.socket(socket_family, socket_type, protocal=0)
參數:
socket_family: socket 地址家族, AF_UNIX/AF_LOCAL 或者 AF_INET
socket_type: socket 連接類型:1)面向連接的(SOCK_STREAM) 2)面向無連接的(SOCK_DGRAME)
protocal:傳輸協議,一般不用設置,使用默認值進行自動匹配就好
4.3 tcp協議和udp協議
TCP(Transmission Control Protocol)可靠的、面向連接的協議(eg:打電話)、傳輸效率低全雙工通信(發送緩存&接收緩存)、面向字節流。使用TCP的應用:Web瀏覽器;電子郵件、文件傳輸程序。
UDP(User Datagram Protocol)不可靠的、無連接的服務,傳輸效率高(發送前時延小),一對一、一對多、多對一、多對多、面向報文,盡最大努力服務,無擁塞控制。使用UDP的應用:域名系統 (DNS);視頻流;IP語音(VoIP)。

4.4 實戰演示
關於常用函數簡介:
socket()模塊函數用法:
創建socket對象socket.socket(socket_family, socket_type, protocal=0)
獲取tcp/ip套接字獲取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
服務端套接字函數
s.bind() 綁定(主機,端口號)到套接字
s.listen() 開始TCP監聽
s.accept() 被動接受TCP客戶的連接,(阻塞式)等待連接的到來
客戶端套接字函數
s.connect() 主動初始化TCP服務器連接
s.connect_ex() connect() 函數的擴展版本,出錯時返回出錯碼,而不是拋出異常
公共用途的套接字函數
s.recv() 接收TCP數據
s.send() 發送TCP數據(send在待發送數據量大於己端緩存區剩余空間時,數據丟失,不會發完)
s.sendall() 發送完整的TCP數據(本質就是循環調用send,sendall在待發送數據量大於己端緩存區剩余空間時,數據不丟失,循環調用send直到發完)
s.recvfrom() 接收UDP數據
s.sendto() 發送UDP數據
s.getpeername() 連接到當前套接字的遠端的地址
s.getsockname() 當前套接字的地址
s.getsockopt() 返回指定套接字的參數
s.setsockopt() 設置指定套接字的參數
s.close() 關閉套接字
面向鎖的套接字方法
s.setblocking() 設置套接字的阻塞與非阻塞模式
s.settimeout() 設置阻塞套接字操作的超時時間
s.gettimeout() 得到阻塞套接字操作的超時時間
面向文件的套接字的函數
s.fileno() 套接字的文件描述符
s.makefile() 創建一個與該套接字相關的文件
4.4.1 TCP編程
4.4.1.1 建立TCP服務器連接的6個步驟
1、創建socket對象。調用socket構造函數:
基本語法: socket.socket(socket_family, socket_type, protocal=0)
參數:
socket_family: socket 地址家族, AF_UNIX/AF_LOCAL 或者 AF_INET
socket_type:socket 連接類型:1)面向連接的(SOCK_STREAM) 2)面向無連接的(SOCK_DGRAME) protocal:
傳輸協議,一般不用設置,使用默認值進行自動匹配就好
如果IP地址為 0.0.0.0 代表本機的任意一個IP 端口 0--1024 為系統保留
IP地址為localhost代表本地主機,指這台計算機,相對應的ip地址為127.0.0.1
3、綁定后,必須准備好套接字,以便接受連接請求:
4.4.1.2 建立TCP客服端

1 import socket 2 # 定義服務器信息 3 print('初始化服務器主機信息') 4 HOST = socket.gethostname() # 本地主機,指這台計算機,相對應的ip地址為127.0.0.1 5 PORT = 5000 #端口 0--1024 為系統保留 6 ADDRESS = (HOST, PORT) 7 BUFFER = 1024 #數據發送和接受的最大數據大小 8 9 # 創建TCP服務socket對象 10 print("初始化服務器主機套接字對象......") 11 server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) 12 # 面向網絡的套接字:通過網絡進行數據交互, TCP協議,server就是socket的實例 13 14 # 綁定主機信息 15 print('綁定的主機信息......') 16 server.bind(ADDRESS) #元組,相當於一個參數 17 18 # 啟動服務器 一個只能接受一個客戶端請求,可以有10個請求排隊 19 print("開始啟動服務器......") 20 server.listen(10) 21 22 #等待連接 23 while True: 24 # 等待來自客戶端的連接 25 print('等待客戶端連接') 26 conn, addr = server.accept() # 等電話 27 print('連接的客服端套接字對象為:{}\n客服端的IP地址(撥進電話號碼):{}'.format(conn, addr)) 28 # 發送給客戶端的數據 29 #server.send("歡迎訪問服務器".encode('utf-8')) 30 31 #收發信息,可以收發多次 32 while True: 33 # 接收客戶端發送的數據 34 print("等待客戶端發送信息:") 35 msg = conn.recv(BUFFER) 36 try: 37 print("client客戶端:{}".format(msg.decode("utf-8")))

1 import socket 2 # 定義要連接的服務器信息 3 HOST = socket.gethostname() 4 PORT = 5000 5 ADDRESS = (HOST, PORT) 6 7 #創建客戶端套接字對象 8 client = socket.socket()# 相當於聲明socket類型,同時生成socket鏈接對象,默認值 9 10 #連接服務器 11 client.connect(ADDRESS) 12 print('歡迎連接服務器') 13 # 和服務器進行數據交互 14 while True: 15 # 給服務器發送消息 16 info = input("請輸入要發送的信息:") 17 client.send(info.encode("utf-8")) 18 19 if info == 'bye': 20 client.close() 21 print("客戶端退出") 22 break 23 #接受服務端信息 24 print("等待服務端發送信息:") 25 data = client.recv(1024) 26 try: 27 print("server服務端端:{}".format(data.decode("utf-8"))) 28 29 except Exception: 30 print("server服務端端:{}".format(data.decode("gbk")))
4.4.2 UDP編程
udp服務端
1 ss = socket() #創建一個服務器的套接字 2 ss.bind() #綁定服務器套接字 3 inf_loop: #服務器無限循環 4 cs = ss.recvfrom()/ss.sendto() # 對話(接收與發送) 5 ss.close()
udp客戶端
1 cs = socket() # 創建客戶套接字 2 comm_loop: # 通訊循環 3 cs.sendto()/cs.recvfrom() # 對話(發送/接收) 4 cs.close() # 關閉客戶套接字

1 from socket import * 2 3 ip_port = ('127.0.0.1', 8080) 4 buffer_size = 1024 5 6 udp_server = socket(AF_INET, SOCK_DGRAM) # 數據報套接字 7 udp_server.bind(ip_port) 8 9 while True: 10 data, addr = udp_server.recvfrom(buffer_size) 11 print(data.decode('utf-8')) 12 print('data from', addr) 13 udp_server.sendto(data.upper(), addr) # upper() 小寫變大寫

1 from socket import * 2 ip_port = ('127.0.0.1', 8080) #服務端IP+端口 3 buffer_size = 1024 4 5 udp_client = socket(AF_INET, SOCK_DGRAM) #udp數據報套接字 6 7 while True: 8 msg = input('>>:').strip() 9 udp_client.sendto(msg.encode('utf-8'), ip_port) 10 #數據,ip地址+端口 11 data, addr = udp_client.recvfrom(buffer_size) 12 print(data.decode('utf-8')) 13 print('data from %s',addr)