1、網絡協議html
客戶端/服務器架構python
1.硬件C/S架構(打印機)shell
2.軟件C/S架構(互聯網中到處是C/S架構):B/S架構也是C/S架構的一種,B/S是瀏覽器/服務器編程
C/S架構與socket的關係:咱們用socket就是爲了完成C/S架構的開發設計模式
osi七層瀏覽器
引子:服務器
須知一個完整的計算機系統是由硬件、操做系統、應用軟件三者組成,具有了這三個條件,一臺計算機系統就能夠本身跟本身玩了(打個單機遊戲,玩個掃雷啥的)網絡
若是你要跟別人一塊兒玩,那你就須要上網了,什麼是互聯網?架構
互聯網的核心就是由一堆協議組成,協議就是標準,好比全世界人通訊的標準是英語併發
若是把計算機比做人,互聯網協議就是計算機界的英語。全部的計算機都學會了互聯網協議,那全部的計算機都就能夠按照統一的標準去收發信息從而完成通訊了。
人們按照分工不一樣把互聯網協議從邏輯上劃分了層級,
詳見網絡通訊原理:http://www.cnblogs.com/linhaifeng/articles/5937962.html
爲什麼學習socket必定要先學習互聯網協議?
首先C/S架構是基於網絡通訊的
而後網絡的核心即一堆網絡協議,也就是協議標準。若是你想開發一款基於網絡通訊的軟件,就必須遵循這些標準
socke層
2、socket是什麼?
socket是應用層與TCP/IP協議通訊的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議。
因此,咱們無需深刻理解tcp/udp協議,socket已經爲咱們封裝好了,咱們只須要遵循socket的規定去編程,寫出的程序天然就是遵循tcp/udp標準的。
3、基於TCP協議的socket
套接字的分類:
基於文件類型的套接字家族:AF_UNIX(在Unix系統上,一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程同時運行在同一機器,能夠經過訪問同一個文件系統間接完成通訊)
基於網絡類型的套接字家族:AF_INET (python支持不少種地址家族,可是因爲咱們只關心網絡編程,因此大部分時候咱們只使用AF_INET)
套接字的工做流程:
下面咱們舉個打電話的小例子來講明一下
若是你要給你的一個朋友打電話,先撥號,朋友聽到電話鈴聲後提起電話,這時你和你的朋友就創建起了鏈接,就能夠講話了。等交流結束,掛斷電話結束這次交談。 生活中的場景就解釋了這工做原理。
(若是你去一家餐館吃飯,假設哪裏的老闆就是服務端,而你本身就是客戶端,當你去吃飯的時候,你確定的知道那個餐館,也就是服務端的地址吧,可是對於你本身來講,餐館的老闆不須要知道你的地址吧)
套接字函數
1 import socket
2 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) 3 # 1.服務端套接字函數 4 phone.bind('主機ip地址',端口號) #綁定到(主機,端口號)套接字 5 phone.listen() #開始TCP監聽 6 phone.accept() #被動接受TCP客戶的鏈接,等待鏈接的到來
1 2.客戶端套接字函數
2 import socket 3 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#買手機 4 phone.connect() #主動鏈接服務端的ip和端口 5 phone.connect_ex() #connect()函數的擴展版本,出錯的時候返回錯碼,而不是拋出異常
1 3.服務端和客戶端的公共用途的嵌套字函數
2 phone.recv() #接受TCP數據
3 phone.send() #發送TCP數據
4 phone.recvfrom() #接受UDP數據
5 phone.sendto() #發送UDP數據
6 phone.getpeername() #接收到當前套接字遠端的地址
7 phone.getsockname() #返回指定套接字的參數
8 phone.setsockopt() #設置指定套接字的參數
9 phone.close() #關閉套接字
1 面向鎖的套接字方法
2 phone.setblocking() #設置套接字的阻塞與非阻塞模式
3 phone.settimeout() #設置阻塞套接字操做的超時時間
4 phone.gettimeout() #獲得阻塞套接字操做的超時時間
1 面向文件的套接字函數
2 phone.fileno() # 套接字的文件描述符
3 phone.makefile() #建立一個與該套接字相關的文件
TCP是基於連接的,必須先啓動服務器,而後再啓動客戶端去連接服務端
服務端:
1 import socket
2 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#買手機
3 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #能夠屢次啓動
4 #執行屢次的時候會報錯,那麼怎麼辦呢、?就在綁卡前面加上上面那句setsockopt方法就ok了
5 phone.bind(('192.168.20.44',8080))#綁定手機卡(ip,端口)
6 # 端口號在1024之前的是系統用的,1024之後的都是你本身寫的程序去定義的端口
7
8 print('starting run......') 9 phone.listen(5) #開機 5表明的是最多掛起5個,也能夠好多個 10 while True: #連接循環 11 coon,client_addr=phone.accept()#等待接電話,(coon是創建的連接,客戶端的ip和端口號組成的元組) 12 print(coon,client_addr) 13 14 #收發消息 15 while True: #通訊循環 16 try: #若是不加try...except ,就會報錯,由於它不知道你何時斷開連接的,服務器還覺得你在運行 17 data = coon.recv(1024) #收了1024個字節的消息 18 print('client data 收到消息:%s'%data.decode('utf-8')) 19 coon.send(data.upper()) #發消息 20 except Exception: #由於你不知道客戶端何時斷開連接, 21 break 22 coon.close() #掛電話 23 phone.close() #關機 24 25 26 # 處理邏輯錯誤的兩種方式: 27 # if 判斷 28 # try...except 異常處理 29 # 異常處理 30 # 當你知道直接錯誤的條件時就用if判斷了 31 # 當程序錯誤必定發生,可是你又預知不了它出錯的條件是什麼的時候,就用try...except
客戶端:
1 import socket
2 phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM)#買手機
3 phone.connect(('192.168.20.44',8080)) #直接鏈接服務端的ip和端口
4
5 # 發收消息
6 while True: 7 msg = input('>>:').strip() #用戶輸入 8 if not msg:continue #若是爲空就繼續輸 9 phone.send(msg.encode('utf-8')) # 發送你輸入的消息 10 # phone.send('hello'.encode('utf-8')) 11 data = phone.recv(1024) #在接收一下 12 print('server back res服務端返回結果:>>%s'%data.decode('utf-8')) 13 14 phone.close()
注意:
若是你在重啓服務端的時候可能遇到這樣的問題:
這個是因爲你的服務端仍然存在四次揮手的time_wait狀態在佔用地址(若是不懂,請深刻研究1.tcp三次握手,四次揮手 2.syn洪水攻擊 3.服務器高併發狀況下會有大量的time_wait狀態的優化方法)。那麼怎麼解決呢?你也能夠這樣的
1 #加入一條socket配置,重用ip和端口
2
3 phone=socket(AF_INET,SOCK_STREAM)
4 phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
5 phone.bind(('127.0.0.1',8080))
4、基於TCP協議模擬ssh遠程執行命令
1 import socket
2 import subprocess 3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)#買手機 4 phone.bind(('192.168.20.44',8081))#綁定手機卡 5 phone.listen(5)#阻塞的最大個數 6 print('starting....') 7 while True: 8 conn,addr=phone.accept()#等待鏈接 9 print(addr,conn) 10 while True: 11 cmd=conn.recv(10240)#接收的最大值 12 # if not cmd :break 13 print('接收的是:%s'%cmd.decode('utf-8')) 14 #處理過程 15 res=subprocess.Popen(cmd.decode('utf-8'),shell=True, #Popen是執行命令的方法 16 stdout=subprocess.PIPE, 17 stderr=subprocess.PIPE ) 18 stdout=res.stdout.read() 19 stuerr=res.stderr.read() 20 conn.send(stdout+stuerr) 21 conn.close() 22 phone.close()
1 import socket
2
3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) 4 phone.connect(('192.168.20.44',8081))#綁定端口 5 while True: 6 cmd=input('>>請輸入').strip() 7 if not cmd: continue 8 phone.send(cmd.encode('utf-8')) 9 data=phone.recv(10240) 10 print('返回的是%s'%data.decode('gbk')) 11 phone.close()
6、基於UDP協議的socket
1 from socket import *
2 udp_server = socket(AF_INET,SOCK_DGRAM)
3 udp_server.bind(('127.0.0.1',8080)) #綁定
4 while True:#通信循環
5 msg,client_addr= udp_server.recvfrom(1024) 6 print('收到的消息是:%s'%msg.decode('utf-8')) 7 udp_server.sendto(msg.upper(),client_addr) 8 udp_server.close()
1 # udp 無連接,因此不須要鏈接
2 from socket import *
3 udp_client = socket(AF_INET,SOCK_DGRAM)
4
5 while True: 6 msg = input('>>:').strip() 7 udp_client.sendto(msg.encode('utf-8'),('127.0.0.1',8080)) 8 res,sever_addr = udp_client.recvfrom(1024) 9 print('返回的結果是:%s'%res.decode('utf-8')) 10 udp_client.close()
基於UDP協議的socket的應用(模擬QQ聊天)
1 from socket import *
2 udp_server= socket(AF_INET,SOCK_DGRAM)
3 udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) 4 udp_server.bind(('127.0.0.1',8080)) 5 print('start running...') 6 7 while True: 8 qq_msg,addr = udp_server.recvfrom(1024) 9 print('來自[%s:%s]的一條消息:\033[44m%s\033[0m'%(addr[0],addr[1],qq_msg.decode('utf-8'))) 10 back_msg = input('回覆消息:>>').strip() 11 udp_server.sendto(back_msg.encode('utf-8'),addr) 12 udp_server.close()
1 from socket import *
2 udp_client = socket(AF_INET,SOCK_DGRAM)
3 qq_name_dic = { 4 '房得成':('127.0.0.1',8080), 5 '陳鳳琴':('127.0.0.1',8080), 6 '王雅玲':('127.0.0.1',8080), 7 '喜洋洋':('127.0.0.1',8080) 8 } 9 while True: 10 qq_name = input('請輸入聊天對象:>>').strip() 11 if qq_name not in qq_name_dic: continue 12 while True: 13 msg = input('請輸入消息,回車發送:').strip() 14 if msg=='quit':break 15 if not msg or not qq_name or qq_name not in qq_name_dic:continue 16 udp_client.sendto(msg.encode('utf-8'),qq_name_dic[qq_name]) 17 back_msg,addr = udp_client.recvfrom(1024) 18 print('來自[%s:%s]的一條消息:\033[41m%s\033[0m'%(addr[0],addr[1],back_msg.decode('utf-8'))) 19 udp_client.close()
運行結果截圖
5、subprocess子進程模塊
1 import subprocess
2 #Popen方法是用來執行系統命令的,直接把結果打印到終端了
3 res =subprocess.Popen(r'dir',shell=True, 4 #r'dsfsdfr',shell=True, 5 # stdin= #標準輸入(不經常使用) 6 stdout=subprocess.PIPE,#stdout 標準輸出 7 stderr=subprocess.PIPE) #stderr 標準錯誤 8 # 拿到的是‘gbk’編碼的結果, 9 # 這個命令可能有正確結果,也可能有錯誤結果 10 print(res.stdout.read().decode('gbk')) 11 print('========') 12 print(res.stdout.read().decode('gbk')) #說明只能讀一次 13 print(res.stderr.read().decode('gbk')) #若是是錯誤的就會提示
6、struct模塊
1 #該模塊能夠把一個類型,如數字,轉成固定長度的bytes類型
2 import struct
3 # res = struct.pack('i',12345)
4 # print(res,len(res),type(res)) #長度是4
5
6 res2 = struct.pack('i',12345111) 7 print(res2,len(res2),type(res2)) #長度也是4 8 9 unpack_res =struct.unpack('i',res2) 10 print(unpack_res) #(12345111,) 11 # print(unpack_res[0]) #12345111