日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程语言 > python >内容正文

python

Python网络编程之socket编程

發布時間:2025/3/15 python 13 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Python网络编程之socket编程 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

什么是Socket?

Socket是應用層與TCP/IP協議族通信的中間軟件抽象層,它是一組接口。在設計模式中,Socket其實就是一個門面模式,它把復雜的TCP/IP協議族隱藏在Socket接口后面,對用戶來說,一組簡單的接口就是全部,讓Socket去組織數據,以符合指定的協議。
TCP連接的端點是由一個IP地址和一個PORT來唯一標識的。IP是用來標識互聯網中的一臺主機的位置,而PORT是用來標識這臺機器上的一個應用程序,IP地址是配置到網卡上的,而PORT是應用程序開啟的。

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

Socket分類

套接字有兩種(或者稱為有兩個種族),分別是基于文件型的和基于網絡型的。

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

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

套接字工作流程


先從服務器端說起。服務器端先初始化Socket,然后與端口綁定(bind),對端口進行監聽(listen),調用accept阻塞,等待客戶端連接。在這時如果有個客戶端初始化一個Socket,然后連接服務器(connect),如果連接成功,這時客戶端與服務器端的連接就建立了。客戶端發送數據請求,服務器端接收請求并處理請求,然后把回應數據發送給客戶端,客戶端讀取數據,最后關閉連接,一次交互結束。

Socket模塊用法

import socketsocket_server = 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)

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

基于TCP的套接字編程

tcp是基于鏈接的,必須先啟動服務端,然后再啟動客戶端去鏈接服務端

服務端開啟

import sockettcpsocket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 創建socket對象 tcpsocket.bind(('127.0.0.1', 8080)) # 把地址綁定到套接字 tcpsocket.listen(5) # 監聽連接,相當于一個連接池 while True:# 服務器無限連接循環conn, addr = tcpsocket.accept()# 接收客戶端連接print(conn, addr)while True:# 通訊循環msg = conn.recv(1024)# 對話接收if len(msg) == 0:breakprint(msg.decode('utf8'), type(msg))conn.send(msg.upper())# 對話發送conn.close()# 關閉客戶端套接字(這是個系統資源占用) tcpsocket.close() # 關閉服務端套接字

客戶端開啟

import socketclient_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) client_socket.connect_ex(('127.0.0.1', 8080)) # connect_ex()出錯時返回出錯碼,而不是拋出異常flag = Truewhile flag:msg = input('請輸入》》》').strip()if len(msg) == 0:continueif msg == 'q':flag = Falseclient_socket.send(bytes(msg, encoding='utf8'))feed_back = client_socket.recv(1024)print(feed_back.decode('utf8')) client_socket.close()

擴展學習:
TCP的三次握手四次揮手
SYN洪水攻擊
服務器高并發情況下會有大量的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 時間

基于UDP的套接字編程

UDP是無鏈接的,先啟動哪一端都不會報錯
通常UDP會用在客戶端向服務端申請一個比特的信息,如果沒有收到答復繼續申請。
用到UDP最廣的是DNS系統,因為客戶端通常只需要發送簡短請求,并收到簡短恢復,UDP非常適合這種操作。
UDP的限制是一個信息包不超過64KB的數據,通常人們只用UDP發送1KB以下的數據。

服務端開啟

import socketudp_socket = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) udp_socket.bind(('127.0.0.1', 8080))while True:msg, addr = udp_socket.recvfrom(1024)# addr是一個元組,第一個元素是ip,第二個元素是portprint(msg.decode('utf8'), addr)udp_socket.sendto(msg.upper(), addr)

客戶端開啟

import socketip_port = ('127.0.0.1', 8080) udp_client = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) while True:msg = input("請輸入>>>").strip()if not msg:continueudp_client.sendto(bytes(msg, encoding='utf8'), ip_port)back_msg, addr= udp_client.recvfrom(1024)print(back_msg.decode('utf8'), addr)

粘包問題

什么是粘包?

知識儲備:Socket收發消息原理

發送端可以是1K1K地發送數據,而接收端的應用程序可以2K2K地提走數據,當然也有可能一次提走3K或6K數據,或者一次只提走幾個字節的數據,也就是說,應用程序所看到的數據是一個整體,或說是一個流(stream),一條消息有多少字節對應用程序是不可見的,因此TCP協議是面向流的協議,這也是容易出現粘包問題的原因。而UDP是面向消息的協議,每個UDP段都是一條消息,應用程序必須以消息為單位提取數據,不能一次提取任意字節的數據,這一點和TCP是很不同的。怎樣定義消息呢?可以認為對方一次性write/send的數據為一個消息,需要明白的是當對方send一條信息的時候,無論底層怎樣分段分片,TCP協議層會把構成整條消息的數據段排序完成后才呈現在內核緩沖區。

例如基于tcp的套接字客戶端往服務端上傳文件,發送時文件內容是按照一段一段的字節流發送的,在接收方看了,根本不知道該文件的字節流從何處開始,在何處結束

所謂粘包問題主要還是因為接收方不知道消息之間的界限,不知道一次性提取多少字節的數據所造成的。

此外,發送方引起的粘包是由TCP協議本身造成的,TCP為提高傳輸效率,發送方往往要收集到足夠多的數據后才發送一個TCP段。若連續幾次需要send的數據都很少,通常TCP會根據優化算法(Nagle,將數據量小并且時間間隔短的數據一次打包發給接收端)把這些數據合成一個TCP段后一次發送出去,這樣接收方就收到了粘包數據。

  • TCP(transport control protocol,傳輸控制協議)是面向連接的,面向流的,提供高可靠性服務。收發兩端(客戶端和服務器端)都要有一一成對的socket,因此,發送端為了將多個發往接收端的包,更有效的發到對方,使用了優化方法(Nagle算法),將多次間隔較小且數據量小的數據,合并成一個大的數據塊,然后進行封包。這樣,接收端,就難于分辨出來了,必須提供科學的拆包機制。 即面向流的通信是無消息保護邊界的。
  • UDP(user datagram protocol,用戶數據報協議)是無連接的,面向消息的,提供高效率服務。不會使用塊的合并優化算法,, 由于UDP支持的是一對多的模式,所以接收端的skbuff(套接字緩沖區)采用了鏈式結構來記錄每一個到達的UDP包,在每個UDP包中就有了消息頭(消息來源地址,端口等信息),這樣,對于接收端來說,就容易進行區分處理了。 即面向消息的通信是有消息保護邊界的。
  • tcp是基于數據流的,于是收發的消息不能為空,這就需要在客戶端和服務端都添加空消息的處理機制,防止程序卡住,而udp是基于數據報的,即便是你輸入的是空內容(直接回車),那也不是空消息,udp協議會幫你封裝上消息頭,實驗略
    udp的recvfrom是阻塞的,一個recvfrom(x)必須對唯一一個sendinto(y),收完了x個字節的數據就算完成,若是y>x數據就丟失,這意味著udp根本不會粘包,但是會丟數據,不可靠

tcp的協議數據不會丟,沒有收完包,下次接收,會繼續上次繼續接收,己端總是在收到ack時才會清除緩沖區內容。數據是可靠的,但是會粘包。

會出現粘包的情況

第一種:發送端需要等緩沖區滿才發送出去,造成粘包(發送數據時間間隔很短,數據了很小,會合到一起,產生粘包)
第二種:接收方不及時接收緩沖區的包,造成多個包接收(客戶端發送了一段數據,服務端只收了一小部分,服務端下次再收的時候還是從緩沖區拿上次遺留的數據,產生粘包)

拆包發生的情況

當發送端緩沖區的長度大于網卡的MTU時,tcp會將這次發送的數據拆成幾個數據包發送出去。

send(字節流)和recv(1024)及sendall

recv里指定的1024意思是從緩存里一次拿出1024個字節的數據
send的字節流是先放入己端緩存,然后由協議控制將緩存內容發往對端,如果待發送的字節流大小大于緩存剩余空間,那么數據丟失,用sendall就會循環調用send,數據不會丟失

TCP粘包制作

服務端開啟

from socket import * import subprocessip_port = ('127.0.0.1', 8080) BUFSIZE = 1024tcp_server = socket(AF_INET, SOCK_STREAM) tcp_server.bind(ip_port) tcp_server.listen(5)while True:conn, addr = tcp_server.accept()print(conn)while True:cmd = conn.recv(BUFSIZE)if len(cmd) == 0:breakres = subprocess.Popen(cmd.decode('utf8'),shell=True,stdout=subprocess.PIPE,stdin=subprocess.PIPE,stderr=subprocess.PIPE)stderr = res.stderr.read()stdout = res.stdout.read()conn.send(stderr)conn.send(stdout)

TCP客戶端制作

from socket import *ip_port = ('127.0.0.1', 8080) BUFSIZE = 1024 tcp_client = socket(AF_INET, SOCK_STREAM) res = tcp_client.connect_ex(ip_port)while True:msg = input("請輸入>>>").strip()if len(msg) == 0:continueif msg == 'quit':breaktcp_client.send(msg.encode('utf-8'))act_res = tcp_client.recv(BUFSIZE)print(act_res.decode('gbk'))

解決粘包問題


為字節流加上自定義固定長度報頭,報頭中包含字節流長度,然后一次send到對端,對端在接收時,先從緩存中取出定長的報頭,然后再取真實數據。
struct模塊 可以把一個類型,如數字,轉成固定長度的bytes。

import json,struct #假設通過客戶端上傳1T:1073741824000的文件a.txt#為避免粘包,必須自定制報頭 header={'file_size':1073741824000,'file_name':'/a/b/c/d/e/a.txt','md5':'8f6fbf8347faa4924a76856701edb0f3'} #1T數據,文件路徑和md5值#為了該報頭能傳送,需要序列化并且轉為bytes head_bytes=bytes(json.dumps(header),encoding='utf-8') #序列化并轉成bytes,用于傳輸#為了讓客戶端知道報頭的長度,用struck將報頭長度這個數字轉成固定長度:4個字節 head_len_bytes=struct.pack('i',len(head_bytes)) #這4個字節里只包含了一個數字,該數字是報頭的長度#客戶端開始發送 conn.send(head_len_bytes) #先發報頭的長度,4個bytes conn.send(head_bytes) #再發報頭的字節格式 conn.sendall(文件內容) #然后發真實內容的字節格式#服務端開始接收 head_len_bytes=s.recv(4) #先收報頭4個bytes,得到報頭長度的字節格式 x=struct.unpack('i',head_len_bytes)[0] #提取報頭的長度head_bytes=s.recv(x) #按照報頭長度x,收取報頭的bytes格式 header=json.loads(json.dumps(header)) #提取報頭#最后根據報頭的內容提取真實的數據,比如 real_data_len=s.recv(header['file_size']) s.recv(real_data_len)

我們可以把報頭做成字典,字典里包含將要發送的真實數據的詳細信息,然后json序列化,然后用struck將序列化后的數據長度打包成4個字節(4個自己足夠用了)

發送時:
先發報頭長度
再編碼報頭內容然后發送
最后發真實內容

接收時:
先手報頭長度,用struct取出來
根據取出的長度收取報頭內容,然后解碼,反序列化
從反序列化的結果中取出待取數據的詳細信息,然后去取真實的數據內容

第一版服務端開啟

import socket,struct,json import subprocess phone=socket.socket(socket.AF_INET,socket.SOCK_STREAM) phone.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1) #就是它,在bind前加phone.bind(('127.0.0.1',8080))phone.listen(5)while True:conn,addr=phone.accept()while True:cmd=conn.recv(1024)if not cmd:breakprint('cmd: %s' %cmd)res=subprocess.Popen(cmd.decode('utf-8'),shell=True,stdout=subprocess.PIPE,stderr=subprocess.PIPE)err=res.stderr.read()print(err)if err:back_msg=errelse:back_msg=res.stdout.read()headers={'data_size':len(back_msg)}head_json=json.dumps(headers)head_json_bytes=bytes(head_json,encoding='utf-8')conn.send(struct.pack('i',len(head_json_bytes))) #先發報頭的長度conn.send(head_json_bytes) #再發報頭conn.sendall(back_msg) #在發真實的內容conn.close()

第一版客戶端開啟

from socket import * import struct,jsonip_port=('127.0.0.1',8080) client=socket(AF_INET,SOCK_STREAM) client.connect(ip_port)while True:cmd=input('>>: ')if not cmd:continueclient.send(bytes(cmd,encoding='utf-8'))head=client.recv(4)head_json_len=struct.unpack('i',head)[0]head_json=json.loads(client.recv(head_json_len).decode('utf-8'))data_len=head_json['data_size']recv_size=0recv_data=b''while recv_size < data_len:recv_data+=client.recv(1024)recv_size+=len(recv_data)print(recv_data.decode('utf-8'))#print(recv_data.decode('gbk')) #windows默認gbk編碼

轉載于:https://www.cnblogs.com/qiaoqianshitou/p/9670676.html

總結

以上是生活随笔為你收集整理的Python网络编程之socket编程的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。