Python Socket 網絡通信詳解

socket介紹

Python 提供了兩個基本的 socket 模塊:

  • Socket 它提供了標準的BSD Socket API。
  • SocketServer 它提供了服務器重心,可以簡化網絡服務器的開發。

Socket 類型

套接字格式:socket(family, type[,protocal]) 使用給定的套接族,套接字類型,協議編號(默認爲0)來創建套接字。

socket 類型 描述
socket.AF_UNIX 用於同一臺機器上的進程通信(既本機通信)
socket.AF_INET 用於服務器與服務器之間的網絡通信
socket.AF_INET6 基於IPV6方式的服務器與服務器之間的網絡通信
socket.SOCK_STREAM 基於TCP的流式socket通信
socket.SOCK_DGRAM 基於UDP的數據報式socket通信
socket.SOCK_RAW 原始套接字,普通的套接字無法處理ICMP、IGMP等網絡報文,而SOCK_RAW可以;其次SOCK_RAW也可以處理特殊的IPV4報文;此外,利用原始套接字,可以通過IP_HDRINCL套接字選項由用戶構造IP頭
socket.SOCK_SEQPACKET 可靠的連續數據包服務

創建TCP Socket:

sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)

創建UDP Socket:

sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)

Socket 函數

  • TCP發送數據時,已建立好TCP鏈接,所以不需要指定地址,而UDP是面向無連接的,每次發送都需要指定發送給誰。
  • 服務器與客戶端不能直接發送列表,元素,字典等帶有數據類型的格式,發送的內容必須是字符串數據。

服務器端 Socket 函數

Socket 函數 描述
s.bind(address) 將套接字綁定到地址,在AF_INET下,以tuple(host, port)的方式傳入,如s.bind((host, port))
s.listen(backlog) 開始監聽TCP傳入連接,backlog指定在拒絕鏈接前,操作系統可以掛起的最大連接數,該值最少爲1,大部分應用程序設爲5就夠用了
s.accept() 接受TCP鏈接並返回(conn, address),其中conn是新的套接字對象,可以用來接收和發送數據,address是鏈接客戶端的地址。

客戶端 Socket 函數

Socket 函數 描述
s.connect(address) 鏈接到address處的套接字,一般address的格式爲tuple(host, port),如果鏈接出錯,則返回socket.error錯誤
s.connect_ex(address) 功能與s.connect(address)相同,但成功返回0,失敗返回errno的值

公共 Socket 函數

Socket 函數 描述
s.recv(bufsize[, flag]) 接受TCP套接字的數據,數據以字符串形式返回,buffsize指定要接受的最大數據量,flag提供有關消息的其他信息,通常可以忽略
s.send(string[, flag]) 發送TCP數據,將字符串中的數據發送到鏈接的套接字,返回值是要發送的字節數量,該數量可能小於string的字節大小
s.sendall(string[, flag]) 完整發送TCP數據,將字符串中的數據發送到鏈接的套接字,但在返回之前嘗試發送所有數據。成功返回None,失敗則拋出異常
s.recvfrom(bufsize[, flag]) 接受UDP套接字的數據u,與recv()類似,但返回值是tuple(data, address)。其中data是包含接受數據的字符串,address是發送數據的套接字地址
s.sendto(string[, flag], address) 發送UDP數據,將數據發送到套接字,address形式爲tuple(ipaddr, port),指定遠程地址發送,返回值是發送的字節數
s.close() 關閉套接字
s.getpeername() 返回套接字的遠程地址,返回值通常是一個tuple(ipaddr, port)
s.getsockname() 返回套接字自己的地址,返回值通常是一個tuple(ipaddr, port)
s.setsockopt(level, optname, value) 設置給定套接字選項的值
s.getsockopt(level, optname[, buflen]) 返回套接字選項的值
s.settimeout(timeout) 設置套接字操作的超時時間,timeout是一個浮點數,單位是秒,值爲None則表示永遠不會超時。一般超時期應在剛創建套接字時設置,因爲他們可能用於連接的操作,如s.connect()
s.gettimeout() 返回當前超時值,單位是秒,如果沒有設置超時則返回None
s.fileno() 返回套接字的文件描述
s.setblocking(flag) 如果flag爲0,則將套接字設置爲非阻塞模式,否則將套接字設置爲阻塞模式(默認值)。非阻塞模式下,如果調用recv()沒有發現任何數據,或send()調用無法立即發送數據,那麼將引起socket.error異常。
s.makefile() 創建一個與該套接字相關的文件

在這裏插入圖片描述

Socket 編程思想

操作ip

127.0.01 本地測試ip
0.0.0.0  局域網可用ip
192.168.1.0 網段
192.168.1.1  網關
192.168.1.255 廣播地址
In [1]: import socket
#獲取主機名
In [2]: socket.gethostname()
Out[2]: 'localhost.localdomain'
# 通過主機名解析ip
In [3]: socket.gethostbyname('localhost.localdomain')
Out[3]: '127.0.0.1'

In [4]: socket.gethostbyname('localhost')
Out[4]: '127.0.0.1'

In [3]: socket.gethostbyname('www.baidu.com')
Out[3]: '180.101.49.11'

#十進制轉換二進制
In [5]: socket.inet_aton('192.168.1.190')
Out[5]: '\xc0\xa8\x01\xbe'

#二進制轉換十進制
In [8]: socket.inet_ntoa('\xc0\xa8\x01\xbe')
Out[8]: '192.168.1.190'

#十進制轉換二進制
In [11]: socket.inet_pton(socket.AF_INET, '192.168.1.190')
Out[11]: '\xc0\xa8\x01\xbe'

#二進制轉換十進制
In [15]: socket.inet_ntop(socket.AF_INET, '\xc0\xa8\x01\xbe')
Out[15]: '192.168.1.190'

操作端口

應用層端口:1–65535
衆所周知的服務端口:1–255
系統端口:256–1023

In [16]: socket.getservbyname('mysql')
Out[16]: 3306

In [17]: socket.getservbyname('http')
Out[17]: 80

TCP 服務器

1、創建套接字,綁定套接字到本地IP與端口

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind()

2、開始監聽鏈接

s.listen()

3、進入循環,不斷接受客戶端的鏈接請求

While True:
    s.accept()

4、接收客戶端傳來的數據,並且發送給對方發送數據

s.recv()
s.sendall()

5、傳輸完畢後,關閉套接字

s.close()

TCP 客戶端

1、創建套接字並鏈接至遠端地址

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect()

2、鏈接後發送數據和接收數據

s.sendall()
s.recv()

3、傳輸完畢後,關閉套接字

練習

示例1

服務器端代碼

import socket

HOST = '192.168.1.100'
PORT = 8001

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.bind((HOST, PORT))
s.listen(5)

print 'Server start at: %s:%s' %(HOST, PORT)
print 'wait for connection...'

while True:
    conn, addr = s.accept()
    print 'Connected by ', addr

    while True:
        data = conn.recv(1024)
        print data

        conn.send("server received you message.")

# conn.close()

客戶端代碼

import socket
HOST = '192.168.1.100'
PORT = 8001

s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.connect((HOST, PORT))

while True:
    cmd = raw_input("Please input msg:")
    s.send(cmd)
    data = s.recv(1024)
    print data

    #s.close()
$ python soc2.py
Please input msg:ls
server received you message.
Please input msg:hello
server received you message.
$ python test1.py
Server start at: 192.168.211.15:80
wait for connection...
Connected by  ('192.168.211.15', 44079)
ls
hello

示例2

打印程序服務端

server2.py

#!/usr/bin/env python3

import socket

HOST = '127.0.0.1'  # 標準的迴環地址 (localhost)
PORT = 65432        # 監聽的端口 (非系統級的端口: 大於 1023)

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.bind((HOST, PORT))
    s.listen()
    conn, addr = s.accept()
    with conn:
        print('Connected by', addr)
        while True:
            data = conn.recv(1024)
            if not data:
                break
            conn.sendall(data)

解說:

  • socket.socket() 創建了一個 socket 對象,並且支持 context manager type,你可以使用 with語句,這樣你就不用再手動調用 s.close() 來關閉 socket 了。
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    pass  # Use the socket object without calling s.close().
  • bind() 用來關聯 socket 到指定的網絡接口(IP 地址)和端口號,bind() 方法的入參取決於 socket地址族,在這個例子中我們使用了 socket.AF_INET (IPv4),它將返回兩個元素的元組:(host, port)。host可以是主機名稱、IP 地址、空字符串,如果使用 IP 地址,host 就應該是 IPv4 格式的字符串,127.0.0.1 是標準的IPv4 迴環地址,只有主機上的進程可以連接到服務器,如果你傳了空字符串,服務器將接受本機所有可用的 IPv4 地址 端口號應該是1-65535 之間的整數(0是保留的),這個整數就是用來接受客戶端鏈接的 TCP 端口號,如果端口號小於1024,有的操作系統會要求管理員權限。

  • listen() 方法調用使服務器可以接受連接請求,這使它成爲一個「監聽中」的 socket
    listen() 方法有一個 backlog 參數。它指定在拒絕新的連接之前系統將允許使用的 未接受的連接 數量。從 Python 3.5 開始,這是可選參數。如果不指定,Python 將取一個默認值。如果你的服務器需要同時接收很多連接請求,增加 backlog 參數的值可以加大等待鏈接請求隊列的長度,最大長度取決於操作系統。比如在 Linux 下,參考 /proc/sys/net/core/somaxconn

  • accept() 方法阻塞並等待傳入連接。當一個客戶端連接時,它將返回一個新的 socket 對象,對象中有表示當前連接的 conn和一個由主機、端口號組成的 IPv4/v6 連接的元組,這裏必須要明白我們通過調用 accept() 方法擁有了一個新的 socket對象。這非常重要,因爲你將用這個 socket 對象和客戶端進行通信。和監聽一個 socket 不同的是後者只用來授受新的連接請求。

  • accept() 獲取客戶端 socket 連接對象 conn 後,使用一個無限 while 循環來阻塞調用conn.recv(),無論客戶端傳過來什麼數據都會使用 conn.sendall() 打印出來,如果 conn.recv()方法返回一個空 byte 對象(b’’),然後客戶端關閉連接,循環結束,with 語句和 conn 一起使用時,通信結束的時候會自動關閉socket 鏈接。

打印程序客戶端

#!/usr/bin/env python3

import socket

HOST = '127.0.0.1'  # 服務器的主機名或者 IP 地址
PORT = 65432        # 服務器使用的端口

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
    s.connect((HOST, PORT))
    s.sendall(b'Hello, world')
    data = s.recv(1024)

print('Received', repr(data))

運行:
第一步

$ python3.8 server1.py

第二步:

$ python3.8 client2.py 
Received b'Hello, world'

第三步

$ python3.8 server1.py
Connected by ('127.0.0.1', 47816)

參考資料:
Python 中的 Socket 編程(指南)
Python Socket 編程詳細介紹

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