1. 程式人生 > 實用技巧 >02_套接字程式設計(socket抽象層)

02_套接字程式設計(socket抽象層)

1.套接字概述

1.套接概述: 套接是進行網路通訊的一種手段(socket)

2.套接字分類:
流式套接字(SOCK_STREAM): 傳輸層基於tcp協議進行通訊
資料報套接字(SOCK_DEGAM): 傳輸層基於udp協議進行通訊
原始套接字(SOCK_RAW): 訪問底層協議的套接字

3.TCP與UDP通訊模型流程圖: https://www.processon.com/view/link/5ef43bfd1e0853263742690b

4.套接字屬性和方法

import socket

s = socket.socket()  #
預設會建立流式套接字 # 功能: 獲取套接字的描述符 # 描述符: 每一個IO作業系統都會分配一個不同的整數與之對應,該整數極為此IO操作的描述符 s.fileno() # 12 print(s.fileon()) # 12 # 獲取套接字型別 s.type # <SocketKind.SOCK_STREAM: 1> print(s.type) # SocketKind.SOCK_STREAM # 獲取套接字繫結的地址 s.getsockname() # ('0.0.0.0', 0) s.bind(("127.0.0.1", 7890)) print(s.getsockname()) #
('127.0.0.1', 7890) # 使用accept生成的套接字呼叫,獲取該套接字對應的客戶端的地址,在一個伺服器有多個客戶端連線時常會用到這個方法 s.listen(128) conn, addr = s.accept() # 阻塞時需要找一個使用者連線 conn.getpeername() # ('127.0.0.1', 53519) print(conn.getpeername()) # ('127.0.0.1', 53519) # s.setsockopt(level, optname, value) 設定套接字選項 # 引數 level: 定義的選項型別,常用選項(IPPROTO_TCP,IPPROTO_IP,SOL_SOCKET)
# 引數 optname: 根據level選項確定的子選項 # 引數 value: 根據子選項設定的值 s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 設定埠重用 s.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # 設定套接字允許接收廣播 # s.getsockopt(level, optname) 獲取套接字選項 # 引數 level: 定義的選項型別,常用選項(IPPROTO_TCP,IPPROTO_IP,SOL_SOCKET) # 引數 optname: 根據level選項確定的子選項 # 返回值: 返回根據子選項設定的值 s.getsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR) # 1 # 面向鎖的套接字方法 s.setblocking() # 設定套接字阻塞與非阻塞模式,引數預設為True表示阻塞 s.settimeout() # 設定阻塞套接字操作的超時時間 s.gettimeout() # 獲取阻塞套接字操作的超時時間 # 面向檔案的套接字函式 s.fileno() # 套接字的檔案描述符 s.makefile() # 建立一個與該套接字相關的檔案

2.TCP流式套接字

1.TCP服務端流程

1.建立套接字
sockfd = socket(socket_family=AF_INET, socket_type=SOCK_STERAM, proto=0) # 建立套接字並返回套接字物件
引數:
socket_family: 選擇地址族種類AF_INET(UNIX)
socket_type: 套接字型別 流式(SOCK_STREAM),資料報(SOCK_DGRAM)
proto: 子協議型別,tcp和udp都沒有用到自協議因此預設為0

2.繫結IP和埠號
sockfd.bind(("", 7890)) # 繫結IP和埠號
引數: 型別為元組('127.0.0.1', 7890) # 第一項是字串形式的IP,第二項是埠號

3.讓套接字具有監聽功能
sockfd.listen(n) # 使套接字變為監聽套接字,同時建立監聽佇列
引數: n監聽佇列大小,一般設定為128

4.等待客戶端連線
new_socket, client_addr = socket.accept() # 阻塞等待客戶端連線
返回值: 型別為元祖(new_socket, client_addr)
第一項: 返回一個新的套接字用來和客戶端通訊
第二項: 返回連線的客戶端的地址

5.訊息的收發
接收: new_socket.recv(buffer) # 接收訊息
引數: 一次接收訊息的大小, 即一次收多少位元組
返回值: 接收到的內容, 型別為位元組
傳送: new_socket.send(data) # 傳送訊息,當沒有接收端的時候send操作會導致管道破裂(broken pipe)
引數: 傳送的內容(bytes), 型別為位元組
返回值: 傳送了多少個位元組

6.關閉套接字
new_socket.close() # 關閉為客戶端服務的套接字
sockfd.close() # 關閉監聽套接字

2.TCP客戶端流程

1.建立流式套接字: sockfd = socket(socket_family=AF_INET, socket_type=SOCK_STREAM, proto=0)

2.發起連線請求
sockfd.connect(('127.0.0.1', 7890)) # 發起連線請求
引數(元組): 第一項是伺服器的IP,第二項是伺服器的PORT

3.收發訊息: sockfd.recv() sockfd.send()

4.關閉套接字: sockfd.close()

3.TCP服務端-通訊示例

import socket


def main():
    # 建立資料流套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 設定埠重用
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 繫結埠
    tcp_server_socket.bind(("", 7890))
    # 讓預設的套接字由主動變為被動監聽 listen
    tcp_server_socket.listen(128)

    # 迴圈目的: 多次呼叫accept等待客戶端連線,為多個客服端服務
    while True:
        print("等待一個新的客戶端的到來...")
        new_client_socket, client_addr = tcp_server_socket.accept()
        print("客戶端' %s '已經到來" % str(client_addr))
        # 迴圈目的: 為同一個客服端服務多次
        while True:
            # 接收客戶端傳送過來的請求
            recv_data = new_client_socket.recv(1024)

            # recv解堵塞的兩種方式: 1.客戶端傳送過來資料,2.客戶端呼叫了close
            if recv_data:
                print("客戶端' %s '傳送過來的請求是: %s" % (str(client_addr), recv_data.decode("utf-8")))
                # 回送資料給客戶端表示響應客戶端的請求
                send_data = "----ok----Request accepted"
                new_client_socket.send(send_data.encode("utf-8"))
            else:
                break

        # 關閉accept返回的套接字
        new_client_socket.close()
        print("已經為 %s 客戶端已經服務完畢" % str(client_addr))
    # 關閉監聽套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()

4.TCP客戶端-通訊示例

import socket


def main():
    # 建立資料流套接字
    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 連線伺服器
    server_ip = input("請輸入要連線的伺服器的ip:")
    serve_port = int(input("請輸入要連線的伺服器的port:"))
    server_addr = (server_ip, serve_port)
    tcp_client_socket.connect(server_addr)  # 連線失敗會報錯
    # s = tcp_client_socket.connect_ex(server_addr)  # 有返回值,如果連線失敗不會報錯,會返回錯誤的編碼
    # 傳送資料
    send_data = input("請輸入要發生的資料:")
    tcp_client_socket.send(send_data.encode("utf-8"))
    # 接收伺服器傳送過來的資料
    recv_data = tcp_client_socket.recv(1024)
    print("接收到的資料為:%s" % recv_data.decode("utf-8"))
    # 關閉套接字
    tcp_client_socket.close()


if __name__ == "__main__":
    main()

5.TCP服務端-檔案下載示例

import socket


def send_file_client(nwe_client_socket, client_addr):
    # 1.接收客戶端傳送過來的需要下載的檔名
    file_name = nwe_client_socket.recv(1024).decode("utf-8")
    print("客戶端 %s 要下載的檔案是:%s" % (str(client_addr), file_name))
    # 2.開啟檔案讀取資料
    file_content = None
    try:
        f = open(file_name, "rb")
        file_content = f.read()
        f.close()
    except Exception as ret:
        print("沒有要下載的檔案%s" % file_name)
    # 3.傳送檔案的資料給客戶端
    if file_content:
        nwe_client_socket.send(file_content)


def main():
    # 建立套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 設定埠重用
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 繫結本地資訊
    local_addr = ("", 7890)
    tcp_server_socket.bind(local_addr)
    # 開啟監聽
    tcp_server_socket.listen(128)
    while True:
        # 等待使用者連線
        nwe_client_socket, client_addr = tcp_server_socket.accept()
        # 呼叫傳送檔案函式,完成為客戶端服務
        send_file_client(nwe_client_socket, client_addr)

        # 關閉套接字
        nwe_client_socket.close()
    # 關閉監聽套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()

6.TCP客戶端-檔案下載示例

import socket


def main():
    # 建立套接字
    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 連線伺服器
    dest_ip = input("請輸入下載伺服器的ip:")
    dest_port = int(input("請輸入下載伺服器的port:"))
    dest_addr = (dest_ip, dest_port)
    tcp_client_socket.connect(dest_addr)
    # 獲取要下載的檔名
    download_file_name = input("請輸入要下載的檔名:")
    # 傳送要下載的檔名
    tcp_client_socket.send(download_file_name.encode("utf-8"))
    # 接收檔案資料
    recv_data = tcp_client_socket.recv(1024)  # 一次接收1k資料
    # 開啟檔案寫入資料
    if recv_data:
        with open("[new]" + download_file_name, "wb") as f:
            f.write(recv_data)
    # 關閉套接字
    tcp_client_socket.close()


if __name__ == "__main__":
    main()

7.TCP粘包現象

1.傳送接收緩衝區
傳送和接收訊息均放到快取區再進行處理
當recv接收訊息一次接收不完的時候下次會繼續接收,當recv阻塞時,如果客戶端斷開則recv立即返回空字串

2.TCP粘包概述:
1.TCP中資料以資料流的方式傳送接收,每次傳送的資料間沒有邊界,在接收時可能造成資料的粘連即為粘包
2.合包機制造成資料混亂: nagle演算法將多次連續傳送且間隔較小的資料進行打包成一個數據傳輸
3.拆包機制造成資料混亂: 在傳送端因為受到資料鏈路層網絡卡的MTU限制,會將大的超過MTU限制的資料進行拆分成多個小的資料包進行傳輸
當傳輸到目標主機的作業系統層時,會將多個小的資料包合併成原本的資料包

3.粘包如何處理方案:
1.每次傳送訊息和結束位置加標誌,每次收到訊息按結束標準切割
2.傳送的訊息新增結構描述,例如加一個包頭,包頭裡記錄 name:大小, password: 大小
3.當連續傳送的時每次傳送有一個短暫延遲sleep(0.1)

4.tcp粘包參考連結: https://www.cnblogs.com/LY-C/p/9120992.html

8.TCP服務端-粘包現象驗證

import socket


def main():
    # 建立資料流套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 繫結埠
    tcp_server_socket.bind(("", 7890))
    # 讓預設的套接字由主動變為被動監聽 listen
    tcp_server_socket.listen(128)
    # 等待客戶端連線
    new_client_socket, client_addr = tcp_server_socket.accept()
    print("客戶%s端已連線" % str(client_addr))

    # 迴圈目的: 為同一個客服端服務多次
    while True:
        # 接收客戶端傳送過來的請求
        recv_data = new_client_socket.recv(1024)
        # recv解堵塞的兩種方式: 1.客戶端傳送過來資料,2.客戶端呼叫了close
        if not recv_data:
            break

        # 客戶端是先遍歷["Coco", "1355656****", "[email protected]", "xx省xx市xx區xxx"]列表,再分了4次傳送使用者資訊的資料
        # 但是此時服務端收到的資料卻是連續的: Coco1355656****[email protected]省xx市xx區xxx
        print("客戶端' %s '傳送過來的請求是: %s" % (str(client_addr), recv_data.decode("utf-8")))
        # 回送資料給客戶端表示響應客戶端的請求
        send_data = "----ok----Request accepted"
        new_client_socket.send(send_data.encode("utf-8"))

    # 關閉accept返回的套接字
    new_client_socket.close()
    print("已經為 %s 客戶端已經服務完畢" % str(client_addr))
    # 關閉監聽套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()

9.TCP客戶端-粘包現象驗證

import socket
import time


def main():
    # 建立資料流套接字
    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 連線伺服器
    server_addr = ("", 7890)
    tcp_client_socket.connect(server_addr)
    # 傳送資料
    user_info = ["Coco", "1355656****", "[email protected]", "xx省xx市xx區xxx"]
    for i in user_info:
        tcp_client_socket.send(str(i).encode("utf-8"))
        # 傳送時新增延時可以解決粘包現象
        # time.sleep(0.1)
    # 接收伺服器傳送過來的資料
    recv_data = tcp_client_socket.recv(1024)
    print("接收到的資料為:%s" % recv_data.decode("utf-8"))
    # 關閉套接字
    tcp_client_socket.close()


if __name__ == "__main__":
    main()

10.TCP服務端-大檔案上傳

import socket
import json
import struct


def main():
    # 建立資料流套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 設定埠重用
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 繫結埠
    tcp_server_socket.bind(("", 7890))
    # 讓預設的套接字由主動變為被動監聽 listen
    tcp_server_socket.listen(128)

    # 迴圈目的: 多次呼叫accept等待客戶端連線,為多個客服端服務
    while True:
        print("等待一個新的客戶端的到來...")
        new_client_socket, client_addr = tcp_server_socket.accept()
        print("客戶端' %s '已經到來" % str(client_addr))

        # 接收客戶端傳送過來的請求
        b_len_dic = new_client_socket.recv(4)

        print(b_len_dic)
        len_dic = struct.unpack("i", b_len_dic)[0]  # unpack得到一個元祖,取下標0的位置獲取到int型別的字典長度
        recv_data = new_client_socket.recv(len_dic).decode("utf-8")

        # new_client_socket.send(b"OK")  # 向客戶端傳送檔案準備就緒標識,同時也避免接收資料過快產生粘包

        recv_dic = json.loads(recv_data)
        if recv_dic["opt"] == "upload":
            filename = "副本" + recv_dic["filename"]
            with open(filename, "ab") as f:
                while recv_dic["filesize"]:
                    content = new_client_socket.recv(1024)
                    f.write(content)
                    recv_dic["filesize"] -= len(content)
        elif recv_dic["opt"] == "download":
            pass

        # 關閉accept返回的套接字
        new_client_socket.close()
        print("已經為 %s 客戶端已經服務完畢" % str(client_addr))
    # 關閉監聽套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()

11.TCP客戶端-大檔案上傳

import socket
import os
import json
import struct


def main():
    # 建立資料流套接字
    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 連線伺服器
    server_ip = input("請輸入要連線的伺服器的ip:")
    serve_port = int(input("請輸入要連線的伺服器的port:"))
    server_addr = (server_ip, serve_port)
    tcp_client_socket.connect(server_addr)  # 連線失敗會報錯
    # s = tcp_client_socket.connect_ex(server_addr)  # 有返回值,如果連線失敗不會報錯,會返回錯誤的編碼

    menu = {"1": "upload", "2": "download"}
    for i in menu:
        print(i, menu[i])
    num = input("請輸入功能選項: ")
    if num == "1":
        send_dic = {"opt": menu.get(num), "filename": None, "filesize": None}
        file_path = input("請輸入一個絕對路徑: ")  # 要上傳檔案的絕對路徑
        filename = os.path.basename(file_path)  # 獲取要上傳檔案的檔名
        filesize = os.path.getsize(file_path)  # 獲取檔案大小

        send_dic["filename"] = filename
        send_dic["filesize"] = filesize
        send_data = json.dumps(send_dic)

        len_dic = len(send_data)  # 獲取字典長度,是一個int資料型別,可能是30,也可能是120
        b_len_dic = struct.pack("i", len_dic)  # 加字典長度打包成一個4位的bytes型別
        # 將bytes型別的字典長度 + bytes型別的字典內容,一起傳送給伺服器
        tcp_client_socket.send(b_len_dic + send_data.encode("utf-8"))

        # tcp_client_socket.recv(1024)  # 1.向伺服器確認是否可以上傳檔案;2.避免與下列程式碼的send過快造成粘包

        with open(file_path, "rb") as f:
            while filesize:
                content = f.read(1024)
                tcp_client_socket.send(content)
                filesize -= len(content)

        # 傳送資料
        tcp_client_socket.send(send_data.encode("utf-8"))
    elif num == "2":
        pass

    # 關閉套接字
    tcp_client_socket.close()


if __name__ == "__main__":
    main()

12.TCP服務端-身份加密驗證

import socket
import os
import hmac


def main():
    # 建立資料流套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 設定埠重用
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 繫結埠
    tcp_server_socket.bind(("", 7890))
    # 讓預設的套接字由主動變為被動監聽 listen
    tcp_server_socket.listen(128)

    # 迴圈目的: 多次呼叫accept等待客戶端連線,為多個客服端服務
    while True:
        print("等待一個新的客戶端的到來...")
        new_client_socket, client_addr = tcp_server_socket.accept()
        print("客戶端' %s '已經到來" % str(client_addr))

        sor = b"hmy"  # 伺服器加鹽
        r_str = os.urandom(16)  # 隨機一個16位長度的bytes型別資料
        new_client_socket.send(r_str)

        # md5加密
        md5_obj = hmac.new(sor, r_str)
        result = md5_obj.digest()

        # 接收客戶端傳送過來的密文與伺服器的密文對比
        recv_msg = new_client_socket.recv(1024)
        if recv_msg == result:
            new_client_socket.send(b"success")
        else:
            new_client_socket.send(b"failed")

        # 關閉accept返回的套接字
        new_client_socket.close()
        print("已經為 %s 客戶端已經服務完畢" % str(client_addr))
    # 關閉監聽套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()

13.TCP客戶端-身份加密驗證

import socket
import hmac


def main():
    # 建立資料流套接字
    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 連線伺服器
    server_ip = input("請輸入要連線的伺服器的ip:")
    serve_port = int(input("請輸入要連線的伺服器的port:"))
    server_addr = (server_ip, serve_port)
    tcp_client_socket.connect(server_addr)  # 連線失敗會報錯
    # s = tcp_client_socket.connect_ex(server_addr)  # 有返回值,如果連線失敗不會報錯,會返回錯誤的編碼

    sor = b"hmy"  # 客戶端加鹽
    r_str = tcp_client_socket.recv(1024)

    # md5加密
    md5_obj = hmac.new(sor, r_str)
    result = md5_obj.digest()
    # 向伺服器傳送驗證密文
    tcp_client_socket.send(result)

    # 接收驗證結果
    recv_msg = tcp_client_socket.recv(1024)
    print(recv_msg)

    # 關閉套接字
    tcp_client_socket.close()


if __name__ == "__main__":
    main()

14.TCP服務端-切換目錄

import socket
import os


def send_data(new_client_socket, path):
    """你給我一個目錄,我把目錄發給client"""
    lis_dir = os.listdir(path)
    str_dir = '--'.join(lis_dir)
    new_client_socket.send(str_dir.encode('utf-8'))


def main():
    # 建立資料流套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 設定埠重用
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 繫結埠
    tcp_server_socket.bind(("", 7890))
    # 讓預設的套接字由主動變為被動監聽 listen
    tcp_server_socket.listen(128)

    # 迴圈目的: 多次呼叫accept等待客戶端連線,為多個客服端服務
    while True:
        print("等待一個新的客戶端的到來...")
        new_client_socket, client_addr = tcp_server_socket.accept()
        print("客戶端' %s '已經到來" % str(client_addr))

        abs_path = new_client_socket.recv(1024).decode('utf-8')  # 獲取使用者輸入的絕對路徑
        current_dir = abs_path + '/'  # 以下再處理,都要根據當前路徑去處理,無論是返回上一層,還是進入下一層
        send_data(new_client_socket, current_dir)  # 把使用者輸入的路徑下的所有檔案及資料夾返回給客戶端

        while True:
            # 測試輸入: /Users/tangxuecheng
            cmd = new_client_socket.recv(1024).decode('utf-8')
            if cmd == '..':
                current_dir = current_dir.split('/')[:-2]
                current_dir = '/'.join(current_dir) + '/'
                # if 如果當前是根目錄:
                #     就返回給客戶端告訴說沒有上一層了
                send_data(new_client_socket, current_dir)
            else:
                filename = cmd.split(' ')[1]  # 獲取使用者輸入的檔名字
                current_dir += filename + '/'  # 將檔名字新增到當前路徑下,組成一個完整的新路徑
                if os.path.isdir(current_dir):  # 如果客戶輸入的檔名字是一個資料夾
                    send_data(new_client_socket, current_dir)
                else:  # 如果不是一個資料夾
                    new_client_socket.send(b'not a folder')

        # 關閉accept返回的套接字
        new_client_socket.close()
        print("已經為 %s 客戶端已經服務完畢" % str(client_addr))
    # 關閉監聽套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()

15.TCP客戶端-切換目錄

import socket


def main():
    # 建立資料流套接字
    tcp_client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 連線伺服器
    server_ip = input("請輸入要連線的伺服器的ip:")
    serve_port = int(input("請輸入要連線的伺服器的port:"))
    server_addr = (server_ip, serve_port)
    tcp_client_socket.connect(server_addr)  # 連線失敗會報錯
    # s = tcp_client_socket.connect_ex(server_addr)  # 有返回值,如果連線失敗不會報錯,會返回錯誤的編碼

    # 測試輸入: /Users/tangxuecheng
    abs_path = input('請輸入您的根目錄:')
    tcp_client_socket.send(abs_path.encode('utf-8'))
    current_dir = tcp_client_socket.recv(1024).decode('utf-8')
    print(current_dir.split('--'))

    while True:
        cmd = input('請輸入>>>')
        # cd + 資料夾      ..
        if cmd == '..':
            tcp_client_socket.send(cmd.encode('utf-8'))
            current_dir = tcp_client_socket.recv(1024).decode('utf-8')
            print(current_dir.split('--'))
        if cmd == 'cd':
            filename = input('請輸入一個資料夾名:')
            tcp_client_socket.send((cmd + ' ' + filename).encode('utf-8'))
            current_dir = tcp_client_socket.recv(1024).decode('utf-8')
            print(current_dir.split('--'))

    # 關閉套接字
    tcp_client_socket.close()


if __name__ == "__main__":
    main()

16.TCP應用-web靜態伺服器

# html資料夾網盤連結: https://pan.baidu.com/s/1wbZ92KLstff3GurLV_C0lg  密碼: hp9o
# 下載完命令列解壓後放到程式的同級路徑下,解壓命令: tar -zxvf 06_html.tar.gz -C ./
import socket


def client_handle(client_socket):
    # 接收客戶端傳送過來的請求資料
    request = client_socket.recv(2048)
    request_handles = request.splitlines()
    for line in request_handles:
        print(line)
    try:
        f = open("./html/index.html", "r")
    except IOError:
        # 找不到檔案時返回404錯誤
        response = "HTTP/1.1 404 not found\r\n"
        response += "\r\n"
        response += "sorry file not found"
    else:
        # 找到檔案時讀取檔案內容
        response = "HTTP/1.1 200 OK\r\n"
        response += "\r\n"
        for line in f:
            response += line
    finally:
        # 向瀏覽器返回資訊
        client_socket.send(response.encode())
        # 關閉客戶端套接字
        client_socket.close()


def main():
    # 1.建立套接字
    tcp_server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2.設定埠重用
    tcp_server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
    # 4.允許區域網內使用者訪問
    local_addr = ("0.0.0.0", 7890)
    tcp_server_socket.bind(local_addr)
    # 5.監聽
    tcp_server_socket.listen(128)
    # 5.迴圈服務
    while True:
        # 客戶端連線
        client_socket, client_addr = tcp_server_socket.accept()
        # 為客戶端服務
        client_handle(client_socket)

    # 6.關閉監聽套接字
    tcp_server_socket.close()


if __name__ == "__main__":
    main()

3.UDP資料報套接字

1.UDP套接字使用流程 1.建立資料報套接字 sockfd = socket(AF_INET,SOCK_DGRAM) 引數: AF_INET表示ipv4,SOCK_DGRAM表示資料報套接字 2.繫結服務端地址: sockfd.bind("127.0.0.1", 7890) 3.收發訊息 接收訊息(recvfrom) data, addr = sockfd.recvfrom(buffersize) # 一次接收一個數據包,如果是資料包一次沒有接收完則會丟失沒有接收的內容 引數: 每次最多接收訊息的大小(位元組) 返回值: data: 接收到的訊息 addr: 訊息傳送者的地址 傳送訊息(sendto) sockfd.sendto(data, addr) # 傳送訊息 引數: data:要傳送的訊息 addr: 傳送給某個主機的地址 返回值: 傳送訊息的位元組數 4.關閉套接字: sockfd.close() 2.UDP應用-傳送訊息 import socket def main(): # 建立一個udp套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 指定接訊息收方的地址 dest_addr = ("", 7788) while True: # 從鍵盤上獲取資料 send_data = input("請輸入要傳送的資料:") # 如果輸入的資料是exit則退出程式 if send_data.lower() == "exit": break # 使用套接字收發資料 # udp_socket.sendto(b"hello world!", ("169.254.119.158", 8080)) udp_socket.sendto(send_data.encode("utf-8"), dest_addr) # 關閉套接字 udp_socket.close() if __name__ == "__main__": main() 3.UDP應用-接收訊息 import socket def main(): # 1.建立套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2.繫結本地資訊 local_addr = ("", 7788) udp_socket.bind(local_addr) # 3.接收資料 while True: recv_data = udp_socket.recvfrom(1024) # 1024表示本次接收的最大位元組數 # 4.列印接收的資料 # recv_data這個變數中儲存的是一個元祖(接收到的資料, (傳送方的ip, port)) recv_msg = recv_data[0] # 第1個元素是對方傳送的資料,型別為位元組 recv_addr = recv_data[1] # 第2個元素是對方的ip和埠,型別為元祖 print(type(recv_msg), type(recv_addr)) # <class 'bytes'> <class 'tuple'> # 功能擴充套件: 含有敏感詞彙訊息在輸出時進行顏色標識 color_dic = {"使用者名稱": "\033[32m", "密碼": "\033[33m", "驗證": "\033[0;32;40m"} msg = recv_msg.decode("utf-8") for i in color_dic: if i in msg: color = color_dic[i] print("%s%s:%s\033[0m" % (color, str(recv_addr), msg)) break else: print("%s:%s" % (str(recv_addr), msg)) # ('127.0.0.1', 61804):Tom你好嗎? # 5.關閉套接字 udp_socket.close() if __name__ == "__main__": main() 4.UDP應用-實現廣播的設定 1.將廣播接收端套和傳送端的接字屬性設定為允許接收廣播 2.將廣播發送端的地址設定為傳送給區域網所有終端: ("192.168.0.255", 7890) 或 ("", 7890) 3.廣播風暴: 在一個網路中大量傳送廣播會佔用大量頻寬,解決方案是組播 5.UDP應用-廣播接收端 # broadcast_recv.py import socket def main(): # 1.建立套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2.設定套接字允許接收廣播 udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # 3.設定埠重用 udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) # 4.固定接收端的埠號 local_addr = ("", 7890) udp_socket.bind(local_addr) # 5.接收資料 while True: try: recv_msg, recv_addr = udp_socket.recvfrom(1024) # 1024表示本次接收的最大位元組數 # 6.列印接收的資料 # print(type(recv_msg), type(recv_addr)) # <class 'bytes'> <class 'tuple'> print("從廣播{}獲取訊息: {}".format(str(recv_addr), recv_msg.decode("utf-8"))) # 7.回送訊息 udp_socket.sendto(b"ok", recv_addr) except (KeyboardInterrupt, SyntaxError): # control + c 終止程式或者語法錯誤時捕獲異常 raise except Exception as e: print(e) # 8.關閉套接字 udp_socket.close() if __name__ == "__main__": main() 6.UDP應用-廣播發送端 # broadcast_send.py import socket import time def main(): # 1.建立一個udp套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2.設定套接字允許接收廣播 udp_socket.setsockopt(socket.SOL_SOCKET, socket.SO_BROADCAST, 1) # 3.設定傳送廣播的地址 # dest_addr = ("192.168.0.255", 7890) # 這種寫法也可以 dest_addr = ("", 7890) # 將訊息傳送給區域網所有終端 while True: time.sleep(1) # 每隔1秒傳送一次廣播 # 使用套接字收發資料 udp_socket.sendto("開始廣播了...".encode("utf-8"), dest_addr) recv_msg, recv_addr = udp_socket.recvfrom(1024) # 1024表示本次接收的最大位元組數 print("從接收端{}獲取訊息: {}".format(str(recv_addr), recv_msg.decode("utf-8"))) # 關閉套接字 udp_socket.close() if __name__ == "__main__": main() 7.函式實現-UDP聊天器 import socket def send_msg(udp_socket): """獲取鍵盤資料,並將其傳送給對方""" # 1. 從鍵盤輸入資料 msg = input("\n請輸入要傳送的資料:") # 2. 輸入對方的ip地址 dest_ip = input("\n請輸入對方的ip地址:") # 3. 輸入對方的port dest_port = int(input("\n請輸入對方的port:")) # 4. 傳送資料 udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port)) def recv_msg(udp_socket): """接收資料並顯示""" # 1. 接收資料 recv_msg = udp_socket.recvfrom(1024) # recv_msg, recv_ip = udp_socket.recvfrom(1024) # 2. 解碼 recv_ip = recv_msg[1] recv_msg = recv_msg[0].decode("utf-8") # 3. 顯示接收到的資料 print(">>>%s:%s" % (str(recv_ip), recv_msg)) def main(): # 1. 建立套接字 udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2. 繫結本地資訊 udp_socket.bind(("", 7788)) while True: # 3. 選擇功能 print("="*30) print("1:傳送訊息") print("2:接收訊息") print("0:退出系統") print("="*30) op_num = input("請輸入要操作的功能序號:") # 4. 根據選擇呼叫相應的函式 if op_num == "1": send_msg(udp_socket) elif op_num == "2": recv_msg(udp_socket) elif op_num == "0": break else: print("輸入有誤,請重新輸入...") # 5.關閉套接字 udp_socket.close() if __name__ == "__main__": main() 8.物件實現-UDP聊天器 import socket class UdpSocket: def __init__(self): # 1. 建立套接字 self.udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) # 2. 繫結本地資訊 self.udp_socket.bind(("", 7788)) # 傳送訊息 def send_msg(self): """獲取鍵盤資料,並將其傳送給對方""" # 1. 從鍵盤輸入資料 msg = input("\n請輸入要傳送的資料:") # 2. 輸入對方的ip地址 dest_ip = input("\n請輸入對方的ip地址:") # 3. 輸入對方的port dest_port = int(input("\n請輸入對方的port:")) # 4. 傳送資料 self.udp_socket.sendto(msg.encode("utf-8"), (dest_ip, dest_port)) # 接收訊息 def recv_msg(self): """接收資料並顯示""" # 1. 接收資料 recv_msg = self.udp_socket.recvfrom(1024) # recv_msg, recv_ip = self.udp_socket.recvfrom(1024) # 2. 解碼 recv_ip = recv_msg[1] recv_msg = recv_msg[0].decode("utf-8") # 3. 顯示接收到的資料 print(">>>%s:%s" % (str(recv_ip), recv_msg)) # 功能選擇 def start_menu(self): while True: print("="*30) print("1:傳送訊息") print("2:接收訊息") print("0:退出系統") print("="*30) op_num = input("請輸入要操作的功能序號:") # 4. 根據選擇呼叫相應的函式 if op_num == "1": self.send_msg() elif op_num == "2": self.recv_msg() elif op_num == "0": break else: print("輸入有誤,請重新輸入...") # 關閉套接字 def __del__(self): print("%s物件已回收" % self) self.udp_socket.close() def main(): # 例項化一個udp聊天器物件 udp_chat = UdpSocket() # 啟動聊天器 udp_chat.start_menu() if __name__ == "__main__": main() 9.自定義socket類 import socket class MySocket(socket.socket): """自定義MySocket類繼承自socket模組中的socket""" def __init__(self, encoding="utf-8"): # 呼叫父類中的初始化方法 super().__init__(type=socket.SOCK_DGRAM) # 自定義預設的編碼格式 self.encoding = encoding def my_sendto(self, send_msg, send_addr): return self.sendto(send_msg.encode(self.encoding), send_addr) def my_recvfrom(self, num): recv_msg, recv_addr = self.recvfrom(num) return recv_msg.decode(self.encoding), recv_addr 10.UDP收發資料包 1.UDP不會發生粘包現象 2.UDP收發資料包大小取捨 udp協議本身對一次收發訊息資料大小限制是: 65535 - ip包頭(20) - udp包頭(8) = 65507 在資料鏈路層網絡卡的MTU一般為1500的限制,所以在鏈路層收發資料包大小限制為: 1500 - ip包頭(20) - udp包頭(8) = 1472 3.UDP收發資料包大小結論 接收資料包: recvfrom(num) # num < 65507 傳送資料包: sendto(num) num > 65507 # 報錯 1472 < num < 65507 # 在資料鏈路層拆包傳送,然而udp本身就是不可靠協議 # 所以一旦拆包後,造成的多個小資料包在網路傳輸中,如果丟失任意一個,那麼此次資料傳輸失敗 num < 1472 # 是比較理想的狀態

4.本地套接字

1.Linux下常用檔案辨識(ls -lh 第一個用來辨識檔案型別) d: 資料夾 -: 普通檔案 l: 連結檔案 s: 套接字檔案,是一個虛擬檔案且大小在顯示上永遠為0,存在的意義是在Linux/Unix下提供本地程序間通訊的一種方式 p: 管道檔案 2.UNIX本地套接字使用流程 服務端流程 1.建立本地套接字: unix_socket = socket(AF_UNIX, SOCK_STREAM) 2.繫結套接字檔案: unix_socket.bind("./unix_socket_test") 3.監聽: unix_socket.listen(128) 4.等待客戶端連線: new_client_socket, client_addr = unix_socket.accept() 4.接收連線: recv_data = unix_socket.recv(1024) 5.收發訊息: new_client_socket.send() 6.關閉客戶端套接字: new_client_socket.close() 8.關閉本地套接字: unix_socket.close() 客戶端流程 1.建立本地套接字: unix_client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) 2.連線本地套接字檔案: unix_client_socket.connect(unix_server_address) 3.傳送訊息: unix_client_socket.send() 4.接收訊息: recv_data = unix_client_socket.recv(1024) 5.關閉套接字: unix_client_socket.close() 3.UNIX應用-客戶端 import socket import sys import traceback def main(): try: # 建立unix本地套接字 unix_client_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) # 檔案已經被服務端建立,且和服務端使用的是同一個socket檔案 unix_server_address = "./unix_socket_test" # 連線本地套接字檔案 unix_client_socket.connect(unix_server_address) except socket.error: traceback.print_exc() sys.exit(1) while True: # 傳送資料 send_data = input("請輸入要發生的資料:") if send_data: unix_client_socket.sendall(send_data.encode("utf-8")) # 接收伺服器傳送過來的資料 recv_data = unix_client_socket.recv(1024) print("接收到的資料為:%s" % recv_data.decode("utf-8")) else: break # 關閉套接字 unix_client_socket.close() if __name__ == "__main__": main() 4.UNIX應用-服務端 import socket import os def main(): # 確定那個檔案作為通訊檔案 unix_server_address = "./unix_socket_test" # 判斷通訊檔案是否存在,如果存在則刪除檔案 if os.path.exists(unix_server_address): os.unlink(unix_server_address) # 建立一個unix本地套接字 unix_socket = socket.socket(socket.AF_UNIX, socket.SOCK_STREAM) # 繫結本地套接字檔案 unix_socket.bind(unix_server_address) # 監聽 unix_socket.listen(128) # 迴圈目的: 多次呼叫accept等待客戶端連線,為多個客服端服務 while True: print("等待一個新的客戶端的到來...") new_client_socket, client_addr = unix_socket.accept() print("客戶端' %s '已經到來" % str(client_addr)) # 迴圈目的: 為同一個客服端服務多次 while True: # 接收客戶端傳送過來的請求 recv_data = new_client_socket.recv(1024) # recv解堵塞的兩種方式: 1.客戶端傳送過來資料,2.客戶端呼叫了close if recv_data: print("客戶端' %s '傳送過來的請求是: %s" % (str(client_addr), recv_data.decode("utf-8"))) # 回送資料給客戶端表示響應客戶端的請求 send_data = "----ok----Request accepted" new_client_socket.send(send_data.encode("utf-8")) else: break # 5.關閉客戶端套接字 new_client_socket.close() # 關閉本地套接字 unix_socket.close() if __name__ == "__main__": main()

5.TCP和UDP區別

1.TCP傳輸資料使用位元組流方式傳輸,UDP是資料包 2.TCP會產生粘包現象,UDP不會 3.TCP對網路條件要求高,UDP不需要 4.TCP程式設計可以保證傳輸的可靠性,UDP不保證 5.TCP使用listen和accept,UDP不需要 6.收發訊息 TCP使用recv send sendall # sendall用法和send一樣,只是返回值不同,成功返回None, 失敗則產生異常 UDP使用recvfrom sendto