python網絡編程

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()
server端
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))
client
"""
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() # 關閉客戶套接字
View Code

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即客戶端地址
相關文章
相關標籤/搜索