一、初識socket socket(套接字)起源於20世紀70年代加利福尼亞大學伯克利分校版本的Unix,即人們所說的BSDUnix。因此,有時人們也把套接字稱為“伯克利套接字”或“BSD套接字”。一開始,套接字被設計用在同一臺主機上多個應用程式之間的通訊。這也被稱進程間通訊,或IPC。socket ...
一、初識socket
socket(套接字)起源於20世紀70年代加利福尼亞大學伯克利分校版本的Unix,即人們所說的BSDUnix。因此,有時人們也把套接字稱為“伯克利套接字”或“BSD套接字”。一開始,套接字被設計用在同一臺主機上多個應用程式之間的通訊。這也被稱進程間通訊,或IPC。socket(套接字)也可用在相同或者不同的設備進程之間進行通信。
套接字(socket)是一個抽象層,應用程式可以通過它發送或接收數據,可對其進行像對文件一樣的打開、讀寫和關閉等操作。套接字允許應用程式將I/O插入到網路中,並與網路中的其他應用程式進行通信。網路套接字是IP地址與埠的組合。
為了滿足不同的通信程式對通信質量和性能的要求,一般的網路系統提供了三種不同類型的套接字,以供用戶在設計網路應用程式時根據不同的要求來選擇。這三種套接為流式套接字(SOCK-STREAM)、數據報套接字(SOCK-DGRAM)和原始套接字(SOCK-RAW)。
流式套接字:它提供了一種可靠的、面向連接的雙向數據傳輸服務,實現了數據無差錯、無重覆的發送。流式套接字內設流量控制,被傳輸的數據看作是無記錄邊界的位元組流。在TCP/IP協議簇中,使用TCP協議來實現位元組流的傳輸,當用戶想要發送大批量的數據或者對數據傳輸有較高的要求時,可以使用流式套接字。
數據報套接字:它提供了一種無連接、不可靠的雙向數據傳輸服務。數據包以獨立的形式被髮送,並且保留了記錄邊界,不提供可靠性保證。數據在傳輸過程中可能會丟失或重覆,並且不能保證在接收端按發送順序接收數據。在TCP/IP協議簇中,使用UDP協議來實現數據報套接字。在出現差錯的可能性較小或允許部分傳輸出錯的應用場合,可以使用數據報套接字進行數據傳輸,這樣通信的效率較高。
原始套接字:該套接字允許對較低層協議(如IP或ICMP)進行直接訪問,常用於網路協議分析,檢驗新的網路協議實現,也可用於測試新配置或安裝的網路設備。
軟體開發架構一般分為C/S和B/S兩種。
- C/S:Client與Server ,中文意思:客戶端與伺服器端架構
- B/S:Browser與Server,中文意思:瀏覽器端與伺服器端架構
二、socket.socket()模塊簡介
socket.socket(family=AF_INET, type=SOCK_STREAM, proto=0, fileno=None)
初始化參數:
family:套接字有很多家族,AF_INET,AF_UNIX ,AF_IRDA,等多種,我們先學習AF_INET,預設是AF_INET。
AF_UNIX:是基於文件類型的套接字家族,unix一切皆文件,基於文件的套接字調用的就是底層的文件系統來取數據,兩個套接字進程運行在同一機器,可以通過訪問同一個文件系統間接完成通信。
AF_INET:是基於網路類型的套接字家族(還有AF_INET6被用於ipv6,AF_INET是使用最廣泛的一個,python支持很多種地址家族,但是由於我們只關心網路編程,所以大部分時候我們只使用AF_INET)
type:套接字類型有如下幾種:預設是SOCK_STREAM。
- SOCK_STREAM:TCP流(常用)
- SOCK_DGRAM¶:UDP數據報(常用)
- SOCK_RAW:原始套接字(常用)
- SOCK_RDM:保證交付數據報但不保證順序。
- SOCK_SEQPACKET:可靠的連續數據包服務
proto:埠號通常為0,這樣socket會隨機使用一個沒被占用的埠,在能確定埠沒被占用的情況下可以手動指定埠號,在地址家族為AF_CAN的情況下,協議應該是CAN_RAW、CAN_BCM或CAN_ISOTP之一。
fileno:從指定的文件描述符中自動檢測family,type和proto的值。預設是None。
三、socket object常用方法:
socket.bind(address):將socket綁定一個地址,這個socket必須沒有綁定過。
socket.listen([backlog]):socket伺服器進入監聽模式以接受連接。如果指定了backlog這個值最少為0。backlog代表沒有被 accept 取走的連接數量。如果未指定,系統會選擇預設的合理值。
socket.accept():接受一個連接。socket必須綁定到一個地址並監聽連接。返回值是一個元組(conn,address),其中conn是一個新的套接字對象,用於在連接上發送和接收數據,address是綁定到連接另一端套接字的地址。
socket.recv(bufsize):接收發送過來的數據。 返回值是一個位元組對象,一次接收的最大數據量由bufsize指定。為了與硬體和網路實際情況保持最佳匹配,bufsize的值應為2的次冪,例如1024,2047,4096等。
socket.recvfrom(bufsize):接收數據。返回值是一個元組(位元組,地址),其中位元組是一個位元組對象,表示接收到的數據,地址是發送數據的socket的地址。
socket.recvmsg(bufsize,ancbufsize,):從套接字接收數據和輔助數據。bufsize(位元組)接收數據的大小ancbufsize參數設置用於接收輔助數據的內部緩衝區的大小(以位元組為單位)。它預設為0,表示將不會接收任何輔助數據。可以使用CMSG_SPACE()或CMSG_LEN()計算輔助數據的適當緩衝區大小,不適合緩衝區的項目可能會被截斷或丟棄。返回值是一個四元組:(數據,ancdata,msg_flags,address)。數據項是一個位元組對象,ancdata項是零個或多個元組(cmsg_level,cmsg_type,cmsg_data)的列表,表示接收到的輔助數據(控制消息):cmsg_level和cmsg_type是分別指定協議級別和協議特定類型的整數,而cmsg_data是位元組對象保存相關數據。 msg_flags項是指示接收消息條件的各種標誌的按位或;有關詳細信息,請參見系統文檔。如果接收套接字未連接,則address是發送套接字的地址(如果有);否則,其值未指定。
socket.send(bytes):將數據發送到socket。該socket必須連接到遠程socket。返回發送的位元組數。
socket.sendall(bytes):與send()類似不同的是此方法繼續從位元組發送數據,直到所有數據都已發送或發生錯誤為止。 成功不返回任何內容。 如果出錯,則會引發異常,所以無法確定成功發送了多少數據。
socket.sendto(bytes,address):將數據發送到socket。不應連接到遠程socket,因為目標socket是按地址指定的。返回發送的位元組數。
socket.connect(address):根據地址連接遠程socket。
socket.fileno():返回socket的文件描述符(一個小整數),如果失敗則返回-1。這對於select.select()非常有用。在Windows下,這個方法返回的小整數不能用於可以使用文件描述符的地方(例如os.fdopen())。Unix沒有這個限制。
socket.getpeername():返回socket連接到的遠程地址,埠號。在某些系統上,可能不支持此功能。
socket.getsockname():返回自己socket的地址和埠號。
socket.getblocking():如果套接字處於阻塞模式,則返回True;如果處於非阻塞模式,則返回False。適用於Python3.7。
socket.shutdown(how):關閉連接。如果how=SHUT_RD則不允許接收數據。如果how=SHUT_WR則不允許發送數據。如果how=SHUT_RDWR如何,則不允許發送和接收。
socket.close():釋放與連接關聯的資源,但不一定立即關閉連接,套接字對象上的所有後續操作都將失敗。遠程端將不再接收任何數據。如果希望及時關閉連接,在close()之前調用shutdown()。
socket.detach():關閉socket對象而不,而不實際關閉底層文件描述符。返迴文件描述符,此調用後無法使用socket對象,但可以使用文件描述符,用於其他目的。
socket.settimeout(value):設置socket阻塞模式下超時時間。value值是非負數(單位:秒),如果給出了value,在value秒後還沒有接收到數據,連接請求,將引發超時異常。如果給定0,則套接字將處於非阻塞模式。如果是None,套接字將進入阻塞模式。
參考文檔: https://docs.python.org/3/library/socket.html?highlight=socket#socket.AF_INET。
四、簡單示例
建立基本的連接
server端:
import socket server_obj = socket.socket() # 創建socket對象 server_obj.bind(("socket伺服器地址",9000)) # socket綁定IP地址和監聽埠 server_obj.listen(5) # 監聽遠程socket連接 con,addr = server_obj.accept() # 建立socket連接 msg = con.recv(1024).decode("utf-8") # 接收遠程socket發來的數據 print(msg) con.send(msg.upper().encode("utf-8")) # 發送數據給遠程socket con.close() # 關閉連接 server_obj.close() # 關閉連接
client端:
import socket client = socket.socket() # 創建socket對象 client.connect(("遠程socket地址",9000)) # 連接遠程socket msg = input(">>>") client.send(msg.encode("utf-8")) # 發送數據給遠程socket recv_msg = client.recv(1024).decode("utf-8") # 接收遠程socket發來的數據 print(recv_msg) client.close() # 關閉socket
上面的示例只能收一次,發一次消息,然後就結束了。下麵我們使用while迴圈,來迴圈收,發消息。
server端:
import socket server_obj = socket.socket() # 創建socket對象 server_obj.bind(("192.168.10.102",9000)) # socket綁定地址和埠 server_obj.listen(5) # 監聽socket連接請求 con,addr = server_obj.accept() # 建立socket連接 print('遠端socket對象:',con.getpeername()) # 列印遠程socket信息 while True: try: msg = con.recv(1024) # 接收遠程socket發來的數據 if msg.decode('utf-8').upper()=='Q':break # 如果發來的是q表示斷開連接 print(msg.decode("utf-8")) con.send('我接到了你發來的消息'.encode('utf-8')) except Exception: break print(連接已斷開) con.close() server_obj.close()
client端:
import socket client = socket.socket() # 創建socket對象 client.connect(("192.168.10.102",9000)) # 連接遠程socket while True: msg = input(">>>") if msg.upper() == 'Q':break # 輸入q退出程式 client.send(msg.encode("utf-8")) # 發送數據給遠程socket recv_msg = client.recv(1024).decode("utf-8") # 接收遠程socket發來的數據 print(recv_msg) print('退出程式') client.close()
此時我們的伺服器只能接受一個客戶端的連接,如果客戶端斷開了,服務端將關閉。如果想讓伺服器一直接受請求一個客戶端斷開連接後,繼續接受下一個客戶端的連接,看如下server代碼:
import socket server_obj = socket.socket() # 創建socket對象 server_obj.bind(("192.168.10.102",9000)) # socket綁定地址和埠 server_obj.listen(5) # 監聽socket連接請求 while 1: print('等待接收遠程socket連接......') con,addr = server_obj.accept() # 建立socket連接 print('連接一臺遠程socket:',con.getpeername()) while True: try: msg = con.recv(1024) # 接收遠程socke if msg.decode('utf-8').upper()=='Q':break print(msg.decode("utf-8")) con.send('我接到了你發來的消息'.encode('utf-8')) except Exception: break con_address,con_port = con.getpeername() # 獲取斷開socke對象信息 print(con_address,'斷開了連接') con.close() server_obj.close()
關於recv要註意的地方:
當緩衝區沒有數據可取時,recv會一直處於阻塞狀態,直到緩衝區至少有一個位元組數據可取,或者遠程端關閉。關閉遠程端並讀取所有數據後,返回空字元串。