1. 程式人生 > 實用技巧 >Python網路程式設計 —— socket(套接字)及通訊

Python網路程式設計 —— socket(套接字)及通訊

1、socket概念

​ Socket是應用層與TCP/IP協議族通訊的中間軟體抽象層,它是一組介面。在設計模式中,Socket其實就是一個門面模式,它把複雜的TCP/IP協議族隱藏在Socket介面後面,對使用者來說,一組簡單的介面就是全部,讓Socket去組織資料,以符合指定的協議。

其實可以認為,socket就是一個模組。我們通過呼叫該模組中已經實現的方法建立兩個程序之間的連線和通訊。
也可以將socket認為是ip+port,因為ip是用來標識網際網路中的一臺主機的位置,而port是用來標識這臺機器上的一個應用程式。
所以我們只要確立了ip和port就能找到一個應用程式,並且使用socket模組來與之通訊。

2、socket(套接字)的發展史及種類

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

基於檔案型別的套接字家族

套接字家族的名字:AF_UNIX

unix一切皆檔案,基於檔案的套接字呼叫的就是底層的檔案系統來取資料,兩個套接字程序執行在同一機器,可以通過訪問同一個檔案系統間接完成通訊。

基於網路型別的套接字家族

套接字家族的名字:AF_INET

(還有AF_INET6被用於ipv6,還有一些其他的地址家族,不過,他們要麼是隻用於某個平臺,要麼就是已經被廢棄,或者是很少被使用,或者是根本沒有實現,所有地址家族中,AF_INET是使用最廣泛的一個,python支援很多種地址家族,但對於網路程式設計而言,大部分時候只使用AF_INET)

3、TCP 協議和 UDP 協議

TCP(Transmission Control Protocol)可靠的、面向連線的協議、傳輸效率低全雙工通訊(傳送快取&接收快取)、面向位元組流。使用TCP的應用:Web瀏覽器;電子郵件、檔案傳輸程式。

UDP(User Datagram Protocol)不可靠的、無連線的服務,傳輸效率高(傳送前時延小),一對一、一對多、多對一、多對多、面向報文,盡最大努力服務,無擁塞控制。使用UDP的應用:域名系統 (DNS);視訊流;IP語音(VoIP)。

4、socket(套接字)初使用

(1)、基於TCP協議的socket

<1>、 伺服器端

import socket

sk = socket.socket()            # 建立一個伺服器的套接字
sk.bind(('127.0.0.1',9001))     # 繫結一個地址  IP +埠
sk.listen()                     # 開始監聽客戶端的請求
# 127.0.0.1 永遠標識本機地址
# 不過交換機的 可以在編寫程式碼的過程中排除一些網路問題

conn,addr = sk.accept()          # 接收一個連線請求
conn.send(b'hello')              # 向客戶端傳送資訊
msg = conn.recv(1024).decode('utf-8')    # 接收客戶端資訊,並限制了接收位元組的個數
print(msg)
conn.close()                             # 結束與對方的資訊傳遞

sk.close()                             # 關閉伺服器套接字

<2>、 客戶端

import socket

sk = socket.socket()                # 建立客戶套接字

sk.connect(('127.0.0.1',9001))      #  嘗試連線伺服器  IP +埠
msg = sk.recv(23)                    # 對話(傳送/接收)
print(msg)
sk.send('你好'.encode('utf-8'))
sk.close()                           # 關閉客戶套接字

注意:

​ TCP是基於連線的,必須先啟動伺服器端,然後在再啟動客戶端去連線服務端。

​ UDP是無連線的,啟動服務之後可以直接接受訊息,不需要提前建立連結。

# 在重啟服務端時有可能會遇到的報錯
OSError: [Error 48] Address already in use

# 解決辦法:加入一條socket配置,重用ip和埠
import socket
from socket import SOL_SOCKET,SO_REUSEADDR
sk = socket.socket()
sk.setsockopt(SOL_SOCKET,SO_REUSEADDR,1)  # 加入這個
sk.bind(('127.0.0.1',8898))  #把地址繫結到套接字
···

(2)、基於UDP協議的socket

<1>、 伺服器端

from socket import socket,SOCK_DGRAM

sk = socket(type=SOCK_DGRAM)        # 建立一個伺服器的套接字
sk.bind(('127.0.0.1',9001))         # 繫結伺服器套接字

while True:
    msg,cli_addr = sk.recvfrom(1024)     # 對話(接收與傳送)
    print(msg.decode('utf-8'))
    msg = input('>>>')
    if msg.upper() == 'Q':continue
    sk.sendto(msg.encode('utf-8'),cli_addr)

<2>、 客戶端

from socket import socket,SOCK_DGRAM

sk = socket(type=SOCK_DGRAM)
server_addr = ('127.0.0.1',9001)

while True:
    msg = input('>>>')
    if msg.upper() == 'Q':
        break
    sk.sendto(msg.encode('utf-8'),server_addr)
    msg = sk.recv(1024)
    print(msg.decode('utf-8'))

5、socket 詳解

socket.socket(family=AF_INET,type=SOCK_STREAM,proto=0,fileno=None)

<1>、 建立socket物件的引數說明:

family 地址系列應為AF_INET(預設值),AF_INET6,AF_UNIX,AF_CAN或AF_RDS。 (AF_UNIX 域實際上是使用本地 socket 檔案來通訊)
type 套接字型別應為SOCK_STREAM(預設值),SOCK_DGRAM,SOCK_RAW或其他SOCK_常量之一。 SOCK_STREAM 是基於TCP的,有保障的(即能保證資料正確傳送到對方)面向連線的SOCKET,多用於資料傳送。 SOCK_DGRAM 是基於UDP的,無保障的面向訊息的socket,多用於在網路上發廣播資訊。
proto 協議號通常為零,可以省略,或者在地址族為AF_CAN的情況下,協議應為CAN_RAW或CAN_BCM之一。
fileno 如果指定了fileno,則其他引數將被忽略,導致帶有指定檔案描述符的套接字返回。 與socket.fromfd()不同,fileno將返回相同的套接字,而不是重複的。 這可能有助於使用socket.close()關閉一個獨立的插座。

<2>、 socket的更多方法介紹

# 服務端套接字函式
s.bind()    # 繫結(主機,埠號)到套接字
s.listen()  # 開始TCP監聽
s.accept()  # 被動接受TCP客戶的連線,(阻塞式)等待連線的到來

# 客戶端套接字函式
s.connect()      # 主動初始化TCP伺服器連線
s.connect_ex()   # connect()函式的擴充套件版本,出錯時返回出錯碼,而不是丟擲異常

# 公共用途的套接字函式
s.recv()            # 接收TCP資料
s.send()            # 傳送TCP資料
s.sendall()         # 傳送TCP資料
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()        # 建立一個與該套接字相關的檔案