Python進階之網絡編程


網絡通信

使用網絡的目的

把多方鏈接在一起,進行數據傳遞;
網絡編程就是,讓不同電腦上的軟件進行數據傳遞,即進程間通信;

ip地址

ip地址概念和作用

IP地址是什么:比如192.168.1.1 這樣的一些數字;
ip地址的作用:用來在電腦中 標識唯一一台電腦,比如192.168.1.1;在本地局域網是唯一的。

網卡信息

查看網卡信息

Linux:ifconfig
windows:ipconfig

  • ensxx:用來與外部進行通信的網卡;
  • lo:環回網卡,用來進行本地通信的;

linux關閉/開啟網卡:sudo ifconfig ensxx down/up

ip和ip地址的分類

ip分為ipv4和ipv6

ip地址分為:

  • A類地址
  • B類地址
  • C類地址
  • D類地址--用於多播
  • E類地址--保留地址,因ipv6誕生,已無用
  • 私有ip

單播--一對一
多播--一對多
廣播--多對多

端口

ip:標識電腦;
端口:標識電腦上的進程(正在運行的程序);
ip和端口一起使用,唯一標識主機中的應用程序,進行統一軟件的通信;

端口分類

知名端口

固定分配給特定進程的端口號,其他進程一般無法使用這個端口號;
小於1024的,大部分都是知名端口;
范圍從0~1023;

動態端口

不固定分配,動態分配,使用后釋放的端口號;
范圍1024~65535;

socket

socket的概念

socket是進程間通信的一種方式,能實現不同主機間的進程間通信,即socket是用來網絡通信必備的東西;

創建socket

創建套接字:

import socket
soc = socket.socket(AddressFamily, Type)

函數socket.socket創建一個socket,該函數有兩個參數:
Address Family:可選 AF_INET(用於internet進程間通信)和AF_UNIX(用於同一台機器進程間通信);
Type:套接字類型,可選 SOCK_STREAM(流式套接字,主用於TCP協議)/SOCK_DGRAM(數據報套接字,主用於UDP套接字);

創建tcp套接字

import socket

soc = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
...
soc.close()

創建udp套接字

import socket

soc = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
...
soc.close()

udp

udp使用socket發送數據

在同一局域網內發消息;
如果用虛擬機和windows,要用橋接模式,確保在同一局域網內;

import socket


def main():
    # 創建一個udp套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 使用套接字收發數據
    udp_socket.sendto(b"hahaha", ("193.168.77.1", 8080))
    # 關閉套接字
    udp_socket.close()


if __name__ == "__main__":
    main()

udp發送數據的幾種情況:

  1. 在固定數據的引號前加b,不能使用於用戶自定義數據;
  2. 用戶自定義數據,並進行發送,使用.encode("utf-8")進行encode編碼
  3. 用戶循環發送數據
  4. 用戶循環發送數據並可以退出

只貼出最后一種情況,即完整代碼

import socket


def main():
    # 創建一個udp套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    while 1:
        # 從鍵盤獲取要發送的數據
        send_data = input("請輸入你要發送的數據:")
        if send_data == "exit":
            break
        # 使用套接字收發數據
        udp_socket.sendto(send_data.encode("utf-8"), ("193.168.77.1", 8080))

    # 關閉套接字
    udp_socket.close()


if __name__ == "__main__":
    main()

udp接收數據

接收到的數據是一個元組,元組第一部分是發送方發送的內容,元組第二部分是發送方的ip地址和端口號;

import socket


def main():
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    localaddr = ('', 8080)
    udp_socket.bind(localaddr)  # 必須綁定自己電腦的ip和端口

    # 接收數據
    recv_data = udp_socket.recvfrom(1024)
    # recv_data這個變量存儲的是一個元組,例如 (b'hahaha', ('192.168.77.1', 8888))
    recv_msg = recv_data[0]
    send_addr = recv_data[1]
    # print("%s 發送了:%s" % (str(send_addr), recv_msg.decode("utf-8")))  # linux發送的數據用utf8解碼
    print("%s 發送了:%s" % (str(send_addr), recv_msg.decode("gbk")))  # windows發送的數據用gbk解碼

    udp_socket.close()


if __name__ == "__main__":
    main()

udp接發數據總結

發送數據的流程:

  1. 創建套接字
  2. 發送數據
  3. 關閉套接字

接收數據的流程:

  1. 創建套接字
  2. 綁定本地自己的信息,ip和端口
  3. 接收數據
  4. 關閉套接字

端口綁定的問題

  • 如果在你發送數據時,還沒有綁定端口,那么操作系統就會隨機給你分配一個端口,循環發送時用的是同一個端口;
  • 也可以先綁定端口,再發送數據。

udp發送消息時自己綁定端口示例

import socket


def main():
    # 創建一個udp套接字
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    # 綁定端口
    udp_socket.bind(('192.168.13.1', 8080))
    while 1:
        # 從鍵盤獲取要發送的數據
        send_data = input("請輸入你要發送的數據:")
        if send_data == "exit":
            break
        # 使用套接字收發數據
        udp_socket.sendto(send_data.encode("utf-8"), ("193.168.77.1", 8080))

    # 關閉套接字
    udp_socket.close()  # 按ctrl+c退出


if __name__ == "__main__":
    main()

但應注意,同一端口在同一時間不能被兩個不同的程序同時使用

單工,半雙工,全雙工

單工半雙工全雙工的理解

單工:
只能單向發送信息,別人接收,別人不能回復消息,比如廣播;

半雙工:
兩個人都能發消息,但是在同一時間只能有一個人發消息,比如對講機;

全雙工
兩個人都能發消息,能同時發,比如打電話;

udp使用同一套接字收且發數據

"""socket套接字是全雙工"""
import socket


def main():
    udp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
    udp_socket.bind(('192.168.13.1', 8080))
    # 讓用戶輸入要發送的ip地址和端口
    dest_ip = input("請輸入你要發送數據的ip地址:")
    dest_port = int(input("請輸入你要發送數據的端口號:"))

    # 從鍵盤獲取要發送的數據
    send_data = input("請輸入你要發送的數據:")
    # 使用套接字收發數據
    udp_socket.sendto(send_data.encode("utf-8"), (dest_ip, dest_port))
    # 套接字可以同時 收發數據;
    recv_data = udp_socket.recvfrom(1024)
    print(recv_data)

    # 關閉套接字
    udp_socket.close()  # 按ctrl+c退出


if __name__ == "__main__":
    main()

在這里體現不出來socket是全雙工,因為現在解釋器只能按照流程,一步一步走下去,后面學習了進程線程協程就可以做到了。

tcp

tcp-可靠傳輸

tcp采取的機制

  1. 采用發送應答機制
  2. 超時重傳
  3. 錯誤校驗
  4. 流量控制和阻塞管理

tcp與udp的區別

  1. tcp更安全可靠,udp相對沒那么安全可靠;
  2. 面向連接
  3. 有序數據傳輸
  4. 重發丟失的數據
  5. 舍棄重復的數據包
  6. 無差錯的數據傳輸
  7. 阻塞/流量控制

tcp,udp應用場景

tcp應用場景:下載,發送消息
udp應用場景:電話,視頻直播等

tcp客戶端

tcp客戶端發送數據

import socket


def main():
    # 1.創建tcp的套接字
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2.鏈接服務器
    tcp_socket.connect(('193.168.11.1', 8080))
    # 3.發送/接收消息
    send_data = input("請輸入你要發送的消息:")
    tcp_socket.send(send_data.encode("utf-8"))
    # 4.關閉套接字
    tcp_socket.close()


if __name__ == "__main__":
    main()

tcp服務器

監聽套接字,專門用來監聽的;
accept會對應新創建的套接字,當監聽套接字收到一個請求后,將該請求分配給新套接字,由此監聽套接字可以繼續去監聽了,而新套接字則為該胡克段服務。

import socket


def main():
    # 創建tcp套接字
    tcp_service_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    tcp_service_socket.bind(('', 8080))
    # 讓默認的套接字由主動變為被動
    tcp_service_socket.listen(128)

    # 等待客戶端的鏈接
    new_client_socket, client_addr = tcp_service_socket.accept()
    print("鏈接的客戶端地址為:", client_addr)
    # 接收客戶端發送過來的請求
    recv_data = new_client_socket.recvfrom(1024)
    print(recv_data)
    # 給客戶端回送消息
    new_client_socket.send("hahahah".encode("utf-8"))

    new_client_socket.close()
    tcp_service_socket.close()


if __name__ == '__main__':
    main()

listen里面的參數,表示同時只允許128個鏈接訪問。

QQ不綁定端口的運行原理-擴展

udp和tcp並用;
使用QQ,先登錄,登錄后告訴騰訊服務器此QQ運行的端口,發消息時,通過騰訊服務器轉發給另一個QQ;
不綁定端口也有一個好處,就是允許多開,即一個電腦上可以運行多個QQ;

recv和recvfrom的區別

recvfrom里面不僅有發過來的數據,還有發過來數據的人的信息;
recv里面就只有數據;

tcp客戶端服務端流程梳理

tcp服務器流程梳理

  1. 創建服務器套接字
  2. 綁定本地信息
  3. 讓默認的套接字由主動變為被動
  4. 等待客戶端的鏈接,堵塞
  5. 被客戶端鏈接后,創建一個新的客服套接字為客戶端服務;
  6. 接收客戶端發送的消息,堵塞
  7. 接收客戶端發送的消息后,給客戶端回消息
  8. 關閉客服套接字,關閉服務端套接字

tcp注意點

  1. tcp服務器一般情況下都需要綁定,否則客戶端找不到這個服務器。

  2. tcp客戶端一般不綁定,因為是主動鏈接服務器,所以只要確定好服務器的ip, port等信息就好,本地客戶端可以隨機。

  3. tcp服務器通過listen可以將socket創建出來的主動套接字變為被動的,這是做tcp服務器時必須要做的。

  4. 當客戶端需要鏈接服務器時,就需要使用connect進行鏈接, udp是不需要鏈接的而是直接發送,但是tcp必須先鏈接,只有鏈接成功才能通信。

  5. 當一個tcp客戶端連接服務器時,服務器端會有1個新的套接字,這個套接字用來標記這個客戶端,單獨為這個客戶端服務。

  6. liston后的套接字是被動套接字,用來接收新的客戶端的鏈接請求的,而accept返回的新套接字是標記這個新客戶端的。

  7. 關閉isten后的套接字意味着被動套接字關閉了,會導致新的客戶端不能夠鏈接服務器,但是之前已經鏈接成功的客戶端正常通信。

  8. 關閉accept返回的套接字意味着這個客戶端已經服務完畢。

9.當客戶端的套接字調用close后.服務器端會recv解堵塞,並且返回的長度為0,因此服務器可以通過 返回數據的長度來區別客戶端是否已經下線。

tcp應用案例 ###

示例1-為一個用戶辦理一次業務:

"""可以理解為銀行一個客服為排隊的人員辦理業務"""

import socket


def main():
    # 1.創建tcp套接字
    tcp_service_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2.綁定本地信息
    tcp_service_socket.bind(('', 8080))

    # 3.讓默認的套接字由主動變為被動
    tcp_service_socket.listen(128)
    while 1:
        # 4.等待客戶端的鏈接
        new_client_socket, client_addr = tcp_service_socket.accept()
        print("鏈接的客戶端地址為:", client_addr)
        # 接收客戶端發送過來的請求
        recv_data = new_client_socket.recvfrom(1024)
        print(recv_data)
        # 給客戶端回送消息
        new_client_socket.send("hahahah".encode("utf-8"))
        # 關閉套接字
        new_client_socket.close()

    tcp_service_socket.close()


if __name__ == '__main__':
    main()

示例2-為同一用戶服務多次並判斷一個用戶是否服務完畢:

"""可以理解為銀行一個客服為排隊的人員辦理業務"""

import socket


def main():
    # 1.創建tcp套接字
    tcp_service_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2.綁定本地信息
    tcp_service_socket.bind(('', 8080))

    # 3.讓默認的套接字由主動變為被動
    tcp_service_socket.listen(128)
    while 1:
        # 4.等待客戶端的鏈接
        new_client_socket, client_addr = tcp_service_socket.accept()
        print("鏈接的客戶端地址為:", client_addr)
        # 循環目的:為同一個客戶服務多次
        while 1:
            # 接收客戶端發送過來的請求
            recv_data = new_client_socket.recvfrom(1024)
            print(recv_data)
            # 如果recv解堵塞,那么有兩種方式
            # 1.客戶端發了數據過來
            # 2.客戶端調用了close
            if recv_data:
                # 給客戶端回送消息
                new_client_socket.send("hahahah".encode("utf-8"))
            else:
                break
        # 關閉套接字
        new_client_socket.close()

    tcp_service_socket.close()


if __name__ == '__main__':
    main()

示例3-tcp文件下載客戶端和服務端:

文件下載客戶端

import socket


def main():
    # 1.創建套接字
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2.獲取服務器的ip,port
    dest_ip = input("請輸入你要鏈接的服務器ip:")
    dest_port = input("請輸入你要鏈接的端口:")
    # 3.鏈接服務器
    tcp_socket.connect((dest_ip, dest_port))

    # 4.獲取下載的文件名字
    want_file = input("請輸入你要下載的文件:")
    # 5.將文件名字發送到服務器
    tcp_socket.send(want_file.encode("utf-8"))

    # 6.接收要下載的文件
    file_data = tcp_socket.recv(1024)
    # 7.將接收文件的數據寫入一個文件中
    if file_data:
        with open("[復件]" + want_file, "wb") as f:
            f.write(file_data)

    # 8.關閉套接字
    tcp_socket.close()
    pass


if __name__ == '__main__':
    main()

文件下載服務端

import socket


def send_file2client(new_socket, client_addr):
    # 1.接受客戶端發送過來的 要下載的文件名
    want_file = new_socket.recv(1024).decode("utf-8")
    print("客戶端 %s 要接收的文件為:%s" % (str(client_addr), want_file))
    # 2.讀取文件數據
    file_data = None
    try:
        f = open(want_file, "rb")
        file_data = f.read()
        f.close()
    except Exception as e:
        print("你要下載的文件 %s 不存在" % want_file)

    # 3.發送文件的數據給客戶端
    if file_data:
        new_socket.send(file_data)


def main():
    # 1.創建套接字
    tcp_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
    # 2.綁定本地信息
    tcp_socket.bind(('', 8080))
    # 3.套接字被動接受 listen
    tcp_socket.listen(128)
    while 1:
        # 4.等待客戶端的鏈接 accept
        new_socket, client_addr = tcp_socket.accept()
        # 5.調用函數發送文件到客戶端
        send_file2client(new_socket, client_addr)
        # 7.關閉套接字
        new_socket.close()

    tcp_socket.close()


if __name__ == '__main__':
    main()


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM