python之socket網絡編程

目錄

  • 客戶端/服務器架構
  • socket邏輯結構
  • socket概念
  • 套接字的概念
  • TCP與UDP套接字應用
  • recv與recvfrom的區別
  • 粘包現象及處理
  • 認證客戶端的連接合法性
  • socktserver併發

 

1、客戶端/服務器架構

一、c/s架構(硬件):打印機服務等html

二、c/s架構(軟件):web服務(瀏覽器爲客戶端),有客戶端的遊戲等python

三、c/s架構與socket的關係:socket就是爲了完成C/S架構的開發linux

2、socket邏輯結構:

  一、網絡模型簡介:web

http://www.cnblogs.com/wangshuyang/articles/7053085.html算法

二、socket邏輯架構圖:shell

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是同一臺機器上不一樣進程或者線程的標識json

4、套接字的概念

一、套接字的發展史:windows

套接字起源於 20 世紀 70 年代加利福尼亞大學伯克利分校版本的 Unix,即人們所說的 BSD Unix。 所以,有時人們也把套接字稱爲「伯克利套接字」或「BSD 套接字」。一開始,套接字被設計用在同 一臺主機上多個應用程序之間的通信。這也被稱進程間通信,或 IPC。設計模式

二、套接字分類:

套接字有兩種(或者稱爲有兩個種族),分別是基於文件型的和基於網絡型的。

a.基於文件類型的套接字家族:套接字家族的名字:AF_UNIX

unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,能夠經過訪問同一個文件系統間接完成通訊基於網絡類型的套接字家族

b.套接字家族的名字:AF_INET

(還有AF_INET6被用於ipv6,還有一些其餘的地址家族,不過,他們要麼是隻用於某個平臺,要麼就是已經被廢棄,或者是不多被使用,或者是根本沒有實現,全部地址家族中,AF_INET是使用最普遍的一個,python支持不少種地址家族,可是因爲咱們只關心網絡編程,因此大部分時候我麼只使用AF_INET)

三、套接字的工做流程:

a.工做流程(服務端--〉客戶端):

服務器端先初始化Socket,而後與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端鏈接。在這時若是有個客戶端初始化一個Socket,而後鏈接服務器(connect),若是鏈接成功,這時客戶端與服務器端的鏈接就創建了。客戶端發送數據請求,服務器端接收請求並處理請求,而後把迴應數據發送給客戶端,客戶端讀取數據,最後關閉鏈接,一次交互結束。

 1 import socket
 2 socket.socket(socket_family,socket_type,protocal=0)
 3 socket_family 能夠是 AF_UNIX 或 AF_INET。socket_type 能夠是 SOCK_STREAM 或 SOCK_DGRAM。protocol 通常不填,默認值爲 0。
 4 
 5 獲取tcp/ip套接字
 6 tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
 7 
 8 獲取udp/ip套接字
 9 udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
10 
11 因爲 socket 模塊中有太多的屬性。咱們在這裏破例使用了'from module import *'語句。使用 'from socket import *',咱們就把 socket 模塊裏的全部屬性都帶到咱們的命名空間裏了,這樣能 大幅減短咱們的代碼。
12 例如tcpSock = socket(AF_INET, SOCK_STREAM)
View Code

  b.python服務端套接字函數

s.bind() 綁定(主機,端口號)到套接字
s.listen() 開始TCP監聽
s.accept() 被動接受TCP客戶的鏈接,(阻塞式)等待鏈接的到來

c.python客戶端套接字函數

s.connect() 主動初始化TCP服務器鏈接
s.connect_ex() connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常

d.公共用途的套接字函數

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() 關閉套接字

c.面向鎖的套接字方法

s.setblocking() 設置套接字的阻塞與非阻塞模式
s.settimeout() 設置阻塞套接字操做的超時時間
s.gettimeout() 獲得阻塞套接字操做的超時時間

d.面向文件的套接字的函數

s.fileno() 套接字的文件描述符
s.makefile() 建立一個與該套接字相關的文件

e.python套接字流程代碼推到

 1 1:用打電話的流程快速描述socket通訊
 2 2:服務端和客戶端加上基於一次連接的循環通訊
 3 3:客戶端發送空,卡主,證實是從哪一個位置卡的
 4 服務端:
 5 from socket import *
 6 phone=socket(AF_INET,SOCK_STREAM)
 7 phone.bind(('127.0.0.1',8081))
 8 phone.listen(5)
 9 
10 conn,addr=phone.accept()
11 while True:
12     data=conn.recv(1024)
13     print('server===>')
14     print(data)
15     conn.send(data.upper())
16 conn.close()
17 phone.close()
18 客戶端:
19 from socket import *
20 
21 phone=socket(AF_INET,SOCK_STREAM)
22 phone.connect(('127.0.0.1',8081))
23 
24 while True:
25     msg=input('>>: ').strip()
26     phone.send(msg.encode('utf-8'))
27     print('client====>')
28     data=phone.recv(1024)
29     print(data)
30 
31 說明卡的緣由:緩衝區爲空recv就卡住,引出原理圖
32 
33 
34 
35 4.演示客戶端斷開連接,服務端的狀況,提供解決方法
36 
37 5.演示服務端不能重複接受連接,而服務器都是正常運行不斷來接受客戶連接的
38 
39 6:簡單演示udp
40 服務端
41 from socket import *
42 phone=socket(AF_INET,SOCK_DGRAM)
43 phone.bind(('127.0.0.1',8082))
44 while True:
45     msg,addr=phone.recvfrom(1024)
46     phone.sendto(msg.upper(),addr)
47 客戶端
48 from socket import *
49 phone=socket(AF_INET,SOCK_DGRAM)
50 while True:
51     msg=input('>>: ')
52     phone.sendto(msg.encode('utf-8'),('127.0.0.1',8082))
53     msg,addr=phone.recvfrom(1024)
54     print(msg)
55 
56 udp客戶端能夠併發演示
57 udp客戶端能夠輸入爲空演示,說出recvfrom與recv的區別,暫且不提tcp流和udp報的概念,留到粘包去說
View Code

5、TCP與UDP套接字應用

一、tcp & socket & python

a.tcp服務端(邏輯)

1 ss = socket() #建立服務器套接字
2 ss.bind()      #把地址綁定到套接字
3 ss.listen()      #監聽連接
4 inf_loop:      #服務器無限循環
5     cs = ss.accept() #接受客戶端連接
6     comm_loop:         #通信循環
7         cs.recv()/cs.send() #對話(接收與發送)
8     cs.close()    #關閉客戶端套接字
9 ss.close()        #關閉服務器套接字(可選)
View Code

b.tcp客戶端(邏輯)

1 cs = socket()    # 建立客戶套接字
2 cs.connect()    # 嘗試鏈接服務器
3 comm_loop:        # 通信循環
4     cs.send()/cs.recv()    # 對話(發送/接收)
5 cs.close()            # 關閉客戶套接字
View Code

c.python & tcp 服務端

 1 #_*_coding:utf-8_*_
 2 __author__ = 'Linhaifeng'
 3 import socket
 4 ip_port=('127.0.0.1',8081)#電話卡
 5 BUFSIZE=1024
 6 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM) #買手機
 7 s.bind(ip_port) #手機插卡
 8 s.listen(5)     #手機待機
 9 
10 
11 while True:                         #新增接收連接循環,能夠不停的接電話
12     conn,addr=s.accept()            #手機接電話
13     # print(conn)
14     # print(addr)
15     print('接到來自%s的電話' %addr[0])
16     while True:                         #新增通訊循環,能夠不斷的通訊,收發消息
17         msg=conn.recv(BUFSIZE)             #聽消息,聽話
18 
19         # if len(msg) == 0:break        #若是不加,那麼正在連接的客戶端忽然斷開,recv便再也不阻塞,死循環發生
20 
21         print(msg,type(msg))
22 
23         conn.send(msg.upper())          #發消息,說話
24 
25     conn.close()                    #掛電話
26 
27 s.close()                       #手機關機
28 
29 服務端改進版
View Code

d.python & tcp 客戶端

 1 #_*_coding:utf-8_*_
 2 __author__ = 'Linhaifeng'
 3 import socket
 4 ip_port=('127.0.0.1',8081)
 5 BUFSIZE=1024
 6 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 7 
 8 s.connect_ex(ip_port)           #撥電話
 9 
10 while True:                             #新增通訊循環,客戶端能夠不斷髮收消息
11     msg=input('>>: ').strip()
12     if len(msg) == 0:continue
13     s.send(msg.encode('utf-8'))         #發消息,說話(只能發送字節類型)
14 
15     feedback=s.recv(BUFSIZE)                           #收消息,聽話
16     print(feedback.decode('utf-8'))
17 
18 s.close()                                       #掛電話
19 
20 客戶端改進版
View Code

ps.可能遇到的問題:

在重啓服務端時可能會遇到端口占用,這個是因爲你的服務端仍然存在四次揮手的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))
View Code

二、udp & socket & python

a.udp服務端(邏輯)

1 ss = socket()   #建立一個服務器的套接字
2 ss.bind()       #綁定服務器套接字
3 inf_loop:       #服務器無限循環
4     cs = ss.recvfrom()/ss.sendto() # 對話(接收與發送)
5 ss.close()                         # 關閉服務器套接字
View Code

b.udp客戶端(邏輯)

1 cs = socket()   # 建立客戶套接字
2 comm_loop:      # 通信循環
3     cs.sendto()/cs.recvfrom()   # 對話(發送/接收)
4 cs.close()                      # 關閉客戶套接字
View Code

c.udp簡單示例:

 1 #_*_coding:utf-8_*_
 2 __author__ = 'Linhaifeng'
 3 import socket
 4 ip_port=('127.0.0.1',9000)
 5 BUFSIZE=1024
 6 udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
 7 
 8 udp_server_client.bind(ip_port)
 9 
10 while True:
11     msg,addr=udp_server_client.recvfrom(BUFSIZE)
12     print(msg,addr)
13 
14     udp_server_client.sendto(msg.upper(),addr)
服務端
 1 #_*_coding:utf-8_*_
 2 __author__ = 'Linhaifeng'
 3 import socket
 4 ip_port=('127.0.0.1',9000)
 5 BUFSIZE=1024
 6 udp_server_client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
 7 
 8 while True:
 9     msg=input('>>: ').strip()
10     if not msg:continue
11 
12     udp_server_client.sendto(msg.encode('utf-8'),ip_port)
13 
14     back_msg,addr=udp_server_client.recvfrom(BUFSIZE)
15     print(back_msg.decode('utf-8'),addr)
客戶端

d.qq聊天示例(因爲udp無鏈接,因此能夠同時多個客戶端去跟服務端通訊)

 1 #_*_coding:utf-8_*_
 2 __author__ = 'Linhaifeng'
 3 import socket
 4 ip_port=('127.0.0.1',8081)
 5 udp_server_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM) #買手機
 6 udp_server_sock.bind(ip_port)
 7 
 8 while True:
 9     qq_msg,addr=udp_server_sock.recvfrom(1024)
10     print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],qq_msg.decode('utf-8')))
11     back_msg=input('回覆消息: ').strip()
12 
13     udp_server_sock.sendto(back_msg.encode('utf-8'),addr)
服務端
 1 import socket
 2 BUFSIZE=1024
 3 udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
 4 
 5 qq_name_dic={
 6     '狗哥':('127.0.0.1',8081),
 7     'B哥':('127.0.0.1',8081),
 8     '一棵樹':('127.0.0.1',8081),
 9     '武大郎':('127.0.0.1',8081),
10 }
11 
12 
13 while True:
14     qq_name=input('請選擇聊天對象: ').strip()
15     while True:
16         msg=input('請輸入消息,回車發送: ').strip()
17         if msg == 'quit':break
18         if not msg or not qq_name or qq_name not in qq_name_dic:continue
19         udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])
20 
21         back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)
22         print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))
23 
24 udp_client_socket.close()
客戶端1
 1 import socket
 2 BUFSIZE=1024
 3 udp_client_socket=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
 4 
 5 qq_name_dic={
 6     '狗哥':('127.0.0.1',8081),
 7     'B哥':('127.0.0.1',8081),
 8     '一棵樹':('127.0.0.1',8081),
 9     '武大郎':('127.0.0.1',8081),
10 }
11 
12 
13 while True:
14     qq_name=input('請選擇聊天對象: ').strip()
15     while True:
16         msg=input('請輸入消息,回車發送: ').strip()
17         if msg == 'quit':break
18         if not msg or not qq_name or qq_name not in qq_name_dic:continue
19         udp_client_socket.sendto(msg.encode('utf-8'),qq_name_dic[qq_name])
20 
21         back_msg,addr=udp_client_socket.recvfrom(BUFSIZE)
22         print('來自[%s:%s]的一條消息:\033[1;44m%s\033[0m' %(addr[0],addr[1],back_msg.decode('utf-8')))
23 
24 udp_client_socket.close()
客戶端2

e.時間服務器:

 1 from socket import *
 2 from time import strftime
 3 
 4 ip_port=('127.0.0.1',9000)
 5 bufsize=1024
 6 
 7 tcp_server=socket(AF_INET,SOCK_DGRAM)
 8 tcp_server.bind(ip_port)
 9 
10 while True:
11     msg,addr=tcp_server.recvfrom(bufsize)
12     print('===>',msg)
13     
14     if not msg:
15         time_fmt='%Y-%m-%d %X'
16     else:
17         time_fmt=msg.decode('utf-8')
18     back_msg=strftime(time_fmt)
19 
20     tcp_server.sendto(back_msg.encode('utf-8'),addr)
21 
22 tcp_server.close()
ntp服務端
 1 from socket import *
 2 ip_port=('127.0.0.1',9000)
 3 bufsize=1024
 4 
 5 tcp_client=socket(AF_INET,SOCK_DGRAM)
 6 
 7 
 8 
 9 while True:
10     msg=input('請輸入時間格式(例%Y %m %d)>>: ').strip()
11     tcp_client.sendto(msg.encode('utf-8'),ip_port)
12 
13     data=tcp_client.recv(bufsize)
14 
15     print(data.decode('utf-8'))
16 
17 tcp_client.close()
ntp客戶端

6、recv與recvfrom的區別

一、收發消息的原理:發消息,都是將數據發送到己端的發送緩衝中,收消息都是從己端的緩衝區中收。

a.send發消息,recv收消息

b.sendto發消息,recvfrom收消息

二、send與sendinto

 tcp是基於數據流的,而udp是基於數據報的:

a.send(bytes_data):發送數據流,數據流bytes_data若爲空,本身這段的緩衝區也爲空,操做系統不會控制tcp協議發空包

b.sendinto(bytes_data,ip_port):發送數據報,bytes_data爲空,還有ip_port,全部即使是發送空的bytes_data,數據報其實也不是空的,本身這端的緩衝區收到內容,操做系統就會控制udp協議發包。

三、recv與recvfrom

a.tcp協議:

1)若是收消息緩衝區裏的數據爲空,那麼recv就會阻塞(阻塞很簡單,就是一直在等着收)
2)只不過tcp協議的客戶端send一個空數據就是真的空數據,客戶端即便有無窮個send空,也跟沒有一個樣。
3)tcp基於連接通訊

    • 基於連接,則須要listen(backlog),指定半鏈接池的大小
    • 基於連接,必須先運行的服務端,而後客戶端發起連接請求
    • 對於mac系統:若是一端斷開了連接,那另一端的連接也跟着完蛋recv將不會阻塞,收到的是空(解決方法是:服務端在收消息後加上if判斷,空消息就break掉通訊循環)
    • 對於windows/linux系統:若是一端斷開了連接,那另一端的連接也跟着完蛋recv將不會阻塞,收到的是空(解決方法是:服務端通訊循環內加異常處理,捕捉到異常後就break掉通信循環)

客戶端發送爲空,測試結果--->驗證:(1)

客戶端直接終止程序,測試結果--->驗證:(2)

 1 mport subprocess
 2 from socket import *
 3 
 4 phone=socket(AF_INET,SOCK_STREAM)
 5 phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
 6 phone.bind(('127.0.0.1',8080))
 7 phone.listen(5)
 8 
 9 conn,addr=phone.accept()
10 
11 while True:
12     data=conn.recv(1024)
13     print('from client msg is ',data)
14     conn.send(data.upper())
服務端
 1 import subprocess
 2 from socket import *
 3 
 4 phone=socket(AF_INET,SOCK_STREAM)
 5 phone.connect(('127.0.0.1',8080))
 6 
 7 
 8 while True:
 9     msg=input('>>: ')
10     phone.send(msg.encode('utf-8'))
11     print('Client message has been sent')
12 
13     data=phone.recv(1024)
14     print('from server msg is ',data.decode('utf-8'))
15 phone.close()
客戶端

  b.udp協議:

1)若是若是收消息緩衝區裏的數據爲「空」,recvfrom也會阻塞
2)只不過udp協議的客戶端sendinto一個空數據並非真的空數據(包含:空數據+地址信息,獲得的報仍然不會爲空),因此客戶端只要有一個sendinto(無論是否發送空數據,都不是真的空數據),服務端就能夠recvfrom到數據。
3)udp無連接

    • 無連接,於是無需listen(backlog),更加沒有什麼鏈接池之說了
    • 無連接,udp的sendinto不用管是否有一個正在運行的服務端,能夠己端一個勁的發消息,只不過數據丟失
    • recvfrom收的數據小於sendinto發送的數據時,在mac和linux系統上數據直接丟失,在windows系統上發送的比接收的大直接報錯
    • 只有sendinto發送數據沒有recvfrom收數據,數據丟失

客戶端發送空,看服務端結果--->驗證(1)

 1 __author__ = 'Linhaifeng'
 2 from socket import *
 3 
 4 ip_port=('127.0.0.1',9003)
 5 bufsize=1024
 6 
 7 udp_server=socket(AF_INET,SOCK_DGRAM)
 8 udp_server.bind(ip_port)
 9 
10 while True:
11     data1,addr=udp_server.recvfrom(bufsize)
12     print(data1)
服務端
1 from socket import *
2 ip_port=('127.0.0.1',9003)
3 bufsize=1024
4 
5 udp_client=socket(AF_INET,SOCK_DGRAM)
6 
7 while True:
8     msg=input('>>: ')
9     udp_client.sendto(msg.encode('utf-8'),ip_port) #發送空,發現服務端能夠接收空
客戶端

分別運行服務端,客戶端--->驗證(2)

 1 from socket import *
 2 
 3 ip_port=('127.0.0.1',9003)
 4 bufsize=1024
 5 
 6 udp_server=socket(AF_INET,SOCK_DGRAM)
 7 udp_server.bind(ip_port)
 8 
 9 data1,addr=udp_server.recvfrom(1)
10 print('第一次收了 ',data1)
11 data2,addr=udp_server.recvfrom(1)
12 print('第二次收了 ',data2)
13 data3,addr=udp_server.recvfrom(1)
14 print('第三次收了 ',data3)
15 print('--------結束----------')
服務端
1 from socket import *
2 ip_port=('127.0.0.1',9003)
3 bufsize=1024
4 
5 udp_client=socket(AF_INET,SOCK_DGRAM)
6 
7 udp_client.sendto(b'hello',ip_port)
8 udp_client.sendto(b'world',ip_port)
9 udp_client.sendto(b'egon',ip_port)
客戶端

不運行服務端,單獨運行客戶端,一點問題沒有,可是消息丟了--->驗證(3)

 1 from socket import *
 2 
 3 ip_port=('127.0.0.1',9003)
 4 bufsize=1024
 5 
 6 udp_server=socket(AF_INET,SOCK_DGRAM)
 7 udp_server.bind(ip_port)
 8 
 9 data1,addr=udp_server.recvfrom(bufsize)
10 print('第一次收了 ',data1)
11 data2,addr=udp_server.recvfrom(bufsize)
12 print('第二次收了 ',data2)
13 data3,addr=udp_server.recvfrom(bufsize)
14 print('第三次收了 ',data3)
15 print('--------結束----------')
服務端
 1 from socket import *
 2 import time
 3 ip_port=('127.0.0.1',9003)
 4 bufsize=1024
 5 
 6 udp_client=socket(AF_INET,SOCK_DGRAM)
 7 
 8 udp_client.sendto(b'hello',ip_port)
 9 udp_client.sendto(b'world',ip_port)
10 udp_client.sendto(b'egon',ip_port)
11 
12 print('客戶端發完消息啦')
13 time.sleep(100)
客戶端

ps:

你單獨運行上面的udp的客戶端,你發現並不會報錯,相反tcp卻會報錯,由於udp協議只負責把包發出去,對方收不收,我根本無論,而tcp是基於連接的,必須有一個服務端先運行着,客戶端去跟服務端創建連接而後依託於連接才能傳遞消息,任何一方試圖把連接摧毀都會致使對方程序的崩潰。

上面的udp程序,你註釋任何一條客戶端的sendinto,服務端都會卡住,爲何?由於服務端有幾個recvfrom就要對應幾個sendinto,哪怕是sendinto(b'')那也要有。

7、粘包現象及處理

一、粘包現象:能夠分爲服務端粘包,和客戶端粘包,緣由爲雙方都不知道發送多少字節的數據,接收應接收多少字節的數據。upd沒有粘包的現象,由於他會丟數據。

發送端能夠是一K一K地發送數據,而接收端的應用程序能夠兩K兩K地提走數據,固然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據,也就是說,應用程序所看到的數據是一個總體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,所以TCP協議是面向流的協議,這也是容易出現粘包問題的緣由。而UDP是面向消息的協議,每一個UDP段都是一條消息,應用程序必須以消息爲單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不一樣的。怎樣定義消息呢?能夠認爲對方一次性write/send的數據爲一個消息,須要明白的是當對方send一條信息的時候,不管底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序完成後才呈如今內核緩衝區。

例如基於tcp的套接字客戶端往服務端上傳文件,發送時文件內容是按照一段一段的字節流發送的,在接收方看了,根本不知道該文件的字節流從何處開始,在何處結束。所謂粘包問題主要仍是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的。

此外,發送方引發的粘包是由TCP協議自己形成的,TCP爲提升傳輸效率,發送方每每要收集到足夠多的數據後才發送一個TCP段。若連續幾回須要send的數據都不多,一般TCP會根據優化算法把這些數據合成一個TCP段後一次發送出去,這樣接收方就收到了粘包數據。

  • TCP(transport control protocol,傳輸控制協議)是面向鏈接的,面向流的,提供高可靠性服務。收發兩端(客戶端和服務器端)都要有一一成對的socket,所以,發送端爲了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將屢次間隔較小且數據量小的數據,合併成一個大的數據塊,而後進行封包。這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通訊是無消息保護邊界的。
  • UDP(user datagram protocol,用戶數據報協議)是無鏈接的,面向消息的,提供高效率服務。不會使用塊的合併優化算法,, 因爲UDP支持的是一對多的模式,因此接收端的skbuff(套接字緩衝區)採用了鏈式結構來記錄每個到達的UDP包,在每一個UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對於接收端來講,就容易進行區分處理了。 即面向消息的通訊是有消息保護邊界的。
  • tcp是基於數據流的,因而收發的消息不能爲空,這就須要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基於數據報的,即使是你輸入的是空內容(直接回車),那也不是空消息,udp協議會幫你封裝上消息頭,實驗略
  • udp的recvfrom是阻塞的,一個recvfrom(x)必須對一個一個sendinto(y),收完了x個字節的數據就算完成,如果y>x數據就丟失,這意味着udp根本不會粘包,可是會丟數據,不可靠

tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端老是在收到ack時纔會清除緩衝區內容。數據是可靠的,可是會粘包。

兩種狀況下會發生粘包:

a.發送端須要等緩衝區滿才發送出去,形成粘包(發送數據時間間隔很短,數據了很小,會合到一塊兒,產生粘包)

 1 from socket import *
 2 ip_port=('127.0.0.1',8080)
 3 
 4 tcp_socket_server=socket(AF_INET,SOCK_STREAM)
 5 tcp_socket_server.bind(ip_port)
 6 tcp_socket_server.listen(5)
 7 
 8 
 9 conn,addr=tcp_socket_server.accept()
10 
11 
12 data1=conn.recv(10)
13 data2=conn.recv(10)
14 
15 print('----->',data1.decode('utf-8'))
16 print('----->',data2.decode('utf-8'))
17 
18 conn.close()
服務端
 1 import socket
 2 BUFSIZE=1024
 3 ip_port=('127.0.0.1',8080)
 4 
 5 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 6 res=s.connect_ex(ip_port)
 7 
 8 
 9 s.send('hello'.encode('utf-8'))
10 s.send('feng'.encode('utf-8'))
客戶端

b.接收方不及時接收緩衝區的包,形成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候仍是從緩衝區拿上次遺留的數據,產生粘包) 

 1 from socket import *
 2 ip_port=('127.0.0.1',8080)
 3 
 4 tcp_socket_server=socket(AF_INET,SOCK_STREAM)
 5 tcp_socket_server.bind(ip_port)
 6 tcp_socket_server.listen(5)
 7 
 8 
 9 conn,addr=tcp_socket_server.accept()
10 
11 
12 data1=conn.recv(2) #一次沒有收完整
13 data2=conn.recv(10)#下次收的時候,會先取舊的數據,而後取新的
14 
15 print('----->',data1.decode('utf-8'))
16 print('----->',data2.decode('utf-8'))
17 
18 conn.close()
服務端
1 import socket
2 BUFSIZE=1024
3 ip_port=('127.0.0.1',8080)
4 
5 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
6 res=s.connect_ex(ip_port)
7 
8 
9 s.send('hello feng'.encode('utf-8'))
客戶端

二、拆包狀況(粘包詳解):

發送端緩衝區的長度大於網卡的MTU時,tcp會將此次發送的數據拆成幾個數據包發送出去。

a.爲什麼tcp是可靠傳輸,udp是不可靠傳輸

tcp在數據傳輸時,發送端先把數據發送到本身的緩存中,而後協議控制將緩存中的數據發往對端,對端返回一個ack=1,發送端則清理緩存中的數據,對端返回ack=0,則從新發送數據,因此tcp是可靠的

而udp發送數據,對端是不會返回確認信息的,所以不可靠

b.send(字節流)和recv(1024)及sendall

recv裏指定的1024意思是從緩存裏一次拿出1024個字節的數據。send的字節流是先放入己端緩存,而後由協議控制將緩存內容發往對端,若是待發送的字節流大小大於緩存剩餘空間,那麼數據丟失,用sendall就會循環調用send,數據不會丟失

三、粘包實例:

讓咱們基於tcp先製做一個遠程執行命令的程序(1:執行錯誤命令 2:執行ls 3:執行ifconfig)。

res=subprocess.Popen(cmd.decode('utf-8'),
shell=True,
stderr=subprocess.PIPE,
stdout=subprocess.PIPE)

的結果的編碼是以當前所在的系統爲準的,若是是windows,那麼res.stdout.read()讀出的就是GBK編碼的,在接收端需要用GBK解碼且只能從管道里讀一次結果。

命令ls -l ; lllllll ; pwd 的結果是既有正確stdout結果,又有錯誤stderr結果

 1 #_*_coding:utf-8_*_
 2 __author__ = 'Linhaifeng'
 3 from socket import *
 4 import subprocess
 5 
 6 ip_port=('127.0.0.1',8080)
 7 BUFSIZE=1024
 8 
 9 tcp_socket_server=socket(AF_INET,SOCK_STREAM)
10 tcp_socket_server.bind(ip_port)
11 tcp_socket_server.listen(5)
12 
13 while True:
14     conn,addr=tcp_socket_server.accept()
15     print('客戶端',addr)
16 
17     while True:
18         cmd=conn.recv(BUFSIZE)
19         if len(cmd) == 0:break
20 
21         res=subprocess.Popen(cmd.decode('utf-8'),shell=True,
22                          stdout=subprocess.PIPE,
23                          stdin=subprocess.PIPE,
24                          stderr=subprocess.PIPE)
25 
26         stderr=act_res.stderr.read()
27         stdout=act_res.stdout.read()
28         conn.send(stderr)
29         conn.send(stdout)
服務端
 1 import socket
 2 BUFSIZE=1024
 3 ip_port=('127.0.0.1',8080)
 4 
 5 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 6 res=s.connect_ex(ip_port)
 7 
 8 while True:
 9     msg=input('>>: ').strip()
10     if len(msg) == 0:continue
11     if msg == 'quit':break
12 
13     s.send(msg.encode('utf-8'))
14     act_res=s.recv(BUFSIZE)
15 
16     print(act_res.decode('utf-8'),end='')
客戶端

上述程序是基於tcp的socket,在運行時會發生粘包

ps.udp協議:

 1 from socket import *
 2 import subprocess
 3 
 4 ip_port=('127.0.0.1',9003)
 5 bufsize=1024
 6 
 7 udp_server=socket(AF_INET,SOCK_DGRAM)
 8 udp_server.bind(ip_port)
 9 
10 while True:
11     #收消息
12     cmd,addr=udp_server.recvfrom(bufsize)
13     print('用戶命令----->',cmd)
14 
15     #邏輯處理
16     res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stderr=subprocess.PIPE,stdin=subprocess.PIPE,stdout=subprocess.PIPE)
17     stderr=res.stderr.read()
18     stdout=res.stdout.read()
19 
20     #發消息
21     udp_server.sendto(stderr,addr)
22     udp_server.sendto(stdout,addr)
23 udp_server.close()
服務端
 1 from socket import *
 2 ip_port=('127.0.0.1',9003)
 3 bufsize=1024
 4 
 5 udp_client=socket(AF_INET,SOCK_DGRAM)
 6 
 7 
 8 while True:
 9     msg=input('>>: ').strip()
10     udp_client.sendto(msg.encode('utf-8'),ip_port)
11 
12     data,addr=udp_client.recvfrom(bufsize)
13     print(data.decode('utf-8'),end='')
客戶端

  四、解決粘包

  爲字節流加上自定義固定長度報頭,報頭中包含字節流長度,而後一次send到對端,對端在接收時,先從緩存中取出定長的報頭,而後再取真實數據。

struct模塊 

該模塊能夠把一個類型,如數字,轉成固定長度的bytes

a.struct詳細用法:

 1 import struct
 2 import binascii
 3 import ctypes
 4 
 5 values1 = (1, 'abc'.encode('utf-8'), 2.7)
 6 values2 = ('defg'.encode('utf-8'),101)
 7 s1 = struct.Struct('I3sf')
 8 s2 = struct.Struct('4sI')
 9 
10 print(s1.size,s2.size)
11 prebuffer=ctypes.create_string_buffer(s1.size+s2.size)
12 print('Before : ',binascii.hexlify(prebuffer))
13 # t=binascii.hexlify('asdfaf'.encode('utf-8'))
14 # print(t)
15 
16 
17 s1.pack_into(prebuffer,0,*values1)
18 s2.pack_into(prebuffer,s1.size,*values2)
19 
20 print('After pack',binascii.hexlify(prebuffer))
21 print(s1.unpack_from(prebuffer,0))
22 print(s2.unpack_from(prebuffer,s1.size))
23 
24 s3=struct.Struct('ii')
25 s3.pack_into(prebuffer,0,123,123)
26 print('After pack',binascii.hexlify(prebuffer))
27 print(s3.unpack_from(prebuffer,0))
View Code

b.使用原理

 1 import json,struct
 2 #假設經過客戶端上傳1T:1073741824000的文件a.txt
 3 
 4 #爲避免粘包,必須自定製報頭
 5 header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T數據,文件路徑和md5值
 6 
 7 #爲了該報頭能傳送,須要序列化而且轉爲bytes
 8 head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化並轉成bytes,用於傳輸
 9 
10 #爲了讓客戶端知道報頭的長度,用struck將報頭長度這個數字轉成固定長度:4個字節
11 head_len_bytes=struct.pack('i',len(head_bytes)) #這4個字節裏只包含了一個數字,該數字是報頭的長度
12 
13 #客戶端開始發送
14 conn.send(head_len_bytes) #先發報頭的長度,4個bytes
15 conn.send(head_bytes) #再發報頭的字節格式
16 conn.sendall(文件內容) #而後發真實內容的字節格式
17 
18 #服務端開始接收
19 head_len_bytes=s.recv(4) #先收報頭4個bytes,獲得報頭長度的字節格式
20 x=struct.unpack('i',head_len_bytes)[0] #提取報頭的長度
21 
22 head_bytes=s.recv(x) #按照報頭長度x,收取報頭的bytes格式
23 header=json.loads(json.dumps(header)) #提取報頭
24 
25 #最後根據報頭的內容提取真實的數據,好比
26 real_data_len=s.recv(header['file_size'])
27 s.recv(real_data_len)
View Code

c.自定義報頭

 1 import socket,struct,json
 2 import subprocess
 3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 4 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加
 5 
 6 phone.bind(('127.0.0.1',8080))
 7 
 8 phone.listen(5)
 9 
10 while True:
11     conn,addr=phone.accept()
12     while True:
13         cmd=conn.recv(1024)
14         if not cmd:break
15         print('cmd: %s' %cmd)
16 
17         res=subprocess.Popen(cmd.decode('utf-8'),
18                              shell=True,
19                              stdout=subprocess.PIPE,
20                              stderr=subprocess.PIPE)
21         err=res.stderr.read()
22         print(err)
23         if err:
24             back_msg=err
25         else:
26             back_msg=res.stdout.read()
27 
28 
29         conn.send(struct.pack('i',len(back_msg))) #先發back_msg的長度
30         conn.sendall(back_msg) #在發真實的內容
31 
32     conn.close()
服務端
 1 import socket,time,struct
 2 
 3 s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 4 res=s.connect_ex(('127.0.0.1',8080))
 5 
 6 while True:
 7     msg=input('>>: ').strip()
 8     if len(msg) == 0:continue
 9     if msg == 'quit':break
10 
11     s.send(msg.encode('utf-8'))
12 
13 
14 
15     l=s.recv(4)
16     x=struct.unpack('i',l)[0]
17     print(type(x),x)
18     # print(struct.unpack('I',l))
19     r_s=0
20     data=b''
21     while r_s < x:
22         r_d=s.recv(1024)
23         data+=r_d
24         r_s+=len(r_d)
25 
26     # print(data.decode('utf-8'))
27     print(data.decode('gbk')) #windows默認gbk編碼
客戶端

d.使用實例:

 1 import socket,struct,json
 2 import subprocess
 3 phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
 4 phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加
 5 
 6 phone.bind(('127.0.0.1',8080))
 7 
 8 phone.listen(5)
 9 
10 while True:
11     conn,addr=phone.accept()
12     while True:
13         cmd=conn.recv(1024)
14         if not cmd:break
15         print('cmd: %s' %cmd)
16 
17         res=subprocess.Popen(cmd.decode('utf-8'),
18                              shell=True,
19                              stdout=subprocess.PIPE,
20                              stderr=subprocess.PIPE)
21         err=res.stderr.read()
22         print(err)
23         if err:
24             back_msg=err
25         else:
26             back_msg=res.stdout.read()
27 
28         headers={'data_size':len(back_msg)}
29         head_json=json.dumps(headers)
30         head_json_bytes=bytes(head_json,encoding='utf-8')
31 
32         conn.send(struct.pack('i',len(head_json_bytes))) #先發報頭的長度
33         conn.send(head_json_bytes) #再發報頭
34         conn.sendall(back_msg) #在發真實的內容
35 
36     conn.close()
服務端
 1 from socket import *
 2 import struct,json
 3 
 4 ip_port=('127.0.0.1',8080)
 5 client=socket(AF_INET,SOCK_STREAM)
 6 client.connect(ip_port)
 7 
 8 while True:
 9     cmd=input('>>: ')
10     if not cmd:continue
11     client.send(bytes(cmd,encoding='utf-8'))
12 
13     head=client.recv(4)
14     head_json_len=struct.unpack('i',head)[0]
15     head_json=json.loads(client.recv(head_json_len).decode('utf-8'))
16     data_len=head_json['data_size']
17 
18     recv_size=0
19     recv_data=b''
20     while recv_size < data_len:
21         recv_data+=client.recv(1024)
22         recv_size+=len(recv_data)
23 
24     print(recv_data.decode('utf-8'))
25     #print(recv_data.decode('gbk')) #windows默認gbk編碼
客戶端

e.ftp上傳下載

  1 import socket
  2 import struct
  3 import json
  4 import subprocess
  5 import os
  6 
  7 class MYTCPServer:
  8     address_family = socket.AF_INET
  9 
 10     socket_type = socket.SOCK_STREAM
 11 
 12     allow_reuse_address = False
 13 
 14     max_packet_size = 8192
 15 
 16     coding='utf-8'
 17 
 18     request_queue_size = 5
 19 
 20     server_dir='file_upload'
 21 
 22     def __init__(self, server_address, bind_and_activate=True):
 23         """Constructor.  May be extended, do not override."""
 24         self.server_address=server_address
 25         self.socket = socket.socket(self.address_family,
 26                                     self.socket_type)
 27         if bind_and_activate:
 28             try:
 29                 self.server_bind()
 30                 self.server_activate()
 31             except:
 32                 self.server_close()
 33                 raise
 34 
 35     def server_bind(self):
 36         """Called by constructor to bind the socket.
 37         """
 38         if self.allow_reuse_address:
 39             self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
 40         self.socket.bind(self.server_address)
 41         self.server_address = self.socket.getsockname()
 42 
 43     def server_activate(self):
 44         """Called by constructor to activate the server.
 45         """
 46         self.socket.listen(self.request_queue_size)
 47 
 48     def server_close(self):
 49         """Called to clean-up the server.
 50         """
 51         self.socket.close()
 52 
 53     def get_request(self):
 54         """Get the request and client address from the socket.
 55         """
 56         return self.socket.accept()
 57 
 58     def close_request(self, request):
 59         """Called to clean up an individual request."""
 60         request.close()
 61 
 62     def run(self):
 63         while True:
 64             self.conn,self.client_addr=self.get_request()
 65             print('from client ',self.client_addr)
 66             while True:
 67                 try:
 68                     head_struct = self.conn.recv(4)
 69                     if not head_struct:break
 70 
 71                     head_len = struct.unpack('i', head_struct)[0]
 72                     head_json = self.conn.recv(head_len).decode(self.coding)
 73                     head_dic = json.loads(head_json)
 74 
 75                     print(head_dic)
 76                     #head_dic={'cmd':'put','filename':'a.txt','filesize':123123}
 77                     cmd=head_dic['cmd']
 78                     if hasattr(self,cmd):
 79                         func=getattr(self,cmd)
 80                         func(head_dic)
 81                 except Exception:
 82                     break
 83 
 84     def put(self,args):
 85         file_path=os.path.normpath(os.path.join(
 86             self.server_dir,
 87             args['filename']
 88         ))
 89 
 90         filesize=args['filesize']
 91         recv_size=0
 92         print('----->',file_path)
 93         with open(file_path,'wb') as f:
 94             while recv_size < filesize:
 95                 recv_data=self.conn.recv(self.max_packet_size)
 96                 f.write(recv_data)
 97                 recv_size+=len(recv_data)
 98                 print('recvsize:%s filesize:%s' %(recv_size,filesize))
 99 
100 
101 tcpserver1=MYTCPServer(('127.0.0.1',8080))
102 
103 tcpserver1.run()
服務端
 1 import socket
 2 import struct
 3 import json
 4 import os
 5 
 6 
 7 
 8 class MYTCPClient:
 9     address_family = socket.AF_INET
10 
11     socket_type = socket.SOCK_STREAM
12 
13     allow_reuse_address = False
14 
15     max_packet_size = 8192
16 
17     coding='utf-8'
18 
19     request_queue_size = 5
20 
21     def __init__(self, server_address, connect=True):
22         self.server_address=server_address
23         self.socket = socket.socket(self.address_family,
24                                     self.socket_type)
25         if connect:
26             try:
27                 self.client_connect()
28             except:
29                 self.client_close()
30                 raise
31 
32     def client_connect(self):
33         self.socket.connect(self.server_address)
34 
35     def client_close(self):
36         self.socket.close()
37 
38     def run(self):
39         while True:
40             inp=input(">>: ").strip()
41             if not inp:continue
42             l=inp.split()
43             cmd=l[0]
44             if hasattr(self,cmd):
45                 func=getattr(self,cmd)
46                 func(l)
47 
48 
49     def put(self,args):
50         cmd=args[0]
51         filename=args[1]
52         if not os.path.isfile(filename):
53             print('file:%s is not exists' %filename)
54             return
55         else:
56             filesize=os.path.getsize(filename)
57 
58         head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize}
59         print(head_dic)
60         head_json=json.dumps(head_dic)
61         head_json_bytes=bytes(head_json,encoding=self.coding)
62 
63         head_struct=struct.pack('i',len(head_json_bytes))
64         self.socket.send(head_struct)
65         self.socket.send(head_json_bytes)
66         send_size=0
67         with open(filename,'rb') as f:
68             for line in f:
69                 self.socket.send(line)
70                 send_size+=len(line)
71                 print(send_size)
72             else:
73                 print('upload successful')
74 
75 
76 
77 
78 client=MYTCPClient(('127.0.0.1',8080))
79 
80 client.run()
客戶端

8、認證客戶端的連接合法性

若是你想在分佈式系統中實現一個簡單的客戶端連接認證功能,又不像SSL那麼複雜,那麼利用hmac+加鹽的方式來實現。

 1 from socket import *
 2 import hmac,os
 3 
 4 secret_key=b'linhaifeng bang bang bang'
 5 def conn_auth(conn):
 6     '''
 7     認證客戶端連接
 8     :param conn:
 9     :return:
10     '''
11     print('開始驗證新連接的合法性')
12     msg=os.urandom(32)
13     conn.sendall(msg)
14     h=hmac.new(secret_key,msg)
15     digest=h.digest()
16     respone=conn.recv(len(digest))
17     return hmac.compare_digest(respone,digest)
18 
19 def data_handler(conn,bufsize=1024):
20     if not conn_auth(conn):
21         print('該連接不合法,關閉')
22         conn.close()
23         return
24     print('連接合法,開始通訊')
25     while True:
26         data=conn.recv(bufsize)
27         if not data:break
28         conn.sendall(data.upper())
29 
30 def server_handler(ip_port,bufsize,backlog=5):
31     '''
32     只處理連接
33     :param ip_port:
34     :return:
35     '''
36     tcp_socket_server=socket(AF_INET,SOCK_STREAM)
37     tcp_socket_server.bind(ip_port)
38     tcp_socket_server.listen(backlog)
39     while True:
40         conn,addr=tcp_socket_server.accept()
41         print('新鏈接[%s:%s]' %(addr[0],addr[1]))
42         data_handler(conn,bufsize)
43 
44 if __name__ == '__main__':
45     ip_port=('127.0.0.1',9999)
46     bufsize=1024
47     server_handler(ip_port,bufsize)
服務端
 1 from socket import *
 2 import hmac,os
 3 
 4 secret_key=b'linhaifeng bang bang bang'
 5 def conn_auth(conn):
 6     '''
 7     驗證客戶端到服務器的連接
 8     :param conn:
 9     :return:
10     '''
11     msg=conn.recv(32)
12     h=hmac.new(secret_key,msg)
13     digest=h.digest()
14     conn.sendall(digest)
15 
16 def client_handler(ip_port,bufsize=1024):
17     tcp_socket_client=socket(AF_INET,SOCK_STREAM)
18     tcp_socket_client.connect(ip_port)
19 
20     conn_auth(tcp_socket_client)
21 
22     while True:
23         data=input('>>: ').strip()
24         if not data:continue
25         if data == 'quit':break
26 
27         tcp_socket_client.sendall(data.encode('utf-8'))
28         respone=tcp_socket_client.recv(bufsize)
29         print(respone.decode('utf-8'))
30     tcp_socket_client.close()
31 
32 if __name__ == '__main__':
33     ip_port=('127.0.0.1',9999)
34     bufsize=1024
35     client_handler(ip_port,bufsize)
客戶端合法
 1 from socket import *
 2 
 3 def client_handler(ip_port,bufsize=1024):
 4     tcp_socket_client=socket(AF_INET,SOCK_STREAM)
 5     tcp_socket_client.connect(ip_port)
 6 
 7     while True:
 8         data=input('>>: ').strip()
 9         if not data:continue
10         if data == 'quit':break
11 
12         tcp_socket_client.sendall(data.encode('utf-8'))
13         respone=tcp_socket_client.recv(bufsize)
14         print(respone.decode('utf-8'))
15     tcp_socket_client.close()
16 
17 if __name__ == '__main__':
18     ip_port=('127.0.0.1',9999)
19     bufsize=1024
20     client_handler(ip_port,bufsize)
客戶端(非法)
 1 from socket import *
 2 import hmac,os
 3 
 4 secret_key=b'linhaifeng bang bang bang1111'
 5 def conn_auth(conn):
 6     '''
 7     驗證客戶端到服務器的連接
 8     :param conn:
 9     :return:
10     '''
11     msg=conn.recv(32)
12     h=hmac.new(secret_key,msg)
13     digest=h.digest()
14     conn.sendall(digest)
15 
16 def client_handler(ip_port,bufsize=1024):
17     tcp_socket_client=socket(AF_INET,SOCK_STREAM)
18     tcp_socket_client.connect(ip_port)
19 
20     conn_auth(tcp_socket_client)
21 
22     while True:
23         data=input('>>: ').strip()
24         if not data:continue
25         if data == 'quit':break
26 
27         tcp_socket_client.sendall(data.encode('utf-8'))
28         respone=tcp_socket_client.recv(bufsize)
29         print(respone.decode('utf-8'))
30     tcp_socket_client.close()
31 
32 if __name__ == '__main__':
33     ip_port=('127.0.0.1',9999)
34     bufsize=1024
35     client_handler(ip_port,bufsize)
客戶端(非法)

9、socktserver併發

基於tcp的套接字,關鍵就是兩個循環,一個連接循環,一個通訊循環

socketserver模塊中分兩大類:server類(解決連接問題)和request類(解決通訊問題)

server類:

request類:

繼承關係:

 

 

如下述代碼爲例,分析socketserver源碼:

ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer)
ftpserver.serve_forever()
查找屬性的順序:ThreadingTCPServer->ThreadingMixIn->TCPServer->BaseServer

    • 實例化獲得ftpserver,先找類ThreadingTCPServer的__init__,在TCPServer中找到,進而執行server_bind,server_active
    • 找ftpserver下的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中找....

源碼分析總結:

基於tcp的socketserver咱們本身定義的類中的

a.self.server即套接字對象
b.self.request即一個連接
c.self.client_address即客戶端地址

基於udp的socketserver咱們本身定義的類中的

a.self.request是一個元組(第一個元素是客戶端發來的數據,第二部分是服務端的udp套接字對象),如(b'adsf', <socket.socket fd=200, family=AddressFamily.AF_INET, type=SocketKind.SOCK_DGRAM, proto=0, laddr=('127.0.0.1', 8080)>)
b.self.client_address即客戶端地址

ftp示例代碼:

 1 import socketserver
 2 import struct
 3 import json
 4 import os
 5 class FtpServer(socketserver.BaseRequestHandler):
 6     coding='utf-8'
 7     server_dir='file_upload'
 8     max_packet_size=1024
 9     BASE_DIR=os.path.dirname(os.path.abspath(__file__))
10     def handle(self):
11         print(self.request)
12         while True:
13             data=self.request.recv(4)
14             data_len=struct.unpack('i',data)[0]
15             head_json=self.request.recv(data_len).decode(self.coding)
16             head_dic=json.loads(head_json)
17             # print(head_dic)
18             cmd=head_dic['cmd']
19             if hasattr(self,cmd):
20                 func=getattr(self,cmd)
21                 func(head_dic)
22     def put(self,args):
23         file_path = os.path.normpath(os.path.join(
24             self.BASE_DIR,
25             self.server_dir,
26             args['filename']
27         ))
28 
29         filesize = args['filesize']
30         recv_size = 0
31         print('----->', file_path)
32         with open(file_path, 'wb') as f:
33             while recv_size < filesize:
34                 recv_data = self.request.recv(self.max_packet_size)
35                 f.write(recv_data)
36                 recv_size += len(recv_data)
37                 print('recvsize:%s filesize:%s' % (recv_size, filesize))
38 
39 
40 ftpserver=socketserver.ThreadingTCPServer(('127.0.0.1',8080),FtpServer)
41 ftpserver.serve_forever()
服務器
 1 import socket
 2 import struct
 3 import json
 4 import os
 5 
 6 
 7 
 8 class MYTCPClient:
 9     address_family = socket.AF_INET
10 
11     socket_type = socket.SOCK_STREAM
12 
13     allow_reuse_address = False
14 
15     max_packet_size = 8192
16 
17     coding='utf-8'
18 
19     request_queue_size = 5
20 
21     def __init__(self, server_address, connect=True):
22         self.server_address=server_address
23         self.socket = socket.socket(self.address_family,
24                                     self.socket_type)
25         if connect:
26             try:
27                 self.client_connect()
28             except:
29                 self.client_close()
30                 raise
31 
32     def client_connect(self):
33         self.socket.connect(self.server_address)
34 
35     def client_close(self):
36         self.socket.close()
37 
38     def run(self):
39         while True:
40             inp=input(">>: ").strip()
41             if not inp:continue
42             l=inp.split()
43             cmd=l[0]
44             if hasattr(self,cmd):
45                 func=getattr(self,cmd)
46                 func(l)
47 
48 
49     def put(self,args):
50         cmd=args[0]
51         filename=args[1]
52         if not os.path.isfile(filename):
53             print('file:%s is not exists' %filename)
54             return
55         else:
56             filesize=os.path.getsize(filename)
57 
58         head_dic={'cmd':cmd,'filename':os.path.basename(filename),'filesize':filesize}
59         print(head_dic)
60         head_json=json.dumps(head_dic)
61         head_json_bytes=bytes(head_json,encoding=self.coding)
62 
63         head_struct=struct.pack('i',len(head_json_bytes))
64         self.socket.send(head_struct)
65         self.socket.send(head_json_bytes)
66         send_size=0
67         with open(filename,'rb') as f:
68             for line in f:
69                 self.socket.send(line)
70                 send_size+=len(line)
71                 print(send_size)
72             else:
73                 print('upload successful')
74 
75 
76 
77 
78 client=MYTCPClient(('127.0.0.1',8080))
79 
80 client.run()
客戶端
相關文章
相關標籤/搜索