網絡編程
什麼是網絡編程
網絡編程最主要的工作就是在發送端把信息通過規定好的協議進行組裝包,在接收端按照規定好的協議把包進行解析,從而提取出
對應的信息,達到通信的目的。
軟件開發的架構
我們經常使用的程序之間的通信大致分爲兩種:
- qq,微信,網盤屬於需要安裝的桌面應用
- 百度,知乎,搜狗等需要用瀏覽器訪問的web類.
這兩種通信方式就是兩種軟件開發架構.
c/s架構
C/S的全稱是client和serve,中文就是客戶端和服務端架構,這種架構也是從用戶層面(物理層面)來劃分的.這裏的客戶端是需要用戶在電腦或者手機上先安裝才能運行的.
B/S架構
B/S就是Browser與Server 中文意思就是:瀏覽器端與服務端架構,這種架構是從用戶層面來劃分的.
Browser瀏覽器,其實也是一種Client客戶端,但是不用安裝什麼應用程序.
網絡基礎
早期的時候兩個計算機要通信的話需要一根網線連接,稱爲聯機
但是這種方法只能是一對一的交流,不能同時多個機子交流所以後邊出現了以太網:局域網與交換機
廣播
廣播是主機一對多的一種通訊模式,一臺主機要發送一條消息,網絡會不選擇的把這條信息都發給所有電腦,就像有限電視一樣.在數據網絡中其被限制在二層交換機的局域網範圍內,禁止廣播數據穿過路由器.
ip地址與ip協議
規定萬羅地址的協議叫ip協議,它定義的地址稱之爲ip地址,廣泛採用的是V4就是ipv4的版本,它規定網絡地址由32位2進製表示.
範圍:0.0.0.0-255.255.255.255
mac地址
在每塊網卡出廠時都會燒製上世界上唯一的mac地址,長度爲48位的2進制,通常由12位16進制數表示.是發送端和接受端的地址.
ARP協議
arp協議是查詢IP地址和MAC地址的對應關係,被稱爲地址解析協議,是根據IP地址獲取物理地址的一個TCP/ip協議.
TCP協議和UDP協議
應用程序間怎麼通信呢?計算機之間的通信可以靠IP地址和MAC地址來幫我們確定唯一的一臺機器,TCP和UDP就是幫我們找到一臺機器上的軟件.
端口
一臺擁有IP地址的主機可以提供很多服務,這些服務雖然是同一個IP但是會有端口來區分不同服務的。
TCP協議
當應用程序希望通過程序通信時,它會發送一個通信請求,這個請求必須送到一個確切的地方,在達成共識返回一個已經連接時,TCP將在兩個應用程序之間建立一個雙全工的通信。
當要結束時,雙方都返回一個斷開連接的請求和回執,一共四條命令。
TCP三次握手
TCP是因特網中的傳輸層協議,使用三次握手協議建立連接。當主動方發出SYN連接請求後,等待對方回答·SYN+ACK[1]·,並最終對對方的 SYN 執行 ACK 確認。這種建立連接的方法可以防止產生錯誤的連接。[1]
TCP三次握手的過程如下:
客戶端發送·SYN(SEQ=x)·報文給服務器端,進入SYN_SEND狀態。
服務器端收到SYN報文,迴應一個·SYN (SEQ=y)··ACK(ACK=x+1)·報文,進入·SYN_RECV·狀態。
客戶端收到服務器端的SYN報文,迴應一個·ACK(ACK=y+1)·報文,進入Established狀態。
三次握手完成,TCP客戶端和服務器端成功地建立連接,可以開始傳輸數據了。
TCP四次揮手
建立一個連接需要三次握手,而終止一個連接要經過四次握手,這是由TCP的半關閉(half-close)造成的。
(1) 某個應用進程首先調用close,稱該端執行“主動關閉”(active close)。該端的TCP於是發送一個FIN分節,表示數據發送完畢。
(2) 接收到這個FIN的對端執行 “被動關閉”(passive close),這個FIN由TCP確認。
注意:FIN的接收也作爲一個文件結束符(end-of-file)傳遞給接收端應用進程,放在已排隊等候該應用進程接收的任何其他數據之後,因爲,FIN的接收意味着接收端應用進程在相應連接上再無額外數據可接收。
(3) 一段時間後,接收到這個文件結束符的應用進程將調用close關閉它的套接字。這導致它的TCP也發送一個FIN。
(4) 接收這個最終FIN的原發送端TCP(即執行主動關閉的那一端)確認這個FIN。[1]
既然每個方向都需要一個FIN和一個ACK,因此通常需要4個分節。
注意:
(1) “通常”是指,某些情況下,步驟1的FIN隨數據一起發送,另外,步驟2和步驟3發送的分節都出自執行被動關閉那一端,有可能被合併成一個分節。[2]
(2) 在步驟2與步驟3之間,從執行被動關閉一端到執行主動關閉一端流動數據是可能的,這稱爲“半關閉”(half-close)。
(3) 當一個Unix進程無論自願地(調用exit或從main函數返回)還是非自願地(收到一個終止本進程的信號)終止時,所有打開的描述符都被關閉,這也導致仍然打開的任何TCP連接上也發出一個FIN。
無論是客戶還是服務器,任何一端都可以執行主動關閉。通常情況是,客戶執行主動關閉,但是某些協議,例如,HTTP/1.0卻由服務器執行主動關閉。[2]
UDP協議
當應用程序希望通過UDP與一個應用程序通信時,傳輸數據之前源端和終端不建立連接。
當它想傳送時就簡單地去抓取來自應用程序的數據,並儘可能快地把它扔到網絡上。
TCP和UDP對比
TCP---傳輸控制協議,提供的是面向連接、可靠的字節流服務。當客戶和服務器彼此交換數據前,必須先在雙方之間建立一個TCP連接,之後才能傳輸數據。TCP提供超時重發,丟棄重複數據,檢驗數據,流量控制等功能,保證數據能從一端傳到另一端。
UDP---用戶數據報協議,是一個簡單的面向數據報的運輸層協議。UDP不提供可靠性,它只是把應用程序傳給IP層的數據報發送出去,但是並不能保證它們能到達目的地。由於UDP在傳輸數據報前不用在客戶和服務器之間建立一個連接,且沒有超時重發等機制,故而傳輸速度很快
TCP(Transmission Control Protocol)可靠的、面向連接的協議(eg:打電話)、傳輸效率低全雙工通信(發送緩存&接收緩存)、面向字節流。使用TCP的應用:Web瀏覽器;電子郵件、文件傳輸程序。
UDP(User Datagram Protocol)不可靠的、無連接的服務,傳輸效率高(發送前時延小),一對一、一對多、多對一、多對多、面向報文,盡最大努力服務,無擁塞控制。使用UDP的應用:域名系統 (DNS);視頻流;IP語音(VoIP)。
osi七層模型
互聯網的核心就是由一堆協議組成,協議就是標準,比如全世界人通信的標準是英語,如果把計算機比作人,互聯網協議就是計算機界的英語。所有的計算機都學會了互聯網協議,那所有的計算機都就可以按照統一的標準去收發信息從而完成通信了。
python網絡編程
Python提供了兩個級別訪問的網絡服務:
- 低級別的網絡服務支持基本的
socket
,它提供了標準的BSD Socket API,可以訪問底層的操作系統Socket接口的全部方法. - 高級別的網絡服務模塊
socketserver
,它提供了服務器中心類,可以簡化網絡服務器的開發.
什麼是socket
Socket又稱"套接字",應用程序通常通過"套接字"向網絡發出請求或者應答網絡請求,使主機間或者一臺計算機上的進程間可以通訊。
socket層
socket()函數
Python 中,我們用 socket()函數來創建套接字,語法格式如下:
socket.socket([family[, type[, proto]]])
參數
- family:套接字家族可以使AF_UNIX或者AF_INET
- type: 套接字類型可以根據是面向連接的還是非連接分爲SOCK_STREAM或SOCK_DGRAM
- protocol:一般不填默認0
socket對象內建的方法
函數 | 描述 |
---|---|
s.bind() | 綁定地址(host,port)到套接字, 在AF_INET下,以元組(host,port)的形式表示地址。 |
s.listen() | 開始TCP監聽。backlog指定在拒絕連接之前,操作系統可以掛起的最大連接數量。該值至少爲1,大部分應用程序設爲5就可以了。 |
s.accept() | 被動接受TCP客戶端連接,(阻塞式)等待連接的到來 |
客戶端套接字 | |
s.connect() | 主動初始化TCP服務器連接,。一般address的格式爲元組(hostname,port),如果連接出錯,返回socket.error錯誤。 |
s.connect_ex() | connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常 |
公共用途的套接字函數 | |
s.recv() | 接收TCP數據,數據以字符串形式返回,bufsize指定要接收的最大數據量。flag提供有關消息的其他信息,通常可以忽略。 |
s.send() | 發送TCP數據,將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。 |
s.sendall() | 完整發送TCP數據,完整發送TCP數據。將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。 |
s.recvfrom() | 接收UDP數據,與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。 |
s.sendto() | 發送UDP數據,將數據發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數 |
s.close() | 關閉套接字 |
s.getpeername() | 返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。 |
s.getsockname() | 返回套接字自己的地址。通常是一個元組(ipaddr,port) |
s.setsockopt(level,optname,value) | 設置給定套接字選項的值。 |
s.getsockopt(level,optname[.buflen]) | 返回套接字選項的值。 |
s.settimeout(timeout) | 設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值爲None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因爲它們可能用於連接的操作(如connect()) |
s.gettimeout() | 返回當前超時期的值,單位是秒,如果沒有設置超時期,則返回None。 |
s.fileno() | 返回套接字的文件描述符。 |
s.setblocking(flag) | 如果flag爲0,則將套接字設爲非阻塞模式,否則將套接字設爲阻塞模式(默認值)。非阻塞模式下,如果調用recv()沒有發現任何數據,或send()調用無法立即發送數據,那麼將引起socket.error異常。 |
s.makefile() | 創建一個與該套接字相關連的文件 |
socket的使用
基於TCP連接
tcp基於的連接,必須先啓動服務端,再去啓動客服端
server端 服務器端
import socket
sk = socket.socket()
sk.bind(('127.0.0.1',8898)) #把地址綁定到套接字
sk.listen() #監聽鏈接
conn,addr = sk.accept() #接受客戶端鏈接
ret = conn.recv(1024) #接收客戶端信息
print(ret) #打印客戶端信息
conn.send(b'hi') #向客戶端發送信息
conn.close() #關閉客戶端套接字
sk.close() #關閉服務器套接字(可選
client端 客戶端
import socket
sk = socket.socket() # 創建客戶套接字
sk.connect(('127.0.0.1',8898)) # 嘗試連接服務器
sk.send(b'hello!')
ret = sk.recv(1024) # 對話(發送/接收)
print(ret)
sk.close() # 關閉客戶套接字
如果在重啓服務器的時候碰到下面情況:
解決方案
#加入一條socket配置,重用ip和端口
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
sk.bind(('127.0.0.1',8898)) #把地址綁定到套接字
sk.listen() #監聽鏈接
conn,addr = sk.accept() #接受客戶端鏈接
ret = conn.recv(1024) #接收客戶端信息
print(ret) #打印客戶端信息
conn.send(b'hi') #向客戶端發送信息
conn.close() #關閉客戶端套接字
sk.close() #關閉服務器套接字(可選)
基於UDP協議的socket
udp是無鏈接的,啓動服務之後可以直接接受消息,不需要提前建立鏈接
server端
import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM) #創建一個服務器的套接字
udp_sk.bind(('127.0.0.1',9000)) #綁定服務器套接字
msg,addr = udp_sk.recvfrom(1024)
print(msg)
udp_sk.sendto(b'hi',addr) # 對話(接收與發送)
udp_sk.close() # 關閉服務器套接字
client端
import socket
ip_port=('127.0.0.1',9000)
udp_sk=socket.socket(type=socket.SOCK_DGRAM)
udp_sk.sendto(b'hello',ip_port)
back_msg,addr=udp_sk.recvfrom(1024)
print(back_msg.decode('utf-8'),addr)