python之Socket編程

1、軟件開發架構前端

C/S架構:客戶端/服務端linux

B/S架構:瀏覽器/服務端shell

手機端:好像是c/s架構比較火,可是b/s架構正在逐步火起來
目的:統一接口,彙集流量用戶

服務端:24小時不間斷提供服務
客戶端:何時想體驗服務,就去找服務端體驗服務

學習網絡編程
你就可以開發一個c/s架構的軟件
學習併發編程 數據庫  前端
你就可以開發一個b/s架構的軟件

2、OSI七層模型數據庫

OSI七層協議:
    應用層
    表示層
    會話層
    傳輸層
    網絡層
    數據鏈路層
    物理鏈接層

也有人將上面的七層歸納爲五層
    1.應用層
    2.傳輸層
    3.網絡層
    4.數據鏈路層
    5.物理鏈接層
1.物理鏈接層
    數據都是基於電信號傳輸(010101這樣的二進制)
    電信號特色:只有高低電平

2.數據鏈路層
    010:我
    0101:你
    01010101010101
    1.規定了電信號的分組方式
    2.規定了任何一臺接入互聯網的計算機都必須有一塊網卡
        網卡:每臺計算機在出廠的時候網卡上面有刻有一段惟一標識碼(不能重複)
        16進制12位數字(前6位:廠商編號 後6位是流水線號)
        這個獨一無二的標識碼咱們稱之爲"mac地址"
    ————》》》上述兩個規定統稱爲"以太網協議"
以太網不可以跨局域網傳輸

交換機:全部的電腦都連交換機就能夠實現多臺電腦之間互聯,你的電腦就不會變成馬蜂窩了

基於以太網協議通訊:通訊基本靠吼
弊端:廣播風暴


3.網絡層(IP協議)
    規定了任何一臺互聯網的計算機都必須有一個ip地址:點分十進制(ipv4版本)
    ip地址目前有兩個版本
        ipv4
            最小:0.0.0.0
            最大:255.255.255.255
        ipv6(冒號16進制)
            最小:0.0.0.0.0.0
            最大:。。。。
    ip地址:可以找到全部聯入互聯網任何一臺計算機

4.傳輸層(端口協議)
    TCP,UDP
    端口(port): 用來標識一臺計算機上的某個應用程序
    端口範圍(0~65535)
    動態分配
    0~1024操做系統佔用
    端口號推薦使用8000以後的
    Django默認端口號8000
    flask默認端口號5000
    MySQL默認端口號3306
    Redis默認端口號6379

    端口:用來標識某一臺計算機上的某一個應用程序
    注意:同一臺計算機上同一時間同一端口號不能重複


小總結:計算機於計算機之間通訊其實計算機上的應用程序之間的通訊
ip地址
port號
ip+port:可以找到任何接入互聯網一臺計算機上面的某個應用程序

5.應用層(HTTP協議,FTP協議...)

TCP協議(流式協議,可靠協議)
    三次握手四次揮手
    
TCP流式協議
    它會將數據量比較小的而且時間間隔比較短的數據
    一次性發送給對方
View Code

3、套接字函數編程

# 服務端套接字函數
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()        建立一個與該套接字相關的文件

4、socket初識json

# 服務端
# 舉例
import socket

server = socket.socket()  # 買手機   不傳參數默認就是TCP協議
server.bind(('127.0.0.1', 8080))  # 插手機卡,傳輸的是一個元組(IP,端口)
server.listen(5)  # 半鏈接池:同時容許5個會話連接,超過連接不上,客戶端會報錯,默認爲None

conn, addr = server.accept()  # 待機  阻塞

data = conn.recv(1024)  # 聽別人說話   接收1024個bytes
print(data)
conn.send(b'hello big baby~')  # 回話

conn.close()  # 掛電話
server.close()  # 關機

# 客戶端
import socket

client = socket.socket()  # 買手機
client.connect(('127.0.0.1', 8080))  # 撥號

client.send(b'hello big baby~')
data = client.recv(1024)
print(data)

client.close()
# 上面的代碼能夠實現一次簡單的信息傳遞
"""
服務端
1.24小時不間斷提供服務端
2.有固定的ip和port
3.支持高併發

併發:只要看起來像同時進行就能夠稱之爲併發
並行:真正意義上的同時執行
"""

socket 通信循環flask

# 服務端
server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

conn, addr = server.accept()
while True:
    try:
        data = conn.recv(1024)  # b'' 若是發送這個字符表明,通話結束
        if len(data) == 0:break  # 針對mac linux系統而言
        print(data)
        conn.send(data.upper())
    except ConnectionResetError:
        break

conn.close()
server.close()
# 端口占用:OSError: [WinError 10048] 一般每一個套接字地址(協議/網絡地址/端口)只容許使用一次。

# 客戶端 # 能夠循環接收發送消息 import socket client = socket.socket() client.connect(('127.0.0.1',8080)) while True: msg = input('please input your msg>>>:').encode('utf-8') if len(msg) == 0:continue client.send(msg) data = client.recv(1024) print(data)

socket通信循環2windows

# 服務端
import socket

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)  # 若是超過5我的鏈接過來就會報錯:ConnectionResetError,因爲目標計算機積極拒絕,沒法鏈接


while True:
    conn, addr = server.accept()
    while True:
        try:
            data = conn.recv(1024)  # b''
            if len(data) == 0:break  # 針對mac linux系統而言
            print(data)
            conn.send(data.upper())
        except ConnectionResetError:
            break
    conn.close()

server.close()

# 客戶端
import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    msg = input('please input your msg>>>:').encode('utf-8')
    if len(msg) == 0:continue
    client.send(msg)
    data = client.recv(1024)
    print(data)

 subprocess模塊回憶瀏覽器

import subprocess

cmd = subprocess.Popen('dir',shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
stdout = cmd.stdout.read().decode('gbk')
stderr = cmd.stderr.read().decode('gbk')
print(stdout+stderr)

subprocess模塊模擬遠程命令緩存

# 實現服務端接收客戶端發出的命令並執行返回結果
# 服務端
import socket
import subprocess

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn,addr = server.accept()
    while True:
        try:
            cmd = conn.recv(1024).decode('utf-8')
            if len(cmd) == 0:break
            obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            print(len(stdout+stderr))  # 查看返回的長度
            conn.send(stdout+stderr) # 將數據發回客戶端
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()

server.close()

# 客戶端
import socket

client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    cmd = input('please input your cmd>>:').encode('utf-8')
    if len(cmd) == 0:continue
    client.send(cmd)
    data = client.recv(1024)
    print(data.decode('gbk'))


# 上面測試執行dir能夠返回服務端目錄下的目錄結構
# 可是執行tasklist返回的數據沒有返回完整!!!!!
# ----緣由是接收發送數據recv接收的長度是1024,可是若是我不知道接收的長度那怎麼辦呢?

粘包現象:

  ------ 從上面tasklist中引出若是接收的字節超過規定的字節怎麼處理?

TCP流式協議
    它會將數據量比較小的而且時間間隔比較短的數據一次性發送給對方
  ----服務端接收的時候會將數據當成一行數據接收
-- 那如何解決這個問題呢?
必須有一個數據先傳到服務端告訴服務端接下來傳輸的數據大小

struct模塊

# 打包一個數據,並打包成一個固定長度
import struct

res = 'ssssssssssssssssssssssssssssssssss'

print(len(res))  # 字符串長度34
head = struct.pack('i',len(res))  # pack打包
print(len(head))  # 屢次改變res長度,head長度仍是爲4

real_len = struct.unpack('i',head)[0]
print('real_len解壓後長度',real_len) # 長度34
# 打包字典發送
import json
d = {
    'file_name':'xxx',
    'file_size':123213123123234543545324546536546563465435435423543256546546635463465563565653,
    'md5':'dkfkdsafksdklafj;asfdsaf'
}
data_bytes = json.dumps(d).encode('utf-8')
print(len(data_bytes))
head = struct.pack('i',len(data_bytes))

head_bytes = struct.unpack('i',head)[0]
print(head_bytes)
View Code

socket解決粘包問題初級版本

# 服務端
import socket
import subprocess
import struct

server = socket.socket()
server.bind(('127.0.0.1',8080))
server.listen(5)

while True:
    conn, addr = server.accept()
    while True:
        try:
            cmd = conn.recv(1024).decode('utf-8')
            if len(cmd) == 0:break
            obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)
            stdout = obj.stdout.read()
            stderr = obj.stderr.read()
            print(len(stdout+stderr))
            # 先製做報頭
            header = struct.pack('i',len(stdout+stderr))
            # 發送報頭
            conn.send(header)
            # 再發真實數據
            conn.send(stdout+stderr)
        except ConnectionResetError as e:
            print(e)
            break
    conn.close()
server.close()

# 客戶端
import socket
import struct

client = socket.socket()
client.connect(('127.0.0.1',8080))

while True:
    cmd = input('please input your cmd>>>:').encode('utf-8')
    if len(cmd) == 0:continue
    client.send(cmd)
    # 先接收報頭
    header = client.recv(4)
    # 解析真實數據長度
    total_size = struct.unpack('i',header)[0]
    # 循環接收真實數據,判斷數據長度
    data = b''
    recv_size = 0
    while recv_size < total_size:
        info = client.recv(1024)
        data += info
        recv_size += len(info)
    print(data.decode('gbk'))
View Code

socket解決粘包問題終極版本

# 解決上面返回字符長度或不知字符長度問題
#
服務端 import socket import subprocess import struct import json server = socket.socket() server.bind(('127.0.0.1',8080)) server.listen(5) while True: conn, addr = server.accept() while True: try: cmd = conn.recv(1024).decode('utf-8') if len(cmd) == 0:break obj = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE) stdout = obj.stdout.read() stderr = obj.stderr.read() print(len(stdout+stderr)) send_dic = {"name":'simon','password':'123','total_size':len(stdout+stderr)} send_bytes = json.dumps(send_dic).encode('utf-8') # 先製做字典的報頭 header = struct.pack('i',len(send_bytes)) # 發送報頭 conn.send(header) # 再發字典 conn.send(send_bytes) # 最後再發真實數據 conn.send(stdout+stderr) except ConnectionResetError as e: print(e) break conn.close() server.close() # 客戶端 import socket import struct import json client = socket.socket() client.connect(('127.0.0.1',8080)) while True: cmd = input('please input your cmd>>>:').encode('utf-8') if len(cmd) == 0:continue client.send(cmd) # 先接收報頭 header = client.recv(4) # 解析獲取字典的長度 dic_len = struct.unpack('i',header)[0] dic_bytes = client.recv(dic_len) real_dic = json.loads(dic_bytes.decode('utf-8')) print(real_dic) # 循環接收真實數據 data = b'' recv_size = 0 while recv_size < real_dic['total_size']: info = client.recv(1024) data += info recv_size += len(info) print(data.decode('gbk'))

socket實現大文件上傳:

# 服務端
import socket
import struct
import json
# 解決報錯信息
from socket import SOL_SOCKET,SO_REUSEADDR

server = socket.socket()
# 解決報錯信息
server.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)
server.bind(('127.0.0.1',8090))
server.listen(5)

conn,addr = server.accept()
# 先收4個長度報頭
header = conn.recv(4)
# 獲取字典長度
dic_len = struct.unpack('i',header)[0]
# 接收字典
dic_bytes = conn.recv(dic_len)
real_dic = json.loads(dic_bytes.decode('utf-8'))
# 獲取文件相關信息
file_name = real_dic.get('file_name')
total_size = real_dic.get("file_size")
with open(file_name,'wb') as f:
    # 循環接收文件
    recv_size = 0
    while recv_size < total_size:
        data = conn.recv(1024)
        f.write(data)  # 收取就存入文件
        recv_size += len(data)
    print('文件上傳成功')

# 客戶端
import socket
import struct
import json
import os

client = socket.socket()
client.connect(('127.0.0.1',8090))

file_path = r'D:\週末\day\day\01 今日內容.mp4'
file_size = os.path.getsize(file_path)  # 獲取文件大小
send_dic = {"file_name":'FBI warning.mp4','file_info':"注意身體",'file_size':file_size}
send_bytes = json.dumps(send_dic).encode('utf-8')
# 製做報頭
head = struct.pack('i',len(send_bytes))
# 發送報頭
client.send(head)
# 發字典
client.send(send_bytes)
# 發文件
with open(file_path,'rb') as f:
    for line in f:
        client.send(line)

5、UDP協議:數據報協議

UDP特色:
1.udp協議客戶端容許發空
2.udp協議不會粘包
3.udp協議服務端不存在的狀況下,客戶端照樣不會報錯
# 服務端
import socket

server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1', 8080))

msg, addr = server.recvfrom(1024)
print(msg.decode('utf-8'))
server.sendto(b'hello', addr)

server.close()

#客戶端
import socket

client = socket.socket(type=socket.SOCK_DGRAM)
# 配置服務端地址端口
server_addr = ('127.0.0.1', 8080)

# 發的時候必須帶上IP地址,服務端才能知道你是誰;客戶端容許發空,服務端也接收爲空
client.sendto(b'hello server baby!', server_addr)
msg, addr = client.recvfrom(1024)
print(msg, addr)
"""
# udp特色    >>>    無連接,相似於發短信,發了就行對方愛回不回,沒有任何關係
# 將服務端關了,客戶端起起來照樣可以發數據。由於不須要考慮服務端能不能收到
"""
# 驗證udp協議有無粘包問題
import socket
server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))
print(server.recvfrom(1024))
print(server.recvfrom(1024))
print(server.recvfrom(1024))

import socket
client = socket.socket(type=socket.SOCK_DGRAM)
server_addr = ('127.0.0.1',8080)
client.sendto(b'hello',server_addr)
client.sendto(b'hello',server_addr)
client.sendto(b'hello',server_addr)

練習:基於UDP實現簡易版本的對話

# 服務端
import socket

server = socket.socket(type=socket.SOCK_DGRAM)
server.bind(('127.0.0.1',8080))

while True:
    data, addr = server.recvfrom(1024)
    print(data.decode('utf-8'))
    server.sendto(data.upper(),addr)

# 多個客戶端
import socket

client = socket.socket(type=socket.SOCK_DGRAM)
server_addr = ('127.0.0.1', 8080)

while True:
    info = input('>>>:')
    info = ('來自客戶端1的消息:%s'%info).encode('utf-8')  # 改中文備註便可
    client.sendto(info, server_addr)
    msg, addr = client.recvfrom(1024)
    print(msg.decode('utf-8'), addr)

client.close()

"""
服務端接收:
客戶端1:sdafasewrtwe
客戶端2:erwtwetwe
客戶端3:retwtrwetw
客戶端4:ewrbxcbvxb
"""

補充:windows電腦和mac電腦的時間同步功能,其實就是基於udp向windows,mac服務器發送請求獲取標準時間

總結:TCP協議就相似於打電話;UDP協議就相似於發短信

SocketServer模塊介紹(讓tcp也能支持併發)

# TCP socketserver使用
import socketserver
class MyTcpServer(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            try:
                data = self.request.recv(1024)  # 對於tcp,self.request至關於conn對象
                if len(data) == 0:break
                print(data)
                self.request.send(data.upper())
            except ConnectionResetError:
                break
if __name__ == '__main__':
    server = socketserver.ThreadingTCPServer(('127.0.0.1',8081),MyTcpServer)  # 請求來了交給MyTcpServer處理
    server.serve_forever()
    
# UDP socketserver使用
import socketserver

class MyUdpServer(socketserver.BaseRequestHandler):
    def handle(self):
        while True:
            data, sock = self.request
            print(data)
            sock.sendto(data.upper(), self.client_address)


if __name__ == '__main__':
    server = socketserver.ThreadingUDPServer(('127.0.0.1', 8080), MyUdpServer)
    server.serve_forever()

 做業:

做業:開發一個支持多用戶在線的FTP程序
要求:
1.用戶加密認證
2.容許同時多用戶登陸
3.每一個用戶有本身的家目錄,且只能訪問本身的家目錄
4.對用戶進行磁盤配額,每一個用戶的可用空間不一樣
5.容許用戶在ftp server 上隨意切換目錄
6.容許用戶查看當前目錄下的文件
7.容許上傳和下載文件,保證文件一致性
8.文件傳輸過程當中顯示進度條
9.附加功能:支持斷點續傳
相關文章
相關標籤/搜索