socket
Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。因此,咱們無需深刻理解tcp/udp協議,socket已經爲咱們封裝好了,咱們只須要遵循socket的規定去編程,寫出的程序天然就是遵循tcp/udp標準的。python
套接字分類
- 基於文件類型的套接字家族:AF_UNIX
- 基於網絡類型的套接字家族:AF_INET
套接字工做流程
套接字函數
1、服務端套接字函數
- s.bind() 綁定(主機,端口號)到套接字
- s.listen() 開始TCP監聽
- s.accept() 被動接受TCP客戶的鏈接,(阻塞式)等待鏈接的到來
2、客戶端套接字函數
- s.connect() 主動初始化TCP服務器鏈接
- s.connect_ex() connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常
3、公共用途的套接字函數
- 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() 關閉套接字
4、面向鎖的套接字方法
tcp是基於連接的,必須先啓動服務端,而後再啓動客戶端去連接服務端linux
5、面向文件的套接字的函數
- s.fileno() 套接字的文件描述符
- s.makefile() 建立一個與該套接字相關的文件
基於TCP的套接字
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
|
"""
服務端:
"""
import
socket
s
=
socket.socket(socket.AF_INET,socket.SOCK_STREAM)
#建立服務器套接字
s.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,
1
)
#重啓服務端時趕上Address already in use 時加上,在bind前加
s.bind((
'127.0.0.1'
,
8083
))
#把地址綁定到套接字
s.listen(
5
)
#監聽連接
print
(
'starting...'
)
while
True
:
# 連接循環
conn,client_addr
=
s.accept()
#接受客戶端連接
print
(client_addr)
while
True
:
#通訊循環
try
:
data
=
conn.recv(
1024
)
if
not
data:
break
#適用於linux操做系統 正在連接的客戶端忽然斷開,recv便再也不阻塞,死循環發生
print
(
'客戶端的數據'
,data)
conn.send(data.upper())
except
ConnectionResetError:
#適用於windows操做系統
break
conn.close()
#關閉客戶端套接字
s.close()
#關閉服務器套接字(可選)
"""
客戶端
"""
import
socket
c
=
socket.socket(socket.AF_INET,socket.SOCK_STREAM)
# 建立客戶套接字
c.connect((
'127.0.0.1'
,
8083
))
# 嘗試鏈接服務器
while
True
:
# 通信循環
msg
=
input
(
'>>: '
).strip()
#msg=''
if
not
msg:
continue
c.send(msg.encode(
'utf-8'
))
#c.send(b'')
data
=
c.recv(
1024
)
print
(data.decode(
'utf-8'
))
c.close()
# 關閉客戶套接字
|
基於UDP的套接字
udp是無連接的,先啓動哪一端都不會報錯shell
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
|
"""
服務端:
"""
from
socket
import
*
server
=
socket(AF_INET,SOCK_DGRAM)
server.bind((
'127.0.0.1'
,
8080
))
while
True
:
data,client_addr
=
server.recvfrom(
1024
)
print
(data)
server.sendto(data.upper(),client_addr)
server.close()
"""
客戶端
"""
from
socket
import
*
client
=
socket(AF_INET, SOCK_DGRAM)
while
True
:
msg
=
input
(
'>>: '
).strip()
client.sendto(msg.encode(
'utf-8'
),(
'127.0.0.1'
,
8080
))
data,server_addr
=
client.recvfrom(
1024
)
print
(data,server_addr)
client.close()
|
粘包現象
1、產生緣由
粘包問題主要是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的。編程
- 發送端須要等緩衝區滿才發送出去,形成粘包(發送數據時間間隔很短,數據量很小,會合到一塊兒,產生粘包)
- 接收方不及時接收緩衝區的包,形成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候仍是從緩衝區拿上次遺留的數據,產生粘包)
2、補充說明
- TCP(transport control protocol,傳輸控制協議)是面向鏈接的,面向流的,提供高可靠性服務。
- UDP(user datagram protocol,用戶數據報協議)是無鏈接的,面向消息的,提供高效率服務。
- 只有TCP有粘包現象,UDP永遠不會粘包
3、解決粘包的方法
把報頭作成字典,字典裏包含將要發送的真實數據的詳細信息,而後json序列化,而後用struck將序列化後的數據長度打包成4個字節json
1.發送時:
- 先發報頭長度
- 再編碼報頭內容而後發送
- 最後發真實內容
2.接收時:
- 先收報頭長度,用struct取出來
- 根據取出的長度收取報頭內容,而後解碼,反序列化
- 從反序列化的結果中取出待取數據的詳細信息,而後去取真實的數據內容
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
|
"""
服務端
"""
import
socket
import
subprocess
import
struct
import
json
phone
=
socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,
1
)
phone.bind((
'127.0.0.1'
,
9909
))
phone.listen(
5
)
print
(
'starting...'
)
while
True
:
# 連接循環
conn,client_addr
=
phone.accept()
print
(client_addr)
while
True
:
#通訊循環
try
:
#一、收命令
cmd
=
conn.recv(
8096
)
if
not
cmd:
break
#適用於linux操做系統
#二、執行命令,拿到結果
obj
=
subprocess.Popen(cmd.decode(
'utf-8'
), shell
=
True
,
stdout
=
subprocess.PIPE,
stderr
=
subprocess.PIPE)
stdout
=
obj.stdout.read()
stderr
=
obj.stderr.read()
#三、把命令的結果返回給客戶端
#第3.1步:製做固定長度的報頭
header_dic
=
{
'filename'
:
'a.txt'
,
'md5'
:
'xxdxxx'
,
'total_size'
:
len
(stdout)
+
len
(stderr)
}
header_json
=
json.dumps(header_dic)
header_bytes
=
header_json.encode(
'utf-8'
)
#第3.2步:先發送報頭的長度
conn.send(struct.pack(
'i'
,
len
(header_bytes)))
#第3.3步:再發報頭
conn.send(header_bytes)
#第3.4步:再發送真實的數據
conn.send(stdout)
conn.send(stderr)
except
ConnectionResetError:
#適用於windows操做系統
break
conn.close()
phone.close()
|
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
|
"""
客戶端
"""
import
socket
import
struct
import
json
phone
=
socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.connect((
'127.0.0.1'
,
9909
))
while
True
:
#一、發命令
cmd
=
input
(
'>>: '
).strip()
#ls /etc
if
not
cmd:
continue
phone.send(cmd.encode(
'utf-8'
))
#二、拿命令的結果,並打印
#第2.1步:先收報頭的長度
obj
=
phone.recv(
4
)
header_size
=
struct.unpack(
'i'
,obj)[
0
]
#第2.2步:再收報頭
header_bytes
=
phone.recv(header_size)
#第2.3步:從報頭中解析出對真實數據的描述信息
header_json
=
header_bytes.decode(
'utf-8'
)
header_dic
=
json.loads(header_json)
print
(header_dic)
total_size
=
header_dic[
'total_size'
]
#第2.4步:接收真實的數據
recv_size
=
0
recv_data
=
b''
while
recv_size < total_size:
res
=
phone.recv(
1024
)
#1024是一個坑
recv_data
+
=
res
recv_size
+
=
len
(res)
print
(recv_data.decode(
'utf-8'
))
phone.close()
|
socketserver實現併發
- 基於tcp的套接字,關鍵就是兩個循環,一個連接循環,一個通訊循環
- socketserver模塊中分兩大類:server類(解決連接問題)和request類(解決通訊問題)
import socketserver class Myserver(socketserver.BaseRequestHandler): def handle(self): self.data = self.request.recv(1024).strip() print("{} wrote:".format(self.client_address[0])) print(self.data) self.request.sendall(self.data.upper()) if __name__ == "__main__": HOST, PORT = "127.0.0.1", 9999 # 設置allow_reuse_address容許服務器重用地址 socketserver.TCPServer.allow_reuse_address = True # 建立一個server, 將服務地址綁定到127.0.0.1:9999 server = socketserver.TCPServer((HOST, PORT),Myserver) # 讓server永遠運行下去,除非強制中止程序 server.serve_forever()
import socket HOST, PORT = "127.0.0.1", 9999 data = "hello" # 建立一個socket連接,SOCK_STREAM表明使用TCP協議 with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock: sock.connect((HOST, PORT)) # 連接到客戶端 sock.sendall(bytes(data + "\n", "utf-8")) # 向服務端發送數據 received = str(sock.recv(1024), "utf-8")# 從服務端接收數據 print("Sent: {}".format(data)) print("Received: {}".format(received))
""" 1.功能類 class Myserver(socketserver.BaseRequestHandler): def handle(self): #放入要併發的邏輯 self.request 就是以前的conn pass 2.server=socketserver.ThreadingTCPServer(("127.0.0.1",8881),Myserver) 3.server.serve_forever() """ import socketserver class Myserver(socketserver.BaseRequestHandler): def handle(self): #放入要併發的邏輯 self.request 就是以前的conn while True: try: client_data=self.request.recv(1024) if len(client_data)==0: break # 適用於linux操做系統 正在連接的客戶端忽然斷開,recv便再也不阻塞,死循環發生 print ("客戶端的數據 >>>",str(client_data,"utf8")) server_data=input("服務端的數據 >>>").strip() self.request.send(bytes(server_data,"utf8")) except ConnectionResetError: # 適用於windows操做系統 break self.request.close() server=socketserver.ThreadingTCPServer(("127.0.0.1",8881),Myserver) server.serve_forever() ###################################################################### import socket c=socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 建立客戶套接字 c.connect(('127.0.0.1',8881)) # 嘗試鏈接服務器 while True: # 通信循環 msg=input('>>: ').strip() #msg='' if not msg:continue c.send(msg.encode('utf-8')) #c.send(b'') data=c.recv(1024) print(data.decode('utf-8')) c.close() # 關閉客戶套接字
4、分析socketserver源碼:
- server=socketserver.ThreadingTCPServer(("127.0.0.1",8881),Myserver)
- server.serve_forever()
- 查找屬性的順序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer
- 實例化獲得server,先找類ThreadingTCPServer的__init__,在TCPServer中找到,進而執行server_bind,server_active
- 找server下的serve_forever,在BaseServer中找到,進而執行self._handle_request_noblock(),該方法一樣是在BaseServer中 執行self._handle_request_noblock()進而執行request,client_address = self.get_request()(就是TCPServer中的self.socket.accept()),而後執行self.process_request(request, client_address)
- 在ThreadingMixIn中找到process_request,開啓多線程應對併發,進而執行process_request_thread,執行self.finish_request(request, client_address) 上述完成了連接循環.
- 開始進入通信部分,在BaseServer中找到finish_request,觸發咱們本身定義的類的實例化,去找__init__方法,而咱們本身定義的類沒有該方法,則去它的父類也就是BaseRequestHandler中找__init__方法....
#_*_coding:utf-8_*_ """ 儘管Son繼承了Base類,父子類中都有一樣的方法, 可是因爲咱們實例化了子類的對象,因此這個在初始化方法裏的self.Testfunc, self指的是子類的對象,固然也就先調用子類中的方法啦。 因此儘管初始化方法在父類執行,可是仍是改變不了它是子類對象的本質, 當使用self去調用Testfunc方法時,始終是先調用子類的方法。 """ class Base(object): def __init__(self,name): self.name = name self.Testfunc() def Testfunc(self): print ('do Base Testfunc') class Son(Base): def Testfunc(self): print ('do Son Testfunc') sonobj = Son('sonobj') # do Son Testfunc """ 儘管這三個類中都有一樣的Testfunc方法,可是,因爲計算機在找方法的時候, 遵循的順序是:Base2,Son,Base,因此它會先找到Base2類, 而這個類中恰好有它要找的方法,它也就拿去執行啦! """ class Base(object): def Testfunc(self): print ('do Base Testfunc') class Son(Base): def __init__(self,name): self.name = name self.Testfunc() def Testfunc(self): print ('do Son Testfunc') class Base2(object): def Testfunc(self): print ('do Base2 Testfunc') class GrandSon(Base2,Son): pass sonobj = Son('sonobj') #do Son Testfunc sonobj = GrandSon('sonobj') #do Base2 Testfunc
1:threadingTCPServer的相關類:windows
2:初始化相關過程:緩存
3:執行serve_forever的相關代碼:服務器
5、源碼分析總結:
一、基於tcp的socketserver咱們本身定義的類中的
- self.server即套接字對象
- self.request即一個連接
- self.client_address即客戶端地址
二、基於udp的socketserver咱們本身定義的類中的
- self.request是一個元組(第一個元素是客戶端發來的數據,第二部分是服務端的udp套接字對象),如(b'adsf', )
- self.client_address即客戶端地址