socket編程html
1、客戶端/服務端架構linux
客戶端/服務端架構 即C/S架構,包括:一、硬件C/S架構,二、軟件C/S架構。算法
互聯網中到處都是C/S架構,學習socket 就是爲了完成C/S架構的開發。shell
C/S架構:
server端要:
一、力求一直提供服務
二、要綁定一個惟一的地址,讓客戶端能明確的找到服務端。編程
2、OSI七層json
一、一個完整的計算機系統是由硬件、操做系統、應用軟件三者組成,具有了這三個條件,一臺計算機系統就能夠本身跟本身玩了。windows
若是要跟別人一塊兒玩,那就須要上網了。互聯網的核心就是由一堆協議組成,協議就是標準,全世界人通訊的標準是英語,若是把計算機比做人,互聯網協議就是計算機界的英語。全部的計算機都學會了互聯網協議,那全部的計算機都就能夠按照統一的標準去收發信息從而完成通訊了。人們按照分工不一樣把互聯網協議從邏輯上劃分了層級,詳見網絡通訊原理:http://www.cnblogs.com/linhaifeng/articles/5937962.html設計模式
二、學socket前學互聯網協議的意義:緩存
學習socket編程目的是開發一款C/S架構軟件,而這款軟件是基於網絡進行通訊的。網絡的核心就是一堆協議,基於網絡開發軟件就必須遵照這些標準;同時socket對網絡通訊協議進行了接口類型的封裝,當須要使用某個協議的時候直接調用這個接口就能實現。服務器
3、socket
Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議。
因此,咱們無需深刻理解tcp/udp協議,socket已經爲咱們封裝好了,咱們只須要遵循socket的規定去編程,寫出的程序天然就是遵循tcp/udp標準。
也有人將socket說成ip+port,ip是用來標識互聯網中的一臺主機的位置,而port是用來標識這臺機器上的一個應用程序,ip地址是配置到網卡上的,而port是應用程序開啓的,ip與port的綁定就標識了互聯網中獨一無二的一個應用程序 而程序的pid是同一臺機器上不一樣進程或者線程的標識
4、套接字
套接字起源於 20 世紀 70 年代,。一開始,套接字被設計用在同 一臺主機上多個應用程序之間的通信。這也被稱進程間通信,或 IPC。
套接字有兩種(或者稱爲有兩個種族),分別是基於文件型的和基於網絡型的。
基於文件類型的套接字家族:
名字:AF_UNIX
經常使用於linux環境中,由於linux中一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,能夠經過訪問同一個文件系統間接完成通訊。
基於網絡類型的套接字家族:
名字:AF_INET
網絡通訊中的ipv4協議,基於網絡編程,幾乎都使用AF_INEF。
5、套接字工做流程:
先從服務器端提及。服務器端先初始化Socket,而後與端口綁定(bind),對端口進行監聽(listen),調用accept接收鏈接請求,等待客戶端鏈接。在這時若是有個客戶端初始化一個Socket,而後鏈接服務器(connect),若是鏈接成功,這時客戶端與服務器端的鏈接就創建了。客戶端發送數據請求,服務器端接收請求並處理請求,而後把迴應數據發送給客戶端,客戶端讀取數據,最後關閉鏈接,一次交互結束。
6、socket模塊的使用方法:
import socket socket.socket(socket_family,socket_type,protocal=0) socket_family 能夠是 AF_UNIX 或 AF_INET。socket_type 能夠是 SOCK_STREAM 或 SOCK_DGRAM。protocol 通常不填,默認值爲 0。 #獲取tcp/ip套接字 tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
服務端套接字函數
s.bind() 綁定(主機,端口號)到套接字
s.listen() 開始TCP監聽
s.accept() 被動接受TCP客戶的鏈接,(阻塞式)等待鏈接的到來
客戶端套接字函數
s.connect() 主動初始化TCP服務器鏈接
公共用途的套接字函數
s.recv() 接收TCP數據
s.send() 發送TCP數據(send在待發送數據量大於己端緩存區剩餘空間時,數據丟失,不會發完)
s.recvfrom() 接收UDP數據
s.sendto() 發送UDP數據
s.close() 關閉套接字
7、基於TCP的套接字
此時就須要深入的理解TCP協議的三次握手,四次揮手!
socket通訊流程與打電話流程的相似,那就先以打電話的例子作一個簡單的套接字通訊。
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機 phone.bind(('127.0.0.1',8080)) #綁定手機卡 phone.listen(5) #開機 print('starting....') conn,addr=phone.accept() #等待電話連接 print('電話線路是',conn) print('客戶端的手機號是',addr) data=conn.recv(1024) #收消息 print('客戶端發來的消息是',data) conn.send(data.upper()) #將收到的數據轉換成大寫,發回 conn.close() phone.close()
import socket phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.connect(('127.0.0.1',8080)) #撥電話 phone.send('hello'.encode('utf-8')) #發消息,說話(只能發送字節類型) data =s.recv(1024) #收消息,聽話 print(data.decode('utf-8')) phone.close()
上述整個流程的問題是:服務端只能接受一次鏈接,而後就完全關閉掉了,並且在鏈接的過程當中,若是直接斷開客戶端,服務端會由於報錯而中斷,或者是出現不能正常鏈接的狀況!針對這種分析,斷開實際狀況應該是,服務端不斷接受連接,而後循環通訊,通訊完畢後只關閉連接,服務器可以繼續接收下一次連接,而且斷開鏈接以後,服務端應馬上釋放TCP協議仍然存在的四次揮手time_wait狀態佔用的地址,優化處理,不影響其餘客戶的訪問。下面是修改以後的代碼:
#coding:utf-8 #打電話舉例 import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #基於TCP/IP協議通訊 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #提早終止TCP四次揮手 phone.bind(("127.0.0.1",8080)) #綁定手機卡 phone.listen(5)#監聽鏈接 print("start....") while True: #鏈接循環 conn,addr = phone.accept()#等待 被動接收鏈接 print("電話線路是:",conn) print("客戶端的手機號是:",addr) while True: #通訊循環 #處理異常(客戶端沒有任何輸入) try: #應對windows系統 data = conn.recv(1024) #接收消息 接收TCP數據 # if not data:break #linux系統 print("客戶端發來的消息是:",data.decode("utf-8")) conn.send(data.upper()) #將收到的數據,轉成大寫從新發給客戶端 except Exception: break conn.close() #將這條線路關閉 phone.close()
#coding:utf-8 import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #基於TCP/IP協議通訊 phone.connect(("127.0.0.1",8080)) #主動初始化TCP服務器鏈接 發起鏈接(必須是元組形式,前邊是地址,後邊是接口) while True: #通訊循環 inp = input(">>>:").strip() if not inp:continue phone.send(inp.encode("utf-8")) #字節數據 向服務端傳值 data = phone.recv(1024) #接收服務端返回的數據 print(data.decode("utf-8")) phone.close()
8、基於UDP的套接字
此處須要理解UDP協議創建連接的兩次握手
import socket UDP_server = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #先建立基於這種通訊協議的對象 IP_port = ("127.0.0.1",8080) #惟一的IP地址和端口 UDP_server.bind(IP_port) #服務端綁定信息 while True: #通訊循環 data,client_addr = UDP_server.recvfrom(1024) #服務端接收發過來的數據(自定義範圍值),格式:(data,客戶端創建通訊鏈接的地址) print(data.decode("utf-8")) #接收的消息 msg = input(">>>:").strip() #交互 UDP_server.sendto(msg.encode("utf-8"),client_addr) #給客戶端發送消息,固定格式:(data,通訊地址)
import socket UDP_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #先建立基於這種通訊協議的對象 client_IP_port = ("127.0.0.1",8080) #通訊IP地址和端口 while True: #通訊循環 inp = input(">>>:").strip() #交互 UDP_client.sendto(inp.encode("utf-8"),client_IP_port) #與服務端創建通訊,發送消息,格式:(data,服務器通訊地址) data,server_addr = UDP_client.recvfrom(1024) #收到返回的消息 print(data.decode("utf-8")) #打印
9、須知:
一、發消息,都是將數據發送到己端的發送緩存中;收消息都是從己端的緩存中收。
二、TCP協議通訊,是基於數據流的鏈接通訊!
2.1 send(bytes_data):發送數據流,數據流bytes_data若爲空,本身這段的緩衝區也爲空,操做系統不會控制tcp協議發空包,即便send無窮個空包,也跟沒有同樣!那麼recv()就不會收到數據,這樣可能就會致使通訊錯誤,進而促使程序卡住崩潰,因此寫代碼的過程當中要真對這一種狀況進行異常處理。
2.2 tcp基於連接通訊
三、UDP協議通訊,是基於數據報的鏈接通訊
服務端與客戶端之間基於UDP協議創建通訊,跟TCP協議相比,服務端就不在須要監聽鏈接地址,客戶端和服務端再也不須要創建連接循環,而直接就能夠實現通信循環。而且服務端和客戶端之間通訊,傳輸的數據報,每一次通訊都是發送一整個報文(帶有數據消息和連接端口具體描述信息的數據)。因爲無需連接通訊,因此無論是否有一個正在運行的服務端,客戶端只要發送消息,服務端就能收到消息;若是服務端沒有打開,那客戶端發送的數據就會丟失。
同時說明一點,udp協議的客戶端發送空數據,其發送過去的存在緩存中的數據包卻不是空數據,而是一條打上了各類標籤的報文數據,服務端再把這條報文消息接收,讀取到客戶端發送的內容。因此服務端也能接收到客戶端發來的空數據,這樣就永遠不用擔憂出現卡機的問題。
還要說明一點,UDP協議不一樣於TCP協議,從發送和接收數據傳輸過程當中,存在緩存中的數據是一條一條的而再也不是數據流,而且數據無論有沒有被接收,必定時間以後都會被從緩存中清除。這樣就避免了粘包的問題,能夠說基於UDP協議通訊的數據,永遠不會出現粘包的問題!
注意:基於TCP協議的通訊,必須先創建連接才能進行數據傳輸,而且是必須先啓動服務端,再啓動客戶端,不然報錯!
而基於UDP協議的通訊,從協議定義角度去理解,不須要創建鏈接就能進行數據傳輸,因此開啓服務端或是客戶端沒有前後順序,就是其中一方不開也不會出問題!能夠理解爲基於UDP協議通訊的服務端或是客戶端只是負責把數據包發出去,你在不在線,接不接收我管不到,我只負責發送。
10、粘包
一、產生緣由
首先明確,粘包現象只在基於TCP協議通訊過程當中出現!
發送端能夠是一K一K地發送數據,而接收端的應用程序能夠兩K兩K地提走數據,固然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據,也就是說,應用程序所看到的數據是一個總體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,所以TCP協議是面向流的協議。對於數據流的傳輸,咱們就根本不知道傳輸文件的字節流從何處開始,又是在何處結束!這也是容易出現粘包問題的緣由。所謂粘包問題主要仍是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的。
須要明白的是當對方send一條信息的時候,不管底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序完成後才呈如今內核緩衝區。
此外,發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一個TCP段。若連續幾回須要send的數據都不多,一般TCP會根據優化算法把這些數據合成一個TCP段後再一次發送出去,這樣接收方就收到了粘包數據。
再一種就是send()發過來的數據流大於recv()一次接收的數據流,這樣就對原數據流形成了拆包。因此因爲tcp的協議數據不會丟,沒有收完包,下次接收會繼續上次的位置繼續接收,己端老是在收到ack時纔會清除緩衝區內容。
總結如下會產生粘包的兩種狀況:
一、發送端須要等緩衝區滿才發送出去,形成粘包(發送數據時間間隔很短,數據了很小,會合到一塊兒,產生粘包)
二、接收方不及時接收緩衝區的包,形成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候仍是從緩衝區拿上次遺留的數據,產生粘包)
二、解決粘包的方法:
1)利用時間模塊,給send()或是recv()加上延遲,從TCP協議上下手,粗暴的解決!這種方式理解就好!
2)首先想下問題的根源,關鍵點就是:接收端不知道發送端將要傳送的字節流的長度,因此解決粘包的方法就是圍繞:如何讓發送端在發送數據前,把本身將要發送的字節流總大小讓接收端知曉,而後接收端來一個死循環接收完全部數據。能夠想象一下,若是直接接收的話,小的數據流還能夠,若是碰到一個好幾T的文件,那得須要多好的設備,才能讓設備接收的時候不一下卡死!因此仍是固定一個接收的範圍,循環的接收直至數據接收完成。
這種方式有個弊端:就是不知道數據傳輸過程當中的網絡延遲可否遇上程序的運行速度!
3)基於第2種的方法的優化,就是給字節流加上自定義固定長度報頭,報頭中包含字節流長度,而後一次send到對端,對端在接收時,先從緩存中取出定長的報頭,而後再取真實數據。
報頭:固定長度
包含對將要發送數據的描述信息
基於這種方式就須要用到 struct 模塊(打包,解包),該模塊能夠把一個類型,如數字,轉成固定長度的bytes。
服務端利用struct模塊,將數據的大小打包一下生成一個固定bytes格式的報頭;客戶端先接收這個報頭,解包拿出數據流的大小。而後服務端發送數據,客戶端根據這個值死循環以固定格式接收這個數據流,接受完成以後再打印。
#coding:utf-8 import socket,subprocess,struct phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) IP_port = ("127.0.0.1",8080) phone.bind(IP_port) phone.listen(5) while True: conn,addr = phone.accept() print("client addr:",addr) while True: try: cmd = conn.recv(1024).decode("utf-8") res = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) out_res = res.stdout.read() err_res = res.stderr.read() data_size = len(out_res)+len(err_res) #發送報頭(數據的大小) conn.send(struct.pack("i",data_size)) #發送數據部分 conn.send(out_res) conn.send(err_res) except Exception: break conn.close() phone.close()
import socket,struct phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) IP_port = ("127.0.0.1",8080) phone.connect(IP_port) while True: cmd = input(">>>:").strip() if not cmd:continue phone.send(bytes(cmd,encoding="utf-8")) #收報頭 header = phone.recv(4) data_size = struct.unpack("i",header)[0] #收數據 recv_size = 0 recv_data = b'' while recv_size < data_size: data = phone.recv(1024) recv_size += len(data) recv_data += data print(recv_data.decode("utf-8")) phone.close()
針對這種方式,咱們能夠再深刻的去理解下,報頭既然是描述數據流信息的,那要不要把報頭寫成一個字典形式,裏邊寫入真實數據的具體信息。服務端和客戶端經過這個報頭,進行更細緻的操做!
socket通訊發送,接收的都是bytes格式的數據,而且bytes只能和字符串進行編碼解碼的轉換!因此就須要先把字典類型的報頭json序列化,獲取json字符串的長度,而後用struct將序列化後的數據長度打包成4個字節(4個本身足夠用了),再把json字符串的報頭轉成bytes格式,等待傳輸。
發送時:
先發報頭長度
再編碼報頭內容而後發送
最後發真實內容
接收時:
先接收報頭長度,用struct取出來
根據取出的長度收取報頭內容,而後解碼,反序列化
從反序列化的結果中取出待取數據的詳細信息,而後去取真實的數據內容
#coding:utf-8 #首先明確一點: #一、無論是發數據或是接收數據,都是先發送到緩存中,或是從緩存中讀取! #二、TCP協議爲了傳輸數據,有本身的處理方法:爲了不網絡延遲致使的數據傳輸問題,對很小的多段數據,會和到一塊兒處理成一個大的數據包,而後再發送 #或者是一次性發送的數據過大,大於自定義的最大接收數據的值,數據就會先被接收一部分,剩餘的還存在緩存之中不會丟失。 #這樣再發送新數據的話,就會和上次剩餘的數據在緩存中拼接在一塊兒。這兩種狀況都會致使 粘包 #三、基於socket的TCP協議通訊,在通訊管道中數據是以 字節數據流 的形式傳輸,對於這個數據流咱們不知道它是從哪裏開始,有是到哪裏結束。 #爲了不粘包,在傳遞真實數據以前,先自定義一個報頭,報頭中寫入要傳遞的數據的信息,先把報頭的長度 打包 發送給客戶端; # 客戶端拿到數據以後,解包收到的內容(即報頭的長度),而後再根據收到的數據 自定義接收報頭的 字節 長度(減小緩存中的佔用,須要多少用多少); #服務端再將已經轉碼成字節格式的報頭髮送給客戶端,客戶端接收到報頭以後,將字節轉碼成字符串,而後利用json將字符串類型的數據轉成真實的數據類型(字典); #而後客戶端在獲得的字典中獲取真實數據的大小,根據這個大小獲取服務端發送給客戶端全部的數據,進行 循環 接收; # 避免大數據傳輸中一次性接收,設備緩存的資源不足問題。同時數據不接收完不打印,避免粘包。 import socket,subprocess,struct,json phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #先聲明一個基於socket通訊對象 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #處理time_wait IP_port = ("127.0.0.1",8080) #服務端惟一通訊地址和端口 phone.bind(IP_port) #做爲本機的惟一標籤,綁定到本機 phone.listen(5) #等待通訊 while True: #鏈接循環 conn,addr = phone.accept() #等待鏈接 print("client addr:",addr) while True: #通信循環 try: #異常判斷 cmd = conn.recv(1024).decode("utf-8") #接收從客戶端發來的數據(bytes數據) # 1024 指定義的從緩存中一次拿取數據的最大量,取值範圍是(單位:字節):0-1024 res = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) #在當前操做系統中執行操做 out_res = res.stdout.read() #正確的命令,按照操做獲得正確的內容 (bytes數據) err_res = res.stderr.read() #錯誤的命名,獲得錯誤的提示 (bytes數據) data_size = len(out_res)+len(err_res) #計算獲得的數據 字節長度 #報頭內容 head_dic = {"data_size":data_size} #作一個字典格式的報頭,裏邊囊括全部關於真實數據的信息 head_json = json.dumps(head_dic) #將字典類型的報頭轉成json格式的字符串 head_bytes = head_json.encode("utf-8") #將字符串轉成bytes,由於socket通訊傳輸的都是字節數據 #part1:先發送報頭長度 conn.send(struct.pack("i",len(head_json))) #將報頭的長度打包發送(字符串格式的長度) #part2:再發送報頭 conn.send(head_bytes) #將報頭髮送 #part3:發送數據 conn.send(out_res) #將真實的數據發送 conn.send(err_res) except Exception: #捕獲異常 break #退出 conn.close() #關閉通信 phone.close()
#coding:utf-8 import socket,struct,json phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #先聲明一個基於socket通訊對象 IP_port = ("127.0.0.1",8080) #鏈接地址 phone.connect(IP_port) #與服務端創建通訊鏈接 #通信循環 while True: #發消息 cmd = input(">>>:").strip() #用戶交互發送消息 if not cmd:continue phone.send(bytes(cmd,encoding="utf-8")) #part1:先收報頭的長度 head_struct = phone.recv(4) #收到從服務端返回的報頭數據 head_len = struct.unpack("i",head_struct)[0] #解包字節數據,獲取報頭的長度 #part2:再收報頭 head_bytes = phone.recv(head_len) #根據報頭的長度,接收報頭(字節數據) head_json = head_bytes.decode("utf-8") #將接收到的報頭轉碼成字符串 head_dic = json.loads(head_json) #再將json字符串,反序列化成字典 print(head_dic) data_size = head_dic["data_size"] #獲取真實數據的大小 #part3:收數據 recv_size = 0 recv_data = b'' while recv_size < data_size: #循環從緩存中獲取真實數據,不取完不打印 data = phone.recv(1024) recv_size += len(data) recv_data += data print(recv_data.decode("utf-8")) phone.close()
11、socketserver實現併發(基於類實現的多線程併發,每創建一次鏈接就在服務端產生一個對象!客戶端的代碼不變!)
前邊寫的通訊程序,都是一個服務器與一個客戶端鏈接,進行通信循環。可是若是多個客戶端與該服務端鏈接,其餘的客戶端就得處於等待掛起的狀態,只能是等待創建鏈接的那個客戶端斷開,其餘的客戶端才能一一的進行鏈接通訊。
這種方式的通訊只能實現一對一的通訊,不能實現並行通訊,形成這種現象的緣由,實質就是:服務端的 鏈接循環 不能實現多個鏈接,進而不能實現併發通訊。
基於這個緣由,咱們使用socketserver這個模塊,實現併發通訊!(此處用到了多線程)
socketserver模塊中分兩大類:server類(解決連接問題)和request類(解決通訊問題)
server類
request類
繼承關係
1)基於多線程的併發繼承關係
2)基於多進程的併發繼承關係
#基於TCP協議實現 多併發 服務端 編寫模版 import socketserver #導入socketserver模塊 class 類名(socketserver.BaseRequestHandler): #創建通信 class建立一個類名繼承 socketserver.BaseRequestHandler 父類 def handle(self): #注意此處沒有__init__而是直接引用handle(self):函數,實例化對象直接調用此方法 print("=-====>",self) # print(self.request) while True: #通訊循環 (這裏就能夠任意編寫) server = self.request #創建通信鏈接,與tcp協議中accept功能一致!收到的是client_addr的通訊線路 data = server.recv(1024) #接收數據 print(data.decode("utf-8")) server.send(data.upper()) #發送數據 IP_port = ("IP地址",端口) #給定惟一的地址 if __name__ == '__main__': #判斷是否爲主函數 obj = socketserver.ThreadingTCPServer(IP_port,類名)#實例化一個 TCP服務 對象(綁定地址,建立的類) obj.serve_forever() #該對象創建連接循環
#基於UDP協議實現 多併發 服務端 編寫模版 import socketserver #導入socketserver模塊 class 類名(socketserver.BaseRequestHandler): #創建通信 class建立一個類名繼承 socketserver.BaseRequestHandler 父類 def handle(self): #注意此處沒有__init__而是直接繼承引用handle(self):函數,實例化對象直接調用此方法 # print("=-====>",self) data = self.request[0] #從客戶端收到的數據 print(data.decode("utf-8")) #打印 UDP_server_conn = self.request[1] #創建通信鏈接,獲得一個客戶端與服務端鏈接創建的套接字對象! UDP_client = self.client_address #獲取服務端的地址 msg = input(">>>:").strip() #回覆內容 UDP_server_conn.sendto(msg.encode('utf-8'),UDP_client) #服務端像客戶端發送內容(內容,和服務端創建鏈接的客戶端地址) IP_Port = ('IP地址',端口) #惟一通訊地址 if __name__ == '__main__': #判斷是否爲主函數 obj=socketserver.ThreadingUDPServer(IP_Port,類名) #實例化一個 UDP服務 對象(綁定地址,建立的類) obj.serve_forever() #創建連接循環
簡單代碼舉例!客戶端代碼不變!服務端代碼寫成多併發形式!!!
import socketserver class FTPserver(socketserver.BaseRequestHandler): #通信 def handle(self): print("=-====>",self) # print(self.request) while True: server = self.request data = server.recv(1024) print(data.decode("utf-8")) server.send(data.upper()) IP_port = ("127.0.0.1",8080) if __name__ == '__main__': obj = socketserver.ThreadingTCPServer(IP_port,FTPserver) obj.serve_forever() #連接循環
#coding:utf-8 import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #基於TCP/IP協議通訊 phone.connect(("127.0.0.1",8080)) #主動初始化TCP服務器鏈接 發起鏈接(必須是元組形式,前邊是地址,後邊是接口) while True: #通訊循環 inp = input(">>>:").strip() if not inp:continue phone.send(inp.encode("utf-8")) #字節數據 向服務端傳值 data = phone.recv(1024) #接收服務端返回的數據 print(data.decode("utf-8")) phone.close()
#coding:utf-8 import socket phone = socket.socket(socket.AF_INET,socket.SOCK_STREAM) #基於TCP/IP協議通訊 phone.connect(("127.0.0.1",8080)) #主動初始化TCP服務器鏈接 發起鏈接(必須是元組形式,前邊是地址,後邊是接口) while True: #通訊循環 inp = input(">>>:").strip() if not inp:continue phone.send(inp.encode("utf-8")) #字節數據 向服務端傳值 data = phone.recv(1024) #接收服務端返回的數據 print(data.decode("utf-8")) phone.close()
代碼分析:
如下述代碼爲例,分析socketserver源碼:
ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer)
ftpserver.serve_forever()
查找屬性的順序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer
源碼分析總結:
基於tcp的socketserver咱們本身定義的類中的
基於udp的socketserver咱們本身定義的類中的
PS:UDP協議多併發:(同理也是更改服務端的代碼,客戶端代碼不變!)
import socketserver #導入socketserver模塊 class UDPServer(socketserver.BaseRequestHandler): #創建通信 class建立一個類名繼承 socketserver.BaseRequestHandler 父類 def handle(self): #注意此處沒有__init__而是直接繼承引用handle(self):函數,實例化對象直接調用此方法 # print("=-====>",self) data = self.request[0] #從客戶端收到的數據 print(data.decode("utf-8")) #打印 UDP_server_conn = self.request[1] #創建通信鏈接,獲得一個客戶端與服務端鏈接創建的套接字對象! UDP_client = self.client_address #獲取服務端的地址 msg = input(">>>:").strip() #回覆內容 UDP_server_conn.sendto(msg.encode('utf-8'),UDP_client) #服務端像客戶端發送內容(內容,和服務端創建鏈接的客戶端地址) IP_Port = ('127.0.0.1',8080) #惟一通訊地址 if __name__ == '__main__': #判斷是否爲主函數 obj=socketserver.ThreadingUDPServer(IP_Port,UDPServer) #實例化一個 UDP服務 對象(綁定地址,建立的類) obj.serve_forever() #創建連接循環
import socket UDP_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #先建立基於這種通訊協議的對象 client_IP_port = ("127.0.0.1",8080) #通訊IP地址和端口 while True: #通訊循環 inp = input(">>>:").strip() #交互 UDP_client.sendto(inp.encode("utf-8"),client_IP_port) #與服務端創建通訊,發送消息,格式:(data,服務器通訊地址) data,server_addr = UDP_client.recvfrom(1024) #收到返回的消息 print(data.decode("utf-8")) #打印
import socket UDP_client = socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #先建立基於這種通訊協議的對象 client_IP_port = ("127.0.0.1",8080) #通訊IP地址和端口 while True: #通訊循環 inp = input(">>>:").strip() #交互 UDP_client.sendto(inp.encode("utf-8"),client_IP_port) #與服務端創建通訊,發送消息,格式:(data,服務器通訊地址) data,server_addr = UDP_client.recvfrom(1024) #收到返回的消息 print(data.decode("utf-8")) #打印