天天看點

Python網絡程式設計(socketserver、TFTP雲盤、HTTPServer伺服器模型)

HTTP?

HTTP是一個應用層協定,由請求和響應構成,是一個标準的用戶端伺服器模型。HTTP是一個無狀态的協定。

通常承載于TCP協定之上,有時也承載于TLS或SSL協定層之上,這個時候,就成了我們常說的HTTPS

預設HTTP的端口号為80,HTTPS的端口号為443。

    what?  無狀态什麼鬼?

       HTTP無狀态協定是指協定對于事務處理沒有記憶能力。缺少狀态意味着如果後續處理需要前面的資訊,

        則它必須重傳,這樣可能導緻每次連接配接傳送的資料量增大。另一方面,在伺服器不需要先前資訊時它的應答就較快

        由于web等用戶端與伺服器互動的應用程式出現後HTTP的無狀态嚴重阻礙了這些應用的實作效率 說以就産生了cookie和Session

cookie:

當使用者使用浏覽器通路一個支援Cookie的網站的時候,使用者會提供包括使用者名在内的個人資訊并且送出至伺服器;

接着,伺服器在向用戶端回傳相應的超文本的同時也會發回這些個人資訊,當然這些資訊并不是存放在HTTP響應體(Response Body)中的,

而是存放于HTTP響應頭(Response Header);當用戶端浏覽器接收到來自伺服器的響應之後,

浏覽器會将這些資訊存放在一個統一的位置,對于Windows作業系統而言,

我們可以從: [系統盤]:\Documents and Settings\[使用者名]\Cookies目錄中找到存儲的Cookie;自此,用戶端再向伺服器發送請求的時候,

都會把相應的Cookie再次發回至伺服器。而這次,Cookie資訊則存放在HTTP請求頭(Request Header)了。

Session :

所謂session就是指用戶端與服務端之間的一種互動過程的狀态資訊(資料) 這個狀态的定界以及生命期是應用本身的事情

當一個使用者向伺服器發送第一個請求時,伺服器為其建立一個session 并且會給這個session建立一個辨別号

這個使用者随後的請求都應該包括這個辨別好伺服器會對這個辨別判斷請求屬于哪一個session

這種機制不使用IP作為辨別,因為很多機器是代理伺服器上網,無法區分主機 可以用cookie和URL重寫來實作session辨別号(sessionID)

URL隻是一個統稱 實際上是URI包含URL和URN由于URN用的非常少 幾乎說有的URI都是URL是以人們更喜歡叫URL

os.listdir(path)           擷取檔案 清單 os.path.isfile() :             判斷一個 檔案是否為 普通檔案 os.path.isdir() :            判斷一個檔案是否為 目錄 

TFTP 檔案伺服器

項目功能 : 

    * 用戶端有簡單的頁面指令提示

    * 功能包含:

               1. 檢視伺服器檔案庫中的檔案清單(普通檔案)

               2. 可以下載下傳其中的某個檔案到本地

               3. 可以上傳用戶端檔案到伺服器檔案庫

     * 伺服器需求 :

               1. 允許多個用戶端同時操作

               2.每個用戶端可能回連續發送指令

技術分析:

       1. tcp套接字更适合檔案傳輸

       2. 并發方案  ---》 fork 多程序并發

       3. 對檔案的讀寫操作

       4. 擷取檔案清單 ----》 os.listdir() 

       粘包的處理

整體結構設計

       1. 伺服器功能封裝在類中(上傳,下載下傳,檢視清單)

       2. 建立套接字,流程函數調用  main()

       3. 用戶端負責發起請求,接受回複,展示

       服務端負責接受請求,邏輯處理

程式設計實作

       1. 搭建整體結構,建立網絡連接配接

       2. 建立多程序和類的結構

       3. 每個功能子產品的實作

伺服器端:

from socket import *
import os 
import signal 
import sys 
import time 

# 檔案庫
FILE_PATH = "/home/tarena/"

# 實作功能子產品
class TftpServer(object):
    def __init__(self,connfd):
        self.connfd = connfd 

        # 查詢
    def do_list(self):
        # 擷取清單
        file_list = os.listdir(FILE_PATH)
        if not file_list:
            self.connfd.send("檔案庫為空".encode())
            # 伺服器目錄無檔案
            return
        else:
            # 有檔案
            self.connfd.send(b'OK')
            time.sleep(0.1)

        files = ""
        for file in file_list:
            # 發送所有普通檔案的檔案名并且不是隐藏檔案
            if os.path.isfile(FILE_PATH+file) and file[0] != '.':
                # 檔案名間隔符 用于用戶端解析
                files = files + file + '#'
            # 一次全部發送 簡單粗暴
        self.connfd.send(files.encode())

        # 下載下傳
    def do_get(self,filename):
        # 判斷檔案是否純在
        try:
            fd = open(FILE_PATH + filename,'rb')
        except:
            self.connfd.send("檔案不存在".encode())
            return
        self.connfd.send(b'OK')
        time.sleep(0.1)
        # 發送檔案
        try:
            while True:
                data = fd.read(1024)
                if not data:
                    break
                self.connfd.send(data)
        except Exception as e:
            print(e)
        time.sleep(0.1)
        self.connfd.send(b'##')  # 表示檔案發送完成
        print("檔案發送完畢")

        # 上傳
    def do_put(self,filename):
        # 限制檔案命重複導緻覆寫源檔案
        try:
            fd = open(FILE_PATH+filename,'xb')
        except:
            self.connfd.send("無法上傳".encode())
            return 
        except FileExistsError:
            self.connfd.send("檔案已存在".encode())
            return 
        self.connfd.send(b'OK')
        # 上傳檔案
        while True:
            data = self.connfd.recv(1024)
            if data == b'##':
                break
            fd.write(data)
        fd.close()
        print("檔案上傳完畢")

# 流程控制,建立套接字,建立并發,方法調用
def main():
    HOST = '0.0.0.0'
    PORT = 8888
    ADDR = (HOST,PORT)
    # 建立套接字
    sockfd = socket()
    sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
    sockfd.bind(ADDR)
    sockfd.listen(5)
    # 忽略子程序退出
    signal.signal(signal.SIGCHLD, signal.SIG_IGN)
    # 循環等待用戶端連結
    while True:
        try: 
            connfd, addr = sockfd.accept()
        except KeyboardInterrupt:
            sockfd.close()
            sys.exit("伺服器退出")
        except Exception as e:
            print(e)
            continue
        print("用戶端登入:",addr)

        # 建立父子程序
        pid = os.fork()
        # 進入子程序
        if pid == 0:
            # 關閉子程序内無用套接字
            sockfd.close()
            tftp = TftpServer(connfd)  # __init__傳參
            while True:
                data = connfd.recv(1024).decode()
                    # 斷開連接配接
                if (not data) or data[0] == 'Q':
                    print("用戶端退出")
                    sys.exit(0)
                elif data[0] == "L":
                    # 申請查詢
                    tftp.do_list()
                elif data[0] == 'G':
                    # 解析檔案名
                    filename = data.split(' ')[-1]
                    # 申請下載下傳
                    tftp.do_get(filename)
                elif data[0] == 'P':
                    filename = data.split(' ')[-1]
                    # 申請上傳
                    tftp.do_put(filename)       
                else:
                    print("用戶端發送錯誤指令")
        else:
            # 關閉父程序内無用套接字 
            connfd.close()
            # 父程序隻用來做用戶端連結
            continue


if __name__ == "__main__":
    main()
           

用戶端:

from socket import *
import sys 
import time 

# 實作各種功能請求
class TftpClient(object):
    def __init__(self,sockfd):
        self.sockfd = sockfd 

    def do_list(self):
        self.sockfd.send(b'L')  # 發送請求類型
        # 接收伺服器回應
        data = self.sockfd.recv(1024).decode()
        if data == "OK":
            data = self.sockfd.recv(4096).decode()
            files = data.split('#')
            for file in files:
                print(file)
            print("檔案展示完畢")
        else:
            # 請求失敗原因
            print(data)

        # 下載下傳指定檔案
    def do_get(self,filename):
        self.sockfd.send(('G ' + filename).encode())
        data = self.sockfd.recv(1024).decode()
        # 請求成功
        if data == 'OK':
            fd = open(filename,'wb')
            while True:
                data = self.sockfd.recv(1024)
                # 結束符
                if data == b'##':
                    break
                fd.write(data)
            fd.close()
            print("%s 下載下傳完成\n"%filename)
        else:
        # 請求失敗原因
            print(data)

    def do_put(self,filename):
        # 判斷本地是否有要上傳的檔案
        try:
            fd = open(filename,'rb')
        except:
            print("上傳檔案不存在")
            return 
        self.sockfd.send(("P "+filename).encode())
        data = self.sockfd.recv(1024).decode()
        # 請求成功
        if data == 'OK':
            while True:
                data = fd.read(1024)
                if not data:
                    break
                self.sockfd.send(data)
            fd.close()
            # 發送結束符并防止粘包
            time.sleep(0.1)
            self.sockfd.send(b'##')
            print("%s 上傳完畢"%filename)
        else:
            # 請求失敗原因
            print(data)


# 建立套接字并建立連接配接
def main():
    # 終端輸入位址
    if len(sys.argv) < 3:
        print("argv is error")
        return
    HOST = sys.argv[1]
    PORT = int(sys.argv[2])
    ADDR = (HOST,PORT)

    sockfd = socket()
    sockfd.connect(ADDR)
    # 建立對象
    tftp = TftpClient(sockfd)   

    while True:
        print("")
        print("==========指令選項===========")
        print("**********  list  *********")
        print("********** get file  ******")
        print("********** put file  ******")
        print("**********  quit  *********")
        print("=============================")

        cmd = input("輸入指令>>")
        # 去除空格判斷指令
        if cmd.strip() == "list":
            # 查詢
            tftp.do_list()
        # 擷取檔案上傳或下載下傳指令
        elif cmd[:3] == "get":
            # 拆分指令擷取檔案名
            filename = cmd.split(' ')[-1]
            # 下載下傳
            tftp.do_get(filename)
        elif cmd[:3] == "put":
            filename = cmd.split(' ')[-1]
            # 上傳
            tftp.do_put(filename)
            # 退出
        elif cmd.strip() == "quit":
            sockfd.send(b'Q')
            sockfd.close()
            sys.exit("歡迎使用")
        else:
            print("請輸入正确指令!")


if __name__ == "__main__":
    main()           
多線程并發 threading

子產品完成多線程并發

對比多程序并發

     優勢 :

            資源消耗少

     缺點 : 

            需要注意對共享資源的操作

實作步驟:

       1. 建立套接字,綁定,監聽

       2. 接收用戶端連接配接請求,建立新的線程

       3. 主線程繼續等待其他用戶端連接配接,分支線程執行用戶端具體請求

       4. 處理完用戶端請求後分支線程自然退出,關閉用戶端套接字

示例:

from socket import * 
import os,sys 
from threading import * 

HOST = "0.0.0.0"
PORT = 8888
ADDR = (HOST,PORT)

#用戶端處理函數
def handler(connfd):
    print("Connect from",connfd.getpeername())
    while True:
        data = connfd.recv(1024).decode()
        if not data:
            break
        print(data)
        connfd.send(b'Receive your msg')
    connfd.close()


def main(ADDR):
    s = socket()
    s.bind(ADDR)
    s.listen(5)

    while True:
        try:
            connfd,addr = s.accept()
            # 處理 Ctrl + C 
        except KeyboardInterrupt:
            s.close()
            sys.exit("伺服器退出")
            # 其他異常
        except Exception as e:
            print(e)
            continue 
            # 建立子線程用于處理用戶端請求
        t = Thread(target=handler,args= (connfd,))
        t.setDaemon(True)
        t.start()

if __name__ == __main__:
    main()
           
socket并發內建子產品

python2  SocketServer

python3 

socketserver     功能 : 

                 通過子產品提供的接口組合可以完成多程序/多線程  tcp/udp的         并發程式

StreamRequestHandler 

     處理tcp請求

DatagramRequestHandler

  處理udp請求

ForkingMixIn 

     建立多程序

ThreadingMixIn

  建立多線程

TCPServer 

 建立tcp  server

UDPServer 

建立udp  server

ForkingTCPServer

       等于    ForkingMixIn  +  TCPServer 

ForkingUDPServer 

    等于    ForkingMixIn  +  UDPServer 

ThreadingTCPServer 

 等于   ThreadingMixIn  +  TCPServer 

ThreadingUDPServer

  等于   ThreadingMixIn  +  UDPServer 

#多程序 tcp server
from socketserver import * 

#建立server類
# class Server(ForkingMixIn,TCPServer):
# class Server(ForkingTCPServer):
#     pass 

#多線程tcp并發
class Server(ThreadingTCPServer):
    pass


#具體的請求處理類
class Handler(StreamRequestHandler):
    def handle(self):
        # self.request ==> accept傳回的套接字
        print("Connect from",self.request.getpeername())
        while True:
            data = self.request.recv(1024).decode()
            if not data:
                break
            print(data)
            self.request.send(b'Receive')


if __name__ == __main__:
    #建立server對象
    server = Server(("0.0.0.0",8888),Handler)

    #啟動伺服器
    server.serve_forever()
           

基于多線程并發的HTTPServer

       1. 接收浏覽器http請求

       2. 對請求進行一定的解析

       3. 根據解析結果傳回對應内容

       4. 如果沒有請求内容則傳回404

       5. 組織Response格式進行回發

更新:

       * 使用多線程并發

       * 增加了具體的請求解析和404情況

       * 使用類進行代碼封裝

       * 增加一定的資料擷取功能

技術點 : threading并發

                tcp socket 傳輸

        HTTP請求和響應格式

相比上次更新了一點點

from socket import * 
from threading import Thread 
import time 

# 存放靜态頁面的目錄
STATIC_DIR = "./static"
ADDR = ('0.0.0.0', 8000)

# HTTPServer類,封裝具體功能
class HTTPServer(object):
    def __init__(self, address):
        # 建立套接字
        self.sockfd = socket()
        # 設定端口重用
        self.sockfd.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
        self.sockfd.bind(address)
        self.sockfd.listen(5)
        # 為對象增加屬性變量
        self.name = "HTTPServer"
        self.port = address[1]
        self.address = address 

    # 啟動伺服器
    def serve_forever(self):
        print("Listen the port %d"%self.port)
        while True:
            # 循環接收用戶端請求并建立新的套接字
            connfd, addr = self.sockfd.accept()
            # 建立線程并運作處理具體請求
            clientThread = Thread(target = self.handleRequest,args = (connfd,))
            # 主線程結束時結束線程
            clientThread.setDaemon(True)
            clientThread.start()

    def handleRequest(self, connfd):
        # 接收用戶端請求
        request = connfd.recv(4096)
        # 按行切割 字元串
        requestHeadlers = request.splitlines()
        # 擷取請求行
        print(connfd.getpeername(), ":" , requestHeadlers[0]) 
        # 擷取請求内容并解析
        getRequest = str(requestHeadlers[0]).split(' ')[1]
        # 并判斷請求類型
        if getRequest == '/' or getRequest[-5:] == '.html':
            # 請求行為網頁請求
            data = self.get_html(getRequest)
        else:
            # 請求指定資料内容
            data = self.get_data(getRequest)
            # 響應請求并返還内容
        connfd.send(data.encode())
        connfd.close()

        # 用于處理網頁請求
    def get_html(self,page):
        # 判斷是否為首頁請求
        if page == "/":
            filename = STATIC_DIR + "/index.html"
        else:
            filename = STATIC_DIR + page

        try:
            f = open(filename)
        except Exception:
            # 沒有找到頁面
            responseHeadlers = "HTTP/1.1 404 Not Found\r\n"
            responseHeadlers += "Content-Type: text/html\r\n"
            responseHeadlers += '\r\n'
            responseBody = "<h1>Sorry,not found the page</h1>"
        else:
            responseHeadlers = "HTTP/1.1 200  OK\r\n"
            responseHeadlers += "Content-Type: text/html\r\n"
            responseHeadlers += '\r\n'
            for i in f:
                responseBody += i
        # 頁面存不存在否響應
        finally:
            return responseHeadlers + responseBody

        # 用于處理資料内容請求
    def get_data(self,data):
        responseHeadlers = "HTTP/1.1 200 OK\r\n"
        responseHeadlers += "\r\n"

        if data == "/time":
            responseBody = time.ctime()
        elif data == "/ParisGabriel":
            responseBody = "Welcome to ParisGabriel"
        else:
            responseBody = "The data not found"
        return responseHeadlers + responseBody


if __name__ == "__main__":
    # 生成伺服器對象
    httpd = HTTPServer(ADDR)
    # 啟動伺服器
    httpd.serve_forever()