socket也叫套接字,是對各種協議的封裝,實現收發數據。
Python裏socket工作過程:(圖片來自網絡)
socket在Python中實際上是一個模塊,實現發送和接收數據的功能。
因爲socket是一個類,所以只導入模塊需要使用socket.socket()創建一個socket對象。
創建一個socket格式:
socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
參數名 | 選項名稱 | 作用 |
family | AF_UNIX | unix系統進程間傳輸數據 |
AF_INET | IPv4網絡傳輸數據 | |
AF_INET6 | IPv6網絡傳輸數據 | |
type | SOCK_STREAM | 流式數據,TCP |
SOCK_DGRAM | 數據報式數據,UDP | |
SOCK_RAW | 原始套接字,普通的套接字無法處理ICMP、IGMP等網絡報文,而SOCK_RAW可以;其次,SOCK_RAW也可以處理特殊的IPv4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由用戶構造IP頭。 | |
SOCK_RDM | 是一種可靠的UDP形式,即保證交付數據報但不保證順序。SOCK_RAM用來提供對原始協議的低級訪問,在需要執行某些特殊操作時使用,如發送ICMP報文。SOCK_RAM通常僅限於高級用戶或管理員運行的程序使用。 | |
SOCK_SEQPACKET | 連續的數據包傳輸(已廢棄) | |
proto | 0 | 默認是0,根據地址簇和套接類別自動選擇合適的協議 |
fileno | 默認是None | If fileno is specified, the other arguments are ignored, causing the socket with the specified file descriptor to return. Unlike socket.fromfd(), fileno will return the same socket and not a duplicate. This may help close a detached socket using socket.close(). |
socket對象的方法:
1、socket分爲服務端和客戶端。
2、TCP傳輸不需要IP,UDP傳輸需要IP地址。
3、socket傳輸字符串需要變成byte型。
4、列表、字典等數據也需要成變byte型。json處理過的數據是字符型的,decode後可以進行send。
5、傳輸大數據,使用長度時,要注意len的對象是原數據,還是encode後的數據,接收方也得計算相應的數據。否則會造成文件長度不匹配
方法名 | 對象 | 作用 |
bind(地址) | 服務端 | 綁定服務端地址,IPv4下,是元組的形式(地址,端口) |
listen(backlog) | 服務端 | 設定客戶端連接數量,數字 |
accept() | 服務端 | 完整的接收信息: (<socket.socket fd=316, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=0, laddr=('127.0.0.1', 6666), raddr=('127.0.0.1', 58775)>, ('127.0.0.1', 58775)) <>部分是套接字信息 後面元組是客端地址。 |
connect(地址) | 客戶端 | 綁定服務端地址,IPv4下,是元組的形式(地址,端口) |
connect_ex() | 客戶端 | 功能與connect相同,但是成功返回0,失敗返回errno的值。 |
s.recv(bufsize[,flag]) | 服務和客戶端 | 接受TCP套接字的數據。數據以字符串形式返回,bufsize指定要接收的最大數據量。flag提供有關消息的其他信息,通常可以忽略。 bufsize官方建議8192,不同系統最大數值不同,一般一次可以收10M左右。 |
s.send(string[,flag]) | 發送TCP數據。將string中的數據發送到連接的套接字。返回值是要發送的字節數量,該數量可能小於string的字節大小。 | |
s.sendall(string[,flag]) | 完整發送TCP數據。將string中的數據發送到連接的套接字,但在返回之前會嘗試發送所有數據。成功返回None,失敗則拋出異常。 | |
s.recvfrom(bufsize[.flag]) | 接受UDP套接字的數據。與recv()類似,但返回值是(data,address)。其中data是包含接收數據的字符串,address是發送數據的套接字地址。 | |
s.sendto(string[,flag],address) | 發送UDP數據。將數據發送到套接字,address是形式爲(ipaddr,port)的元組,指定遠程地址。返回值是發送的字節數。 | |
s.close() | 關閉套接字。 | |
s.getpeername() | 返回連接套接字的遠程地址。返回值通常是元組(ipaddr,port)。 | |
s.getsockname() | 返回套接字自己的地址。通常是一個元組(ipaddr,port) | |
s.setsockopt(level,optname,value) | 設置給定套接字選項的值。 | |
s.getsockopt(level,optname[.buflen]) | 返回套接字選項的值。 | |
s.settimeout(timeout) | 設置套接字操作的超時期,timeout是一個浮點數,單位是秒。值爲None表示沒有超時期。一般,超時期應該在剛創建套接字時設置,因爲它們可能用於連接的操作(如 client 連接最多等待5s ) | |
sk.fileno() | 套接字的文件描述符 |
服務端連接實例:
import socket server = socket.socket() server.bind("localhost",6666) # localhost是本地主機,也可以寫172.0.0.1 server.listen(6) # 同時允許5個客戶端 while True: # 此位置的while是爲了客戶端結束後,再等待其它客戶端進入。 conn,addr = server.accept() # 接收一套接字信息,和地址。對應的是客戶端的connect while True: conn.recv(1024) # 服務端先接收數據,可以改變每次接收的數值,但是不要小於客戶端發送的值。 conn.send(b'00000') # 字符前面加r,變成字節數據,纔可以傳輸 # 這裏就是互相通信的主體,可以有多個recv和send,需要注意的是,一收一發,要和客戶端對應 # 服務端和客戶端不能同時收或同時發。 break # 結束此客戶端,繼續listen其它客戶端
客戶端連接實例:
import socket client = socket.socket() # 創建套接字對象 client.connect(('localhost',6666)) # 連接的主機名和端 口,也可以是字符串的ip地址 "127.0.0.1" while True: client.send(b'11111') client.recv(1024) # 這裏就是互相通信的主體,可以有多個recv和send,需要注意的是,一收一發,要和客戶端對應 break client.close() # 客戶端關閉連接。
簡單FTP製作的問題點
json.decoder.JSONDecodeError: Extra data:
因爲傳輸的過程中有二進制數據,所以json無法decode。
傳輸文件完成時怎麼返回?
客戶端都一收一發。並且在傳輸個列表,第一項是標誌,第二項是True,當兩項不匹配時,提示錯誤,並返回選項列表。
有時候服務器運行程序時間長,沒有到接收語句,而客戶端發送數據太快,導致出錯
在客戶端加個sleep...
logging使用filehandler中文亂碼
創建filehandler時,寫入encode參數
file_handler = logging.FileHandler(log_path,encoding='utf-8')
調用logging模塊,重複輸出
1、使用removeHandler()把這個logger裏的handler移除掉
2、在log方法裏做判斷,如果這個logger已有handler,則不再添加handler。
字典、列表無法傳輸
使用json序列化後傳輸。json序列化後原來是字節的!
“粘包”:A給B連續發送兩個send,B接收到的兩個數據都混在一起,分不清第一次還是第二次接收的
原因是,A發送時存在緩存區,大約0.5S後緩存消失
如果想要分開兩次的數據,A使用send後立即執行recv,接收B的send,然後再發送第二句。
'''更好的解決粘包''' # recv_file_size 已經接收的大小 # file_size 文件總大小 # base_recv 每次接收的大小 base_recv = 1024 recv_file_size = 0 while recv_file_size<file_size: data = conn.recv(base_recv).decode() if file_size-recv_file_size < base_recv: base_recv = file_size-recv_file_size recv_file_size =+ len(data)