Python--網絡編程

軟件開發的架構

咱們瞭解的涉及到兩個程序之間通信的應用大體分爲兩種:html

第一種是應用類:qq、微信、網盤、優酷這一類是屬於須要安裝的桌面應用python

第二種是web類:好比百度、知乎、博客園等使用瀏覽器訪問就能夠直接使用的應用web

這些應用的本質其實都是兩個程序之間的通信。而這兩個分類又對應了兩個軟件開發的架構~算法

C/S架構

C/S即:Client與Server,中文意思:客戶端與服務端架構,這種架構也是從用戶層面(也能夠是物理層面)來劃分的。shell

這裏的客戶端通常泛指客戶端應用程序EXE,程序須要先安裝後,才能運行在用戶的電腦上,對用戶的電腦操做系統環境依賴較大。編程

B/S架構

B/S即:Browser與Server,中文意思:瀏覽器端與服務器端架構,這種架構是從用戶層面來劃分的json

Browser瀏覽器,其實也是一種Client客戶端,只是這個客戶端不須要用戶去安裝什麼應用程序,只須要在瀏覽器上經過HTTP請求服務端相關的資源(網頁資源),客戶端Browser瀏覽器就能進行增刪改查。windows

B/S架構和C/S的區別

C/S架構的優缺點: *優勢:
    1.客戶端由於是獨立設計,因此能夠實現個性化
    2.由於客戶端是須要進行安裝的,能夠不須要重複安裝和加載
    3.由於客戶端是獨立開發的,因此有能力對客戶端進行安全設計
4.若是遇到不一樣的操做系統,須要爲不一樣的操做系統各開發一套客戶端
*缺點:
    1.由於客戶端是不須要重複安裝,因此用戶能夠不更新與升級,增長了維護成本。
    2.由於須要開發客戶端和服務器兩套程序,因此開發成本會增長
B/S架構的優缺點: *優勢:
    1.由於B/S架構具有通用性,因此開發成本較低。
    2.由於不須要安裝客戶端,因此客戶端不須要進行升級,只須要更新後臺代碼便可實現全部客戶端的更新。
    3.由於B/S架構多用WEB網頁進行開發,因此增、刪功能也很是容易,只須要修改網頁便可完成
*缺點:
    1.耗流量,每次都要加載所有的內容(不過有緩存能夠下降流量損耗)
    2.由於沒有獨立的客戶端,因此沒法實現個性化(經過帳號體系能夠實現)
    3.由於沒有獨立設計客戶端,因此客戶端難以實現安全控制(HTTPS、控件)。
    4.難以實現特殊的操做(刪本地文件),因此全部的殺毒軟件都是C/S架構的。
B/S架構更多的時候是使用了HTTP協議、而C/S架構更多的時候使用的WinSocket協議(TCP、UDP)

socket 概念

socket層瀏覽器

Socket是應用層與TCP/IP協議族通訊的中間軟件抽象層,它是一組接口。在設計模型中,Sokcet其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket接口後面,對用戶來講,一組簡單的接口就是所有,讓Socket去組織數據,以符合指定的協議。緩存

socket就是一個模塊。咱們經過調用模塊中已經實現的方法創建兩個進程之間的鏈接和通訊。
也有人將socket說成ip+port,由於ip是用來標識互聯網中的一臺主機的位置,而port是用來標識這臺機器上的一個應用程序。
因此咱們只要確立了ip和port就能找到一個應用程序,而且使用socket模塊來與之通訊。
通俗講socket

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

基於文件類型的套接字家族

套接字家族的名稱:AF_UNIX

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

基於網絡類型的套接字家族

套接字家族的名稱:AF_INET

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

套接字工做流程

一個生活中的場景。你要打電話給一個朋友,先撥號,朋友聽到電話鈴聲後提起電話,這時你和你的朋友就創建起了鏈接,就能夠講話了。等交流結束後,掛斷電話結束這次交談。生活中的場景就解釋了這工做原理。

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

socket 模塊使用方法

 1 import socket
 2 socket.socket(socket_family, socket_type, protocal=0)
 3 
 4     socket_faily:能夠是 AF_UNIX 或 AF_INET; 
 5     socket_type:能夠是 SOCK_STREAM 或 SOCK_DGRAM;
 6     protocol:通常不填,默認值爲 0;
 7 
 8 獲取tcp/ip套接字
 9 tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
10 
11 獲取udp/ip套接字
12 dupSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
13 
14 因爲 socket 模塊中有太多的屬性。咱們在這裏破例使用了'from module import *'語句。使用 'from socket import *',咱們就把 socket 模塊裏的全部屬性都帶到咱們的命名空間裏了,這樣能 大幅減短咱們的代碼。
15 12 例如tcpSock = socket(AF_INET, SOCK_STREAM)
  • 服務端套接字函數
    s.bind()    綁定(主機,端口號)到套接字
    s.listen()  開始TCP監聽
    s.accept()  被動接受TCP客戶的鏈接,(阻塞式)等待鏈接的到來
  • 客戶端套接字函數
    s.connect()     主動初始化TCP服務器鏈接
    s.connect_ex()  connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常
  • 公共用途的套接字函數
    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()           關閉套接字
  • 面向鎖的套接字方法
    s.setblocking()     設置套接字的阻塞與非阻塞模式
    s.settimeout()      設置阻塞套接字操做的超時時間
    s.gettimeout()      獲得阻塞套接字操做的超時時間
  • 面向文件的套接字函數
    s.fileno()          套接字的文件描述符
    s.makefile()        建立一個與該套接字相關的文件

基於TCP協議的socket

tcp是基於鏈接的,必須先啓動服務端,而後再啓動客戶端去鏈接服務端

tcp服務端

import socket
sk = socket.socket()            # 建立服務器套接字對象
sk.bind(('127.0.0.1', 8088))    # 把地址綁定到套接字
sk.listen()                     # 監聽鏈接
conn, addr = sk.accept()        # 接受客戶端鏈接
ret = conn.recv(1024)           # 接受客戶端信息
print(ret)                      # 打印客戶端信息
conn.send(b'hello')             # 向客戶端發送信息
conn.close()                    # 關閉客戶端套接字
sk.close()                      # 關閉服務器套接字(可選)

tcp客戶端

import socket
sk = socket.socket()            # 建立客戶套接字
sk.connect(('127.0.0.1', 8088)) # 嘗試鏈接服務器
sk.send(b'Hai')                 # 向服務端發送信息
ret = sk.recv(1024)             # 接受服務端發送的信息
print(ret)                      # 打印服務端接受到的信息
sk.close()                      # 關閉客戶套接字

socket通訊流程與打電話流程相似,例子:

import socket
ip_port = ('127.0.0.1', 8088)   # 電話卡
buffer = 1024       # 收發消息的字節大小
sk = socket.socket(socket.AF_INET, socket.SOCK_STREAM)  # 買手機
sk.bind(ip_port)    # 手機插卡
sk.listen()     # 手機待機

conn, addr = sk.accept()        # 手機接電話
print('接到來自%s的電話' %addr[0])

msg = conn.recv(buffer)     # 聽消息,聽話
print(msg, type(msg))

conn.send(msg.upper())      # 發消息,說話

conn.close()        # 掛電話

sk.close()      # 手機關機
服務端
import socket
ip_port = ('127.0.0.1',8088)
buffer = 1024
s=socket.socket(socket.AF_INET, socket.SOCK_STREAM)

s.connect_ex(ip_port)           # 撥電話

s.send('hello world'.encode('utf-8'))         # 發消息,說話(只能發送字節類型)

feedback=s.recv(buffer)                       # 收消息,聽話
print(feedback.decode('utf-8'))

s.close()           # 掛電話
客戶端

基於tcp實現的qq聊天

import socket
ip_port = ('127.0.0.1', 8088)
buffer = 1024
sk = socket.socket()
sk.bind(ip_port)
sk.listen()
coon, addr = sk.accept()

while True:
    msg = coon.recv(buffer).decode('utf-8')
    if msg == 'bye':
        break
    print(msg)
    info = input(">>>: ")
    if info == 'q':
        coon.send(b'bye')
        break
    coon.send(info.encode('utf-8'))

coon.close()
sk.close()
server端
import socket
ip_port = ('127.0.0.1', 8088)
buffer = 1024
sk = socket.socket()
sk.connect(ip_port)

while True:
    msg = input(">>>: ")
    if msg == 'q':
        sk.send(b'bye')
        break
    sk.send(msg.encode('utf-8'))
    info = sk.recv(buffer).decode('utf-8')
    if info == 'bye':
        break
    print(info)
sk.close()
客戶端

基於UDP協議的socket

udp是無連接的,啓動服務以後能夠直接接收消息,不須要提早創建鏈接

udp服務端

import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)  # 建立一個服務器的套接字
udp_sk.bind(('127.0.0.1', 8900))    # 綁定服務器套接字
msg, addr = udp_sk.recvfrom(1024)   # 接受消息
print(msg.decode('utf-8'))
udp_sk.sendto(b'bye', addr)     # 發送消息
udp_sk.close()  # 關閉服務器套接字

# udp的server 不須要監聽也不須要創建鏈接
# 在啓動服務後只能被動的等待客戶端發送消息過來
# 客戶端發送消息的同時還會自帶地址信息
# 消息回覆的時候,不只須要發送消息,還須要把對方的地址填寫上

udp客戶端

import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1', 8900)
udp_sk.sendto(b'hello', ip_port)
ret, addr = udp_sk.recvfrom(1024)
print(ret.decode('utf-8'))
udp_sk.close()

基於udp實現的qq聊天

import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)
udp_sk.bind(('127.0.0.1', 8090))
while True:
    msg, addr = udp_sk.recvfrom(1024)
    print(msg.decode('utf-8'))
    info = input("server:>>> ").encode('utf-8')
    udp_sk.sendto(info, addr)
sk.close()
服務端
# 客戶端1
import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1', 8090)
while True:
    info = input("client1>>>: ")
    info = ('\033[34m 來自client1的消息: %s\033[0m' % info).encode('utf-8')
    udp_sk.sendto(info, ip_port)
    msg, addr = udp_sk.recvfrom(1024)
    print(msg.decode('utf-8'))
udp_sk.close()
客戶端1
# 客戶端2
import socket
udp_sk = socket.socket(type=socket.SOCK_DGRAM)
ip_port = ('127.0.0.1', 8090)
while True:
    info = input("client1>>>: ")
    info = ('\033[34m 來自client2的消息: %s\033[0m' % info).encode('utf-8')
    udp_sk.sendto(info, ip_port)
    msg, addr = udp_sk.recvfrom(1024)
    print(msg.decode('utf-8'))
udp_sk.close()
客戶端2

基於udp實現的時間服務器

# 需求
    # 寫一個時間同步的服務器
    # 服務端接收請求
    # 按照client端發送的時間格式,將服務器時間轉換成對應格式
    # 發送給客戶端

from socket import *
from time import strftime

ip_port = ('127.0.0.1', 9000)
bufsize = 1024

tcp_server = socket(AF_INET, SOCK_DGRAM)
tcp_server.bind(ip_port)

while True:
    msg, addr = tcp_server.recvfrom(bufsize)
    print('===>', msg)

    if not msg:
        time_fmt = '%Y-%m-%d %X'
    else:
        time_fmt = msg.decode('utf-8')
    back_msg = strftime(time_fmt)

    tcp_server.sendto(back_msg.encode('utf-8'), addr)

tcp_server.close()
服務端
# 發送時間格式
# client端每隔一段時間發送請求到服務端
# 發送時間的格式
from socket import *
ip_port=('127.0.0.1',9000)
bufsize=1024

tcp_client=socket(AF_INET,SOCK_DGRAM)

while True:
    msg=input('請輸入時間格式(例%Y %m %d)>>: ').strip()
    tcp_client.sendto(msg.encode('utf-8'),ip_port)

    data=tcp_client.recv(bufsize)

    print(data.decode('utf-8'))

tcp_client.close()
客戶端

socket參數的解釋

socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)

建立socket對象的參數說明:

family 地址系列應爲AF_INET(默認值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。
(AF_UNIX 域其實是使用本地 socket 文件來通訊)
type 套接字類型應爲SOCK_STREAM(默認值),SOCK_DGRAM,SOCK_RAW或其餘SOCK_常量之一。
SOCK_STREAM 是基於TCP的,有保障的(即能保證數據正確傳送到對方)面向鏈接的SOCKET,多用於資料傳送。 
SOCK_DGRAM 是基於UDP的,無保障的面向消息的socket,多用於在網絡上發廣播信息。
proto 協議號一般爲零,能夠省略,或者在地址族爲AF_CAN的狀況下,協議應爲CAN_RAW或CAN_BCM之一。
fileno 若是指定了fileno,則其餘參數將被忽略,致使帶有指定文件描述符的套接字返回。
與socket.fromfd()不一樣,fileno將返回相同的套接字,而不是重複的。
這可能有助於使用socket.close()關閉一個獨立的插座。

黏包

黏包現象

讓咱們基於tcp先建立一個遠程執行命令的程序(命令ls -l;llll;pwd)

import subprocess
# 內置模塊 和os模塊的功能有類似之處
# 能執行操做系統的命令的功能
ret = subprocess.Popen('dir',    # 要執行的命令
                       shell=True,  # 表示要執行的是一條系統命令
                       stdout=subprocess.PIPE, # 存儲執行結果的正常信息
                       stderr=subprocess.PIPE) # 存儲執行結果的錯誤信息

的結果的編碼是以當前所在的系統爲準的,若是是windows,那麼res.stdout.read()讀出的就是GBK編碼的,在接收端須要用GBK解碼

且只能從管道里讀一次結果
subprocess模塊

同時執行多條命令以後,獲得的結果極可能只有一部分,在執行其餘命令的時候又接受到以前執行的另外一部分的結果,這種現象就是黏包

基於tcp協議實現的黏包

from socket import *
import subprocess

ip_port=('127.0.0.1',8081)
BUFSIZE=1024
code = 'gbk'    # 在windows上面執行命令時候返回的是gbk, 因此這裏所有使用gbk

tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)

while True:
    conn,addr=tcp_socket_server.accept()
    print('客戶端',addr)

    while True:
        cmd=conn.recv(BUFSIZE)
        if len(cmd) == 0:break

        res=subprocess.Popen(cmd.decode(code),shell=True,
                         stdout=subprocess.PIPE,
                         stdin=subprocess.PIPE,
                         stderr=subprocess.PIPE)

        stderr=res.stderr.read()
        stdout=res.stdout.read()
        conn.send(stderr)
        conn.send(stdout)
tcp - server
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8081)
code = 'gbk'    # 在windows上面執行命令時候返回的是gbk, 因此這裏所有使用gbk

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)

while True:
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    if msg == 'quit':break

    s.send(msg.encode(code))
    act_res=s.recv(BUFSIZE)

    print(act_res.decode(code),end='')
tcp - client

基於udp協議實現的黏包

from socket import *
import subprocess

ip_port=('127.0.0.1',9000)
bufsize=1024
code = 'gbk'    # 在windows上面執行命令時候返回的是gbk, 因此這裏所有使用gbk

udp_server=socket(AF_INET,SOCK_DGRAM)
udp_server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
udp_server.bind(ip_port)

while True:
    #收消息
    cmd,addr=udp_server.recvfrom(bufsize)
    print('用戶命令----->',cmd)

    #邏輯處理
    res=subprocess.Popen(cmd.decode(code),shell=True,stderr=subprocess.PIPE,stdin=subprocess.PIPE,stdout=subprocess.PIPE)
    stderr=res.stderr.read()
    stdout=res.stdout.read()

    #發消息
    udp_server.sendto(stderr,addr)
    udp_server.sendto(stdout,addr)
udp_server.close()
udp - server
from socket import *
ip_port=('127.0.0.1',9000)
bufsize=1024
code = 'gbk'    # 在windows上面執行命令時候返回的是gbk, 因此這裏所有使用gbk

udp_client=socket(AF_INET,SOCK_DGRAM)

while True:
    msg=input('>>: ').strip()
    udp_client.sendto(msg.encode(code),ip_port)
    err,addr=udp_client.recvfrom(bufsize)
    out,addr=udp_client.recvfrom(bufsize)
    if err:
        print('error : %s'%err.decode(code),end='')
    if out:
        print(out.decode(code), end='')
udp - client

注意:只有TCP有黏包現象,UDP永遠不會有黏包 

黏包成因

TCP協議中的數據傳遞

tcp協議的拆包機制

當放鬆段緩衝區的長度大於網卡的MTU時,tcp會將此次發送給的數據拆成幾個數據包發送過去。
MTU是Maximum Transmission Unit的縮寫,意思是網絡上傳送的最大數據包。MTU的單位是字節。大部分網絡設備的MTU都是1500;
若是本機的MTU比網關的MTU大,大的數據包就會被拆開來傳送,這樣會產生不少數據包碎片,增長丟包率,下降網絡速度。

面向流的通訊特色和Nagle算法

TCP(transport control protocol,傳輸控制協議)是面向鏈接的,面向流的,提供高可靠性服務。
收發兩端(客戶端和服務器端)都要有一一成對的socket,所以,發送端爲了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將屢次間隔較小且數據量小的數據,合併成一個大的數據塊,而後進行封包。
這樣,接收端,就難於分辨出來了,必須提供科學的拆包機制。 即面向流的通訊是無消息保護邊界的。
對於空消息:tcp是基於數據流的,因而收發的消息不能爲空,這就須要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基於數據報的,即使是你輸入的是空內容(直接回車),也能夠被髮送,udp協議會幫你封裝上消息頭髮送過去。
可靠黏包的tcp協議:tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端老是在收到ack時纔會清除緩衝區內容。數據是可靠的,可是會粘包。

基於tcp協議特色的黏包現象成因

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

例如基於tcp的套接字客戶端往服務端上傳文件,發送時文件內容是按照一段一段的字節流發送的,在接收方看了,根本不知道該文件的字節流從何處開始,在何處結束

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

UDP不會發生黏包

例如基於tcp的套接字客戶端往服務端上傳文件,發送時文件內容是按照一段一段的字節流發送的,在接收方看了,根本不知道該文件的字節流從何處開始,在何處結束

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

補充說明:

    用UDP協議發送時,用sendto函數最大能發送數據的長度爲:65535- IP頭(20) – UDP頭(8)=65507字節。用sendto函數發送數據時,若是發送數據長度大於該值,則函數會返回錯誤。(丟棄這個包,不進行發送) 

    用TCP協議發送時,因爲TCP是數據流協議,所以不存在包大小的限制(暫不考慮緩衝區的大小),這是指在用send函數時,數據長度參數不受限制。而實際上,所指定的這段數據並不必定會一次性發送出去,若是這段數據比較長,會被分段發送,若是比較短,可能會等待和下一次數據一塊兒發送。
udp和tcp一次發送數據長度的限制

會發生黏包的兩種狀況

狀況一 發送方的緩存機制:

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

#_*_coding:utf-8_*_
from socket import *
ip_port=('127.0.0.1',8080)

tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)


conn,addr=tcp_socket_server.accept()


data1=conn.recv(10)
data2=conn.recv(10)

print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))

conn.close()
服務端
#_*_coding:utf-8_*_
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)


s.send('hello'.encode('utf-8'))
s.send('egg'.encode('utf-8'))
客戶端

狀況二 接受方的緩存機制:

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

#_*_coding:utf-8_*_
from socket import *
ip_port=('127.0.0.1',8080)

tcp_socket_server=socket(AF_INET,SOCK_STREAM)
tcp_socket_server.bind(ip_port)
tcp_socket_server.listen(5)


conn,addr=tcp_socket_server.accept()


data1=conn.recv(2) #一次沒有收完整
data2=conn.recv(10)#下次收的時候,會先取舊的數據,而後取新的

print('----->',data1.decode('utf-8'))
print('----->',data2.decode('utf-8'))

conn.close()
服務端
#_*_coding:utf-8_*_
import socket
BUFSIZE=1024
ip_port=('127.0.0.1',8080)

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(ip_port)


s.send('hello egg'.encode('utf-8'))
客戶端

總結

黏包現象只發生在tcp協議中:

一、從表面上看,黏包問題主要是由於發送方和接收方的緩存機制、tcp協議面向流通訊的特色。

二、實際上,主要仍是由於接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所形成的。

粘包的解決方案

方案一

問題的根源在於,接收端不知道發送端要傳送的字節流的長度,因此解決粘包的方法就是圍繞,如何讓發送端在發送數據前,把本身要發送的字節流總大小讓接收端知曉,而後接收一個死循環接收完全部數據。

import socket

ip_port = ('127.0.0.1', 8088)
buffer = 1024

sk = socket.socket()
sk.bind(ip_port)
sk.listen()
coon, addr = sk.accept()

while True:
    cmd = input("命令>>>: ")
    if cmd == 'q':
        coon.send(b'bye')
        break
    coon.send(cmd.encode('gbk'))
    num = coon.recv(buffer).decode('utf-8') # 接受客戶端計算出來的內容的長度
    coon.send(b'ok')    # 回覆客戶端表示已經收到
    ret = coon.recv(int(num)).decode('gbk') # 按照內容的長度來指定接受的字節大小
    print(ret)

coon.close()
sk.close()
服務端
import socket
import subprocess
ip_port = ('127.0.0.1', 8088)
buffer = 1024

sk_client = socket.socket()
sk_client.connect(ip_port)

while True:
    cmd = sk_client.recv(buffer).decode('gbk')
    if cmd == 'bye':
        break
    print("服務端命令————>:%s" % cmd)
    ret = subprocess.Popen(cmd, shell=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) # 執行系統命令
    stdout = ret.stdout.read()
    stderr = ret.stderr.read()
    sk_client.send(str(len(stdout) + len(stderr)).encode('utf-8'))  # 發送計算出來的內容字節的長度
    sk_client.recv(buffer)
    # 發送命令執行完的數據
    sk_client.send(stdout)
    sk_client.send(stderr)

sk_client.close()
客戶端
存在的問題:
程序的運行速度遠快於網絡傳輸速度,因此在發送一段字節前,先用send去發送該字節流長度,這種方式會放大網絡延遲帶來的性能損耗

方案二

咱們能夠藉助一個模塊,這個模塊能夠把要發送的數據長度轉換成固定長度的字節。這樣客戶端每次接收消息以前只要先接受這個固定長度字節的內容看一看接下來要接收的信息大小,那麼最終接受的數據只要達到這個值就中止,就能恰好很少很多的接收完整的數據了

struct模塊

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

>>> struct.pack('i',1111111111111)

struct.error: 'i' format requires -2147483648 <= number <= 2147483647 #這個是範圍

import json,struct
#假設經過客戶端上傳1T:1073741824000的文件a.txt

#爲避免粘包,必須自定製報頭
header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T數據,文件路徑和md5值

#爲了該報頭能傳送,須要序列化而且轉爲bytes
head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化並轉成bytes,用於傳輸

#爲了讓客戶端知道報頭的長度,用struck將報頭長度這個數字轉成固定長度:4個字節
head_len_bytes=struct.pack('i',len(head_bytes)) #這4個字節裏只包含了一個數字,該數字是報頭的長度

#客戶端開始發送
conn.send(head_len_bytes) #先發報頭的長度,4個bytes
conn.send(head_bytes) #再發報頭的字節格式
conn.sendall(文件內容) #而後發真實內容的字節格式

#服務端開始接收
head_len_bytes=s.recv(4) #先收報頭4個bytes,獲得報頭長度的字節格式
x=struct.unpack('i',head_len_bytes)[0] #提取報頭的長度

head_bytes=s.recv(x) #按照報頭長度x,收取報頭的bytes格式
header=json.loads(json.dumps(header)) #提取報頭

#最後根據報頭的內容提取真實的數據,好比
real_data_len=s.recv(header['file_size'])
s.recv(real_data_len)
#_*_coding:utf-8_*_
#http://www.cnblogs.com/coser/archive/2011/12/17/2291160.html
import struct
import binascii
import ctypes

values1 = (1, 'abc'.encode('utf-8'), 2.7)
values2 = ('defg'.encode('utf-8'),101)
s1 = struct.Struct('I3sf')
s2 = struct.Struct('4sI')

print(s1.size,s2.size)
prebuffer=ctypes.create_string_buffer(s1.size+s2.size)
print('Before : ',binascii.hexlify(prebuffer))
# t=binascii.hexlify('asdfaf'.encode('utf-8'))
# print(t)


s1.pack_into(prebuffer,0,*values1)
s2.pack_into(prebuffer,s1.size,*values2)

print('After pack',binascii.hexlify(prebuffer))
print(s1.unpack_from(prebuffer,0))
print(s2.unpack_from(prebuffer,s1.size))

s3=struct.Struct('ii')
s3.pack_into(prebuffer,0,123,123)
print('After pack',binascii.hexlify(prebuffer))
print(s3.unpack_from(prebuffer,0))
關於struct的詳細用法

使用struct解決黏包

藉助struct模塊,咱們知道長度數字能夠被轉換成一個標準大小的4字節數字。所以能夠利用這個特色來預先發送數據長度。

發送時 接收時
先發送struct轉換好的數據長度4字節 先接收4個字節使用struct模塊轉成數字來獲取要接收的長度
再發送數據 再按照長度接收數據
import socket,struct,json
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加

phone.bind(('127.0.0.1',8080))

phone.listen(5)

while True:
    conn,addr=phone.accept()
    while True:
        cmd=conn.recv(1024)
        if not cmd:break
        print('cmd: %s' %cmd)

        res=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        err=res.stderr.read()
        print(err)
        if err:
            back_msg=err
        else:
            back_msg=res.stdout.read()


        conn.send(struct.pack('i',len(back_msg))) #先發back_msg的長度
        conn.sendall(back_msg) #在發真實的內容

    conn.close()
服務端(自定製報頭)
#_*_coding:utf-8_*_
import socket,time,struct

s=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
res=s.connect_ex(('127.0.0.1',8080))

while True:
    msg=input('>>: ').strip()
    if len(msg) == 0:continue
    if msg == 'quit':break

    s.send(msg.encode('utf-8'))



    l=s.recv(4)
    x=struct.unpack('i',l)[0]
    print(type(x),x)
    # print(struct.unpack('I',l))
    r_s=0
    data=b''
    while r_s < x:
        r_d=s.recv(1024)
        data+=r_d
        r_s+=len(r_d)

    # print(data.decode('utf-8'))
    print(data.decode('gbk')) #windows默認gbk編碼
客戶端(自定製報頭)

咱們還能夠把報頭作成字典,字典裏包含將要發送的真實數據的詳細信息,而後json序列化,而後用struck將序列化後的數據長度打包成4個字節(4個本身足夠用了)

發送時 接收時
先發送報頭長度 先收報頭長度,用struct取出來
再編碼報頭內容而後發送 根據取出的長度收取報頭內容,而後解碼,反序列化
最後發送真實內容 從反序列化的結果中取出待取數據的詳細信息,而後去取真實的數據內容
import socket,struct,json
import subprocess
phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM)
phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加

phone.bind(('127.0.0.1',8080))

phone.listen(5)

while True:
    conn,addr=phone.accept()
    while True:
        cmd=conn.recv(1024)
        if not cmd:break
        print('cmd: %s' %cmd)

        res=subprocess.Popen(cmd.decode('utf-8'),
                             shell=True,
                             stdout=subprocess.PIPE,
                             stderr=subprocess.PIPE)
        err=res.stderr.read()
        print(err)
        if err:
            back_msg=err
        else:
            back_msg=res.stdout.read()

        headers={'data_size':len(back_msg)}
        head_json=json.dumps(headers)
        head_json_bytes=bytes(head_json,encoding='utf-8')

        conn.send(struct.pack('i',len(head_json_bytes))) #先發報頭的長度
        conn.send(head_json_bytes) #再發報頭
        conn.sendall(back_msg) #在發真實的內容

    conn.close()
服務端:定製稍微複雜一點的報頭
from socket import *
import struct,json

ip_port=('127.0.0.1',8080)
client=socket(AF_INET,SOCK_STREAM)
client.connect(ip_port)

while True:
    cmd=input('>>: ')
    if not cmd:continue
    client.send(bytes(cmd,encoding='utf-8'))

    head=client.recv(4)
    head_json_len=struct.unpack('i',head)[0]
    head_json=json.loads(client.recv(head_json_len).decode('utf-8'))
    data_len=head_json['data_size']

    recv_size=0
    recv_data=b''
    while recv_size < data_len:
        recv_data+=client.recv(1024)
        recv_size+=len(recv_data)

    print(recv_data.decode('utf-8'))
    #print(recv_data.decode('gbk')) #windows默認gbk編碼
客戶端

實例:實現大文件的上傳,自定義包頭

初級版

# 實現一個大文件的上傳或者下載
import socket
import struct
import json


IP_PORT = ('127.0.0.1', 8088)
BUFFER = 4096
sk_server = socket.socket()
sk_server.bind(IP_PORT)
sk_server.listen()

coon, addr = sk_server.accept()

head_len = coon.recv(4)    # 接受客戶端處理過的head_len
head_len = struct.unpack('i', head_len)[0]
json_head = coon.recv(int(head_len)).decode('utf-8')
head = json.loads(json_head)    # 拿到報頭
filesize = head['filesize']     # 從包頭中取到文件大小

with open(head['filename'], 'wb') as f:
    while filesize:
        if filesize >= BUFFER:
            data = coon.recv(BUFFER)
            f.write(data)
            filesize -= BUFFER
        else:
            data = coon.recv(filesize)
            f.write(data)
            break
coon.close()
sk_server.close()
server端(接收)
# 發送端
import socket
import os
import json
import struct


IP_PORT = ('127.0.0.1', 8088)

BUFFER = 4096
sk_client = socket.socket()
sk_client.connect(IP_PORT)

# 發送文件
head = {'filename':'01 python  s9day31 複習和認識tcp的長鏈接.mp4',
        'filesize':None,
        'filepath': r'G:\第3部分-網絡編程(9期(女神講的,很是詳細))\day31'
        }
file_path = os.path.join(head['filepath'],head['filename'])
print(file_path)
filesize = os.path.getsize(file_path)
head['filesize'] = filesize

json_head = json.dumps(head)    # 字典轉成字符串
bytes_head = json_head.encode('utf-8')  # 字符串轉成bytes

# 計算head的長度
head_len = len(bytes_head)   # 報頭的長度
pack_len = struct.pack('i', head_len)   # 將報頭的長度轉成4個字節的長度
sk_client.send(pack_len)    # 先發送報頭的長度
sk_client.send(bytes_head)  # 再發送bytes類型的報頭

with open(file_path, 'rb') as f:
    while filesize:
        if filesize >= BUFFER:
            data = f.read(BUFFER)   # 每次讀出來的大小
            sk_client.send(data)
            filesize-=BUFFER
        else:
            data = f.read(filesize)
            sk_client.send(data)
            break
sk_client.close()
client端(發送)

升級版

import socket
import struct
import json
import hashlib


class Server_socket():
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port
        self.server = socket.socket()
        self.server.bind((self.ip, self.port))
        self.server.listen(5)
        self.conn, self.addr = self.server.accept()

    def header(self):
        '''接受包頭的函數'''
        head_len = self.conn.recv(4)    # 接受struct類型包頭的長度
        head_len = struct.unpack('i', head_len)[0] # 進行解struct包頭,獲得包頭的實際長度
        json_head = self.conn.recv(int(head_len)).decode('utf-8')   # 獲得json類型的包頭
        head = json.loads(json_head)    # 拿到真正的包頭
        fileSize = head['file_size']    # 從包頭取出文件的大小
        fileName = head['file_name']    # 從包頭取出文件的名字
        return fileSize, fileName

    def recvFile(self):
        '''接收文件並計算hash值'''
        BUFFER = 1024
        hash_md5_obj = hashlib.md5()
        fileSize, fileName = self.header()
        with open(fileName, 'wb') as f:
            while fileSize:
                if fileSize >= BUFFER:
                    data = self.conn.recv(BUFFER)
                    f.write(data)
                    hash_md5_obj.update(data)
                    fileSize -= BUFFER
                else:
                    data = self.conn.recv(fileSize)
                    f.write(data)
                    hash_md5_obj.update(data)
                    break
        local_hash_value = hash_md5_obj.hexdigest()
        client_hash_value = self.recvHash()
        if local_hash_value == client_hash_value:
            self.conn.send("Upload success ...".encode('utf-8'))
        else:
            self.conn.send("Upload failure ...".encode('utf-8'))
        self.conn.close()
        self.server.close()

    def recvHash(self):
        '''接收客戶端的hash值'''
        client_hash_len = self.conn.recv(4)
        client_hash_len = struct.unpack('i', client_hash_len)[0]
        client_json_hash = self.conn.recv(int(client_hash_len)).decode('utf-8')
        client_hash = json.loads(client_json_hash)
        return client_hash

server = Server_socket('127.0.0.1', 8088)
server.recvFile()
server端(接收)
import socket
import os
import json
import struct
import hashlib


class Client_socket():
    def __init__(self, ip, port):
        self.ip = ip
        self.port = port
        self.client = socket.socket()
        self.client.connect((self.ip, self.port))

    def header(self):
        '''發送包頭的長度'''
        while True:
            filePath = input("input filepath >>>: ")
            if os.path.exists(filePath):
                fileName = os.path.basename(filePath)
                fileSize = os.path.getsize(filePath)
                header = {
                    "file_path": filePath,
                    "file_name": fileName,
                    "file_size": fileSize
                }
                json_head = json.dumps(header)  # 獲得json類型的header
                bytes_head = json_head.encode('utf-8')  # 獲得bytes類型的header
                head_len = len(bytes_head)  # 計算包頭的長度
                pick_head_len = struct.pack('i', head_len)  # 轉換成4個字節長度
                self.client.send(pick_head_len) # 先發送包頭的長度
                self.client.send(bytes_head)    # 再發送bytes類型報頭
                return fileSize, filePath

    def sendFile(self):
        '''發送文件並計算hash值'''
        BUFFER = 1024
        hash_md5_obj = hashlib.md5()
        fileSize, filePath = self.header()
        with open(filePath, 'rb') as f:
            while fileSize:
                if fileSize >= BUFFER:
                    data = f.read(BUFFER)
                    self.client.send(data)
                    hash_md5_obj.update(data)
                    fileSize -= BUFFER
                else:
                    data = f.read(fileSize)
                    hash_md5_obj.update(data)
                    self.client.send(data)
                    break
        hash_value = hash_md5_obj.hexdigest()   # 拿到hash值
        self.sendHash(hash_value)   # 發送給服務端
        ret = self.client.recv(1024).decode('utf-8')    # 拿到上傳的結果
        print(ret)

    def sendHash(self, hashValue):
        '''發送hash值'''
        bytes_hash = json.dumps(hashValue).encode('utf-8')
        hash_len = len(bytes_hash)
        pick_hash_len = struct.pack('i', hash_len)
        self.client.send(pick_hash_len)
        self.client.send(bytes_hash)

client = Client_socket('127.0.0.1', 8088)
client.sendFile()
client端(發送)

socket的更多方法

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

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

公共用途的套接字函數
s.recv()            接收TCP數據
s.send()            發送TCP數據
s.sendall()         發送TCP數據
s.recvfrom()        接收UDP數據
s.sendto()          發送UDP數據
s.getpeername()     鏈接到當前套接字的遠端的地址
s.getsockname()     當前套接字的地址
s.getsockopt()      返回指定套接字的參數
s.setsockopt()      設置指定套接字的參數
s.close()           關閉套接字

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

面向文件的套接字的函數
s.fileno()          套接字的文件描述符
s.makefile()        建立一個與該套接字相關的文件
更多方法
官方文檔對socket模塊下的socket.send()和socket.sendall()解釋以下:

socket.send(string[, flags])
Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Returns the number of bytes sent. Applications are responsible for checking that all data has been sent; if only some of the data was transmitted, the application needs to attempt delivery of the remaining data.

send()的返回值是發送的字節數量,這個數量值可能小於要發送的string的字節數,也就是說可能沒法發送string中全部的數據。若是有錯誤則會拋出異常。

–

socket.sendall(string[, flags])
Send data to the socket. The socket must be connected to a remote socket. The optional flags argument has the same meaning as for recv() above. Unlike send(), this method continues to send data from string until either all data has been sent or an error occurs. None is returned on success. On error, an exception is raised, and there is no way to determine how much data, if any, was successfully sent.

嘗試發送string的全部數據,成功則返回None,失敗則拋出異常。

故,下面兩段代碼是等價的:

#sock.sendall('Hello world\n')

#buffer = 'Hello world\n'
#while buffer:
#    bytes = sock.send(buffer)
#    buffer = buffer[bytes:]
send和sendall方法的區別

驗證用戶合法性

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

demo1:

import socket
import hmac
import os
secret_key = b'egg'     # 定義一個和客戶端的secret_key
IP_PORT = ('127.0.0.1', 8088)

sk_server = socket.socket()

sk_server.bind(IP_PORT)
sk_server.listen()

def check_conn(conn):
    msg = os.urandom(32)
    conn.send(msg)
    h = hmac.new(secret_key, msg)
    digest = h.digest()
    client_digest = conn.recv(1024) # 拿到客戶端加密後的digest
    return hmac.compare_digest(digest, client_digest)   # 服務端和客戶端對比


conn, addr = sk_server.accept()
ret = check_conn(conn)
if ret:
    print("合法的客戶端")
    conn.close()
else:
    print("不合法的客戶端")
    conn.close()
sk_server.close()
server
import socket
import hmac
secret_key = b'egg'     # 定義一個和服務端的secret_key

IP_PORT = ('127.0.0.1', 8088)
sk_client = socket.socket()
sk_client.connect(IP_PORT)
msg = sk_client.recv(1024)  
h = hmac.new(secret_key, msg)
digest = h.digest()

sk_client.send(digest)

sk_client.close()
client

demo2:

#_*_coding:utf-8_*_
from socket import *
import hmac,os

secret_key=b'bang bang'
def conn_auth(conn):
    '''
    認證客戶端連接
    :param conn:
    :return:
    '''
    print('開始驗證新連接的合法性')
    msg=os.urandom(32)
    conn.sendall(msg)
    h=hmac.new(secret_key,msg)
    digest=h.digest()
    respone=conn.recv(len(digest))
    return hmac.compare_digest(respone,digest)

def data_handler(conn,bufsize=1024):
    if not conn_auth(conn):
        print('該連接不合法,關閉')
        conn.close()
        return
    print('連接合法,開始通訊')
    while True:
        data=conn.recv(bufsize)
        if not data:break
        conn.sendall(data.upper())

def server_handler(ip_port,bufsize,backlog=5):
    '''
    只處理連接
    :param ip_port:
    :return:
    '''
    tcp_socket_server=socket(AF_INET,SOCK_STREAM)
    tcp_socket_server.bind(ip_port)
    tcp_socket_server.listen(backlog)
    while True:
        conn,addr=tcp_socket_server.accept()
        print('新鏈接[%s:%s]' %(addr[0],addr[1]))
        data_handler(conn,bufsize)

if __name__ == '__main__':
    ip_port=('127.0.0.1',9999)
    bufsize=1024
    server_handler(ip_port,bufsize)
服務端
#_*_coding:utf-8_*_
__author__ = 'Linhaifeng'
from socket import *
import hmac,os

secret_key=b'bang bang'
def conn_auth(conn):
    '''
    驗證客戶端到服務器的連接
    :param conn:
    :return:
    '''
    msg=conn.recv(32)
    h=hmac.new(secret_key,msg)
    digest=h.digest()
    conn.sendall(digest)

def client_handler(ip_port,bufsize=1024):
    tcp_socket_client=socket(AF_INET,SOCK_STREAM)
    tcp_socket_client.connect(ip_port)

    conn_auth(tcp_socket_client)

    while True:
        data=input('>>: ').strip()
        if not data:continue
        if data == 'quit':break

        tcp_socket_client.sendall(data.encode('utf-8'))
        respone=tcp_socket_client.recv(bufsize)
        print(respone.decode('utf-8'))
    tcp_socket_client.close()

if __name__ == '__main__':
    ip_port=('127.0.0.1',9999)
    bufsize=1024
    client_handler(ip_port,bufsize)
客戶端(合法)
#_*_coding:utf-8_*_
from socket import *

def client_handler(ip_port,bufsize=1024):
    tcp_socket_client=socket(AF_INET,SOCK_STREAM)
    tcp_socket_client.connect(ip_port)

    while True:
        data=input('>>: ').strip()
        if not data:continue
        if data == 'quit':break

        tcp_socket_client.sendall(data.encode('utf-8'))
        respone=tcp_socket_client.recv(bufsize)
        print(respone.decode('utf-8'))
    tcp_socket_client.close()

if __name__ == '__main__':
    ip_port=('127.0.0.1',9999)
    bufsize=1024
    client_handler(ip_port,bufsize)
客戶端(非法:不知道加密方式)
#_*_coding:utf-8_*_
from socket import *
import hmac,os

secret_key=b'linhaifeng bang bang bang1111'
def conn_auth(conn):
    '''
    驗證客戶端到服務器的連接
    :param conn:
    :return:
    '''
    msg=conn.recv(32)
    h=hmac.new(secret_key,msg)
    digest=h.digest()
    conn.sendall(digest)

def client_handler(ip_port,bufsize=1024):
    tcp_socket_client=socket(AF_INET,SOCK_STREAM)
    tcp_socket_client.connect(ip_port)

    conn_auth(tcp_socket_client)

    while True:
        data=input('>>: ').strip()
        if not data:continue
        if data == 'quit':break

        tcp_socket_client.sendall(data.encode('utf-8'))
        respone=tcp_socket_client.recv(bufsize)
        print(respone.decode('utf-8'))
    tcp_socket_client.close()

if __name__ == '__main__':
    ip_port=('127.0.0.1',9999)
    bufsize=1024
    client_handler(ip_port,bufsize)
客戶端(非法:不知道secret_key)

socketserver

socketserver實現多併發

import socketserver


class MySocketServer(socketserver.BaseRequestHandler):  # 自定義一個socket類,可是必須繼承socketserver.BaseRequestHandler
    def handle(self):   # 全部的鏈接收發都寫在handle裏面,  方法名必須是handle
        msg = self.request.recv(1024).decode('utf-8')
        print(msg)
        self.request.send("收到啦".encode('utf-8'))

if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1', 8088), MySocketServer) # 建立一個server,綁定IP和端口,socketserver.ThreadingTCPServer((IP, PORT), 自定義類的類名)
    server.serve_forever()  # 讓server永遠運行下去,除非強制中止程序
server端
import socket

IP_PORT = ('127.0.0.1', 8088)

sk_client = socket.socket()
sk_client.connect(IP_PORT)

sk_client.send("哈嘍...".encode('utf-8'))
msg = sk_client.recv(1024).decode('utf-8')
print(msg)
sk_client.close()
client端

參考 http://www.cnblogs.com/Security-Darren/p/4594393.html

 

 

 

說明:部份內容摘自景女神的博客

相關文章
相關標籤/搜索