Python網絡編程入門篇

下面是小凰凰的簡介,看下吧!
💗人生態度:珍惜時間,渴望學習,熱愛音樂,把握命運,享受生活
💗學習技能:網絡 -> 雲計算運維 -> python全棧( 當前正在學習中)
💗您的點贊、收藏、關注是對博主創作的最大鼓勵,在此謝過!
有相關技能問題可以寫在下方評論區,我們一起學習,一起進步。
後期會不斷更新python全棧學習筆記,秉着質量博文爲原則,寫好每一篇博文。

一、兩種軟件架構

一個是C/S架構,一個是B/S架構!

1. C/S架構:Client/Server架構,我們平常手機中使用的app、電腦中的QQ音樂等軟件這些都是C/S架構
2. B/S架構:Browser/Server架構,我們進入瀏覽器裏的瀏覽的網頁哪些全部都是B/S架構。

C/S架構與socket的關係:socket實現C/S架構的Client和Server端的數據交互的,注意:B/S架構是不需要socket的。

二、socket層

socket層是對傳輸層、網絡層、數據鏈路層、物理層的一個抽象,它將四層的封包、解包等所有操作都做了,我們只需要把數據給交給socket,指明收件人的地址(做些配置)即可發送或接收數據。
在這裏插入圖片描述

三、什麼是socket?

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

所以,我們無需深入理解tcp/udp協議,socket已經爲我們封裝好了,我們只需要遵循socket的規定去編程,寫出的程序自然就是遵循tcp/udp標準的。

也有人將socket說成ip+port,ip是用來標識互聯網中的一臺主機的位置,而port是用來標識這臺機器上的一個應用程序,ip地址是配置到網卡上的,而port是應用程序開啓的,ip與port的綁定就標識了互聯網中獨一無二的一個應用程序

而程序的pid是同一臺機器上不同進程或者線程的標識,

'爲什麼不用ip+pid標示應用程序?'
答:首先你要知道程序的端口專門用來傳輸、接收數據的,進程是用來傳輸數據的嗎?而且一個應用程序會有很多個進程號,當然一個應用程序的主進程一般只有一個,會有很多子進程。

四、套接字發展史及分類

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

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

套接字家族的名字:AF_UNIX
unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,可以通過訪問同一個文件系統間接完成通信

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

套接字家族的名字:AF_INET
(還有AF_INET6被用於ipv6,還有一些其他的地址家族,不過,他們要麼是隻用於某個平臺,要麼就是已經被廢棄,或者是很少被使用,或者是根本沒有實現,所有地址家族中,AF_INET是使用最廣泛的一個,python支持很多種地址家族,但是由於我們只關心網絡編程,所以大部分時候我麼只使用AF_INET)

五、套接字工作流程

1、tcp套接字工作流程圖(僞代碼)

在這裏插入圖片描述

2、socket模塊用法

import socket
socket.socket(socket_family,socket_type,protocal=0)
socket_family 可以是 AF_UNIX 或 AF_INET。socket_type 可以是 SOCK_STREAM 或 SOCK_DGRAM。protocol 一般不填,默認值爲 0# 獲取tcp/ip套接字
tcpSock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

# 獲取udp/ip套接字
udpSock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

# 由於 socket 模塊中有太多的屬性。我們在這裏破例使用了'from module import *'語句。使用 'from socket import *',我們就把 socket 模塊裏的所有屬性都帶到我們的命名空間裏了,這樣能 大幅減短我們的代碼。
例如:tcpSock = socket(AF_INET, SOCK_STREAM)
(1)服務端套接字函數
s.bind()    綁定(主機,端口號)到套接字
s.listen()  開始TCP監聽
s.accept()  被動接受TCP客戶的連接,(阻塞式)等待連接的到來
(2)客戶端套接字函數
s.connect()     主動初始化TCP服務器連接
s.connect_ex()  connect()函數的擴展版本,出錯時返回出錯碼,而不是拋出異常
(3)公共用途的套接字函數
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()           關閉套接字
(4)面向鎖的套接字方法
s.setblocking()     設置套接字的阻塞與非阻塞模式
s.settimeout()      設置阻塞套接字操作的超時時間
s.gettimeout()      得到阻塞套接字操作的超時時間
(5)面向文件的套接字的函數
s.fileno()          套接字的文件描述符
s.makefile()        創建一個與該套接字相關的文件

六、基於TCP的套接字

tcp是面向連接的,必須先啓動服務端,然後再啓動客戶端去連接服務端

1、代碼實現TCP套接字通信

# tcp服務端
from socket import *
server = socket(AF_INET, SOCK_STREAM) # 創建服務器套接字
server.bind(('127.0.0.1',8000)) # 把地址、端口綁定到套接字,注意裏面是個元組
server.listen(5)      # 監聽連接,並將連接請求放入一個大小爲5的半連接池
while True:  # 連接循環,一般來說服務器7*24小時都應該等待客戶端連接,因此這裏無限循環,每次通信循環結束(一個客戶端數據交互完成),accept又去半連接池拿到一個連接,服務下一個客戶端
	conn,addr = server.accept() # 從半連接池中選擇最早來的一個連接,接受連接
    while True:         # 通信循環,和客戶端可以一直通訊,不能說一句話就掛機了啊!
    	conn.recv()/conn.send() # 對話(接收與發送)
    	if 收到的信息長度爲0: # 收到的信息長度爲0,則說明客戶端已經斷開連接
    		break
    conn.close()    # 關閉客戶端套接字
server.close()    # 關閉服務器套接字(可選),一般不要
# tcp客戶端
from socket import *
client = socket(AF_INET, SOCK_STREAM)    # 創建客戶套接字
client.connect(('127.0.0.1',8000))    # 請求連接服務器
while True:        # 通訊循環
	msg = input('請輸入您要執行的命令 >>>').strip()
    if msg == '': # 如果你什麼命令都沒輸,就回車了,這種情況必須處理,不能把空數據發到服務端,不然服務端接收到空數據,以爲你掛機了,這樣服務端會結束和你的通信
        continue
    if msg == 'exit': # 表示你想結束通信了!
        break # break之後就可以執行close操作了!
	conn.send()/conn.recv() # 對話(發送與接收)
client.close()

2、代碼報錯分析

在這裏插入圖片描述這個是由於你的服務端仍然存在四次揮手的time_wait狀態在佔用地址(如果不懂,請深入研究1.tcp三次握手,四次揮手 2.syn洪水攻擊 3.服務器高併發情況下會有大量的time_wait狀態的優化方法

#加入一條socket配置,重用ip和端口

phone=socket(AF_INET,SOCK_STREAM)
phone.setsockopt(SOL_SOCKET,SO_REUSEADDR,1) #就是它,在bind前加
phone.bind(('127.0.0.1',8080))

# 發現系統存在大量TIME_WAIT狀態的連接,通過調整linux內核參數解決,
vi /etc/sysctl.conf

編輯文件,加入以下內容:
net.ipv4.tcp_syncookies = 1
net.ipv4.tcp_tw_reuse = 1
net.ipv4.tcp_tw_recycle = 1
net.ipv4.tcp_fin_timeout = 30
 
然後執行 /sbin/sysctl -p 讓參數生效。
 
net.ipv4.tcp_syncookies = 1 表示開啓SYN Cookies。當出現SYN等待隊列溢出時,啓用cookies來處理,可防範少量SYN攻擊,默認爲0,表示關閉;

net.ipv4.tcp_tw_reuse = 1 表示開啓重用。允許將TIME-WAIT sockets重新用於新的TCP連接,默認爲0,表示關閉;

net.ipv4.tcp_tw_recycle = 1 表示開啓TCP連接中TIME-WAIT sockets的快速回收,默認爲0,表示關閉。

net.ipv4.tcp_fin_timeout 修改系統默認的 TIMEOUT 時間
複製代碼

linux內核參數優化詳解請見:https://blog.csdn.net/weixin_44571270/article/details/104879180

3、實戰演練(實現遠程控制雲服務器)

(1)源代碼

這裏我不用from導寫的,初學者建議不要使用from導socket模塊,熟練後可以使用from導

# settings.py
IP_PORT = ('127.0.0.1',8082) # 如果遠程控制雲服務器,請改成公網ip+port,且安全組開放指定的端口
READ_SIZE = 2048
# tcp服務端
import subprocess
import socket
import settings
server = socket.socket(socket.AF_INET,socket.SOCK_STREAM) # 不用from導,可以明確看出AF_INET這些屬性都是模塊的,而不是socket類的。
server.bind(settings.IP_PORT)
server.listen(5)
while True:
    conn,addr = server.accept()
    while True:
        msg = conn.recv(settings.READ_SIZE)
        if len(msg) == 0:
            print('客戶端正常close,也會發送空數據給服務端!')
            break
        msg = msg.decode('utf-8')
        obj = subprocess.Popen(msg,shell=True,
                         stdout=subprocess.PIPE,
                         stderr=subprocess.PIPE)
        std_msg = obj.stdout.read()
        err_msg = obj.stderr.read()
        msg = std_msg + err_msg
        conn.send(msg)
    conn.close()
# tcp客戶端
import socket
import settings
client = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
client.connect(settings.IP_PORT)
while True:
    msg = input('請輸入您要執行的命令 >>>').strip()
    if msg == '':
        continue
    if msg == 'exit':
        break
    client.send(msg.encode('utf-8'))
    feedback = client.recv(settings.READ_SIZE)
    print(feedback.decode('utf-8'))
client.close()
(2)執行結果

在這裏插入圖片描述

(3)此程序的不足之處
1. 如果命令得到的輸出太大,會造成read一次2048字節,讀不完,當前命令得到數據不完整,下次命令執行,就會出現粘包現象,這個後面我會解決!
2. 只能執行ps -ef、ls、ifconfig這種命令,無法像真正的遠程控制軟件,執行vim、cd等特殊命令,cd命令還好解決,vim這種交互式命令,目前我還沒想到解決方法,只能等後面解決了。

七、基於UDP的套接字

udp是面向無連接的,先啓動哪一端都不會報錯

1、UDP套接字簡單示例

# udp服務端
import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
server=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
server.bind(ip_port)
while True:
    msg,addr=server.recvfrom(BUFSIZE) # 因爲面向無連接,所以只管循環:收和回數據
    print(msg,addr) # b'hahaha' ('127.0.0.1', 58146)
    server.sendto(msg.upper(),addr) # 因爲面向無連接,因此,你服務端發送也要帶着客戶端ip和port
# udp客戶端
import socket
ip_port=('127.0.0.1',9000)
BUFSIZE=1024
client=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
while True:
    msg=input('>>: ').strip()
    if not msg:
    	continue
    client.sendto(msg.encode('utf-8'),ip_port) # 因爲面向無連接,所以send必須把ip和port的元組帶上

    back_msg,addr=client.recvfrom(BUFSIZE)
    print(back_msg.decode('utf-8'),addr) # HAHAHA ('127.0.0.1', 9000)

2、實戰演練(聊天室)

由於技術原因,此實戰項目目前完成的功能太菜!由於用戶ip和端口在不斷變化,因此,因此賬戶名需要不斷更改綁定的IP加端口。感覺不好。因此等到合適的機會再寫這個程序吧!

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章