一.楔子
你現在已經學會了寫python代碼,假如你寫了兩個python文件a.py和b.py,分別去運行,你就會發現,這兩個python的文件分別運行的很好。但是如果這兩個程序之間想要傳遞一個數據,你要怎么做呢?
這個問題以你現在的知識就可以解決了,我們可以創建一個文件,把a.py想要傳遞的內容寫到文件中,然后b.py從這個文件中讀取內容就可以了。
?
但是當你的a.py和b.py分別在不同電腦上的時候,你要怎么辦呢?
類似的機制有計算機網盤,qq等等。我們可以在我們的電腦上和別人聊天,可以在自己的電腦上向網盤中上傳、下載內容。這些都是兩個程序在通信。
?
二.軟件開發的架構
我們了解的涉及到兩個程序之間通訊的應用大致可以分為兩種:
第一種是應用類:qq、微信、網盤、優酷這一類是屬于需要安裝的桌面應用
第二種是web類:比如百度、知乎、博客園等使用瀏覽器訪問就可以直接使用的應用
這些應用的本質其實都是兩個程序之間的通訊。而這兩個分類又對應了兩個軟件開發的架構~
1.C/S架構
C/S即:Client與Server ,中文意思:客戶端與服務器端架構,這種架構也是從用戶層面(也可以是物理層面)來劃分的。
這里的客戶端一般泛指客戶端應用程序EXE,程序需要先安裝后,才能運行在用戶的電腦上,對用戶的電腦操作系統環境依賴較大。
?
?
2.B/S架構
B/S即:Browser與Server,中文意思:瀏覽器端與服務器端架構,這種架構是從用戶層面來劃分的。
Browser瀏覽器,其實也是一種Client客戶端,只是這個客戶端不需要大家去安裝什么應用程序,只需在瀏覽器上通過HTTP請求服務器端相關的資源(網頁資源),客戶端Browser瀏覽器就能進行增刪改查。
?
三.網絡基礎
網絡基礎
1.一個程序如何在網絡上找到另一個程序?
首先,程序必須要啟動,其次,必須有這臺機器的地址,我們都知道我們人的地址大概就是國家\省\市\區\街道\樓\門牌號這樣字。那么每一臺聯網的機器在網絡上也有自己的地址,它的地址是怎么表示的呢?
就是使用一串數字來表示的,例如:100.4.5.6
IP地址是指互聯網協議地址(英語:Internet Protocol Address,又譯為網際協議地址),是IP Address的縮寫。IP地址是IP協議提供的一種統一的地址格式,它為互聯網上的每一個網絡和每一臺主機分配一個邏輯地址,以此來屏蔽物理地址的差異。IP地址是一個32位的二進制數,通常被分割為4個“8位二進制數”(也就是4個字節)。IP地址通常用“點分十進制”表示成(a.b.c.d)的形式,其中,a,b,c,d都是0~255之間的十進制整數。例:點分十進IP地址(100.4.5.6),實際上是32位二進制數(01100100.00000100.00000101.00000110)。
?
"端口"是英文port的意譯,可以認為是設備與外界通訊交流的出口。
因此ip地址精確到具體的一臺電腦,而端口精確到具體的程序。
2.osi七層模型
引子
須知一個完整的計算機系統是由硬件、操作系統、應用軟件三者組成,具備了這三個條件,一臺計算機系統就可以自己跟自己玩了(打個單機游戲,玩個掃雷啥的)
如果你要跟別人一起玩,那你就需要上網了,什么是互聯網?
互聯網的核心就是由一堆協議組成,協議就是標準,比如全世界人通信的標準是英語,如果把計算機比作人,互聯網協議就是計算機界的英語。所有的計算機都學會了互聯網協議,那所有的計算機都就可以按照統一的標準去收發信息從而完成通信了。
osi七層模型
人們按照分工不同把互聯網協議從邏輯上劃分了層級:
?
?
3.socket概念
socket層
理解socket
Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。
?
其實站在你的角度上看,socket就是一個模塊。我們通過調用模塊中已經實現的方法建立兩個進程之間的連接和通信。
也有人將socket說成ip+port,因為ip是用來標識互聯網中的一臺主機的位置,而port是用來標識這臺機器上的一個應用程序。
所以我們只要確立了ip和port就能找到一個應用程序,并且使用socket模塊來與之通信。
3.套接字(socket)的發展史
套接字起源于 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。 因此,有時人們也把套接字稱為“伯克利套接字”或“BSD 套接字”。一開始,套接字被設計用在同 一臺主機上多個應用程序之間的通訊。這也被稱進程間通訊,或 IPC。套接字有兩種(或者稱為有兩個種族),分別是基于文件型的和基于網絡型的。?
基于文件類型的套接字家族
套接字家族的名字:AF_UNIX
unix一切皆文件,基于文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,可以通過訪問同一個文件系統間接完成通信
基于網絡類型的套接字家族
套接字家族的名字:AF_INET
(還有AF_INET6被用于ipv6,還有一些其他的地址家族,不過,他們要么是只用于某個平臺,要么就是已經被廢棄,或者是很少被使用,或者是根本沒有實現,所有地址家族中,AF_INET是使用最廣泛的一個,python支持很多種地址家族,但是由于我們只關心網絡編程,所以大部分時候我么只使用AF_INET)
?
4.tcp協議和udp協議
TCP(Transmission Control Protocol)可靠的、面向連接的協議(eg:打電話)、傳輸效率低全雙工通信(發送緩存&接收緩存)、面向字節流。使用TCP的應用:Web瀏覽器;電子郵件、文件傳輸程序。
UDP(User Datagram Protocol)不可靠的、無連接的服務,傳輸效率高(發送前時延小),一對一、一對多、多對一、多對多、面向報文,盡最大努力服務,無擁塞控制。使用UDP的應用:域名系統?(DNS);視頻流;IP語音(VoIP)。
我知道說這些你們也不懂,直接上圖。
四.套接字(socket)初使用
基于TCP協議的socket
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)
?
?qq聊天
#_*_coding:utf-8_*_ import socket ip_port=('127.0.0.1',8081) udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) udp_server_sock.bind(ip_port)while True:qq_msg,addr=udp_server_sock.recvfrom(1024)print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))back_msg=input('回復消息: ').strip()udp_server_sock.sendto(back_msg.encode('utf-8'),addr)server
?
#_*_coding:utf-8_*_ import socket BUFSIZE=1024 udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)qq_name_dic={'金老板':('127.0.0.1',8081),'哪吒':('127.0.0.1',8081),'egg':('127.0.0.1',8081),'yuan':('127.0.0.1',8081), }while True:qq_name=input('請選擇聊天對象: ').strip()while True:msg=input('請輸入消息,回車發送,輸入q結束和他的聊天: ').strip()if msg == 'q':breakif not msg or not qq_name or qq_name not in qq_name_dic:continueudp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))udp_client_socket.close()client
?
時間服務器
# _*_coding:utf-8_*_ from socket import * from time import strftimeip_port = ('127.0.0.1', 9000) bufsize = 1024tcp_server = socket(AF_INET, SOCK_DGRAM) tcp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) tcp_server.bind(ip_port)while True:msg, addr = tcp_server.recvfrom(bufsize)print('===>', msg)if not msg:time_fmt = '%Y-%m-%d %X'else:time_fmt = msg.decode('utf-8')back_msg = strftime(time_fmt)tcp_server.sendto(back_msg.encode('utf-8'), addr)tcp_server.close()server
#_*_coding:utf-8_*_ from socket import * ip_port=('127.0.0.1',9000) bufsize=1024tcp_client=socket(AF_INET,SOCK_DGRAM)while True:msg=input('請輸入時間格式(例%Y %m %d)>>: ').strip()tcp_client.sendto(msg.encode('utf-8'),ip_port)data=tcp_client.recv(bufsize)client
?
socket參數的詳解
socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)
創建socket對象的參數說明:
family | 地址系列應為AF_INET(默認值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。 (AF_UNIX 域實際上是使用本地 socket 文件來通信) |
type | 套接字類型應為SOCK_STREAM(默認值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。 SOCK_STREAM?是基于TCP的,有保障的(即能保證數據正確傳送到對方)面向連接的SOCKET,多用于資料傳送。? SOCK_DGRAM?是基于UDP的,無保障的面向消息的socket,多用于在網絡上發廣播信息。 |
proto | 協議號通常為零,可以省略,或者在地址族為AF_CAN的情況下,協議應為CAN_RAW或CAN_BCM之一。 |
fileno | 如果指定了fileno,則其他參數將被忽略,導致帶有指定文件描述符的套接字返回。 與socket.fromfd()不同,fileno將返回相同的套接字,而不是重復的。 這可能有助于使用socket.close()關閉一個獨立的插座。 |