ZetCode

Python Socket

最后修改于 2024 年 1 月 29 日

Python Socket 教程展示了如何使用套接字进行 Python 网络编程。套接字编程是低级的。本教程的目的是介绍网络编程,包括这些低级细节。存在更高级的 Python API,例如 Twisted,它可能更适合。

在编程中,套接字 是在网络上运行的两个程序之间通信的端点。套接字用于在客户端程序和服务器程序之间创建连接。

Python 的 socket 模块提供了对 Berkeley 套接字 API 的接口。

注意: 在网络中,套接字一词有不同的含义。它用于 IP 地址和端口号的组合。

网络协议

TCP/IP 是一系列协议,设备通过 Internet 和大多数本地网络进行通信时使用。TCP 更可靠,具有广泛的错误检查,并需要更多资源。HTTP、SMTP 或 FTP 等服务使用它。UDP 的可靠性差得多,错误检查有限,所需的资源也更少。VoIP 等服务使用它。

socket.SOCK_STREAM 用于创建 TCP 套接字,socket.SOCK_DGRAM 用于 UDP。

地址族

创建套接字时,我们必须指定其地址族。然后,我们只能将该类型的地址与套接字一起使用。

对于 AF_INET 地址族,指定了 (主机, 端口) 对。主机是一个字符串,表示 Internet 域名表示法中的主机名(如 example.com)或 IPv4 地址(如 93.184.216.34),端口是一个整数。

Python 获取 IP 地址

使用 gethostbyname,我们可以获取主机的 IP 地址。

get_ip.py
#!/usr/bin/python

import socket

ip = socket.gethostbyname('example.com')
print(ip)

该示例打印 example.com 的 IP 地址。

$ ./get_ip.py
93.184.216.34

Python UDP 套接字示例

UDP 是一种通信协议,它在网络上传输独立的包,不保证到达,也不保证传递顺序。Quote of the Day (QOTD) 是使用 UDP 的一项服务。

qotd_client.py
#!/usr/bin/python

import socket

with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:

    message = b''
    addr = ("djxmmx.net", 17)

    s.sendto(message, addr)

    data, address = s.recvfrom(1024)
    print(data.decode())

该示例创建了一个连接到 QOTD 服务的客户端程序。

import socket

我们导入 socket 模块。

with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:

创建一个用于 IPv4 的数据报套接字。

message = b''

我们发送一个空消息;QOTD 服务通过向套接字发送任意数据来工作;它只是用一个报价进行响应。为了通过 TCP/UDP 通信,我们使用二进制字符串。

addr = ("djxmmx.net", 17)

我们提供地址和端口。

s.sendto(message, addr)

我们使用 sendto 方法发送数据。

data, address = s.recvfrom(1024)

UDP 套接字使用 recvfrom 接收数据。它的参数是缓冲区大小。返回值是一对 (数据, 地址),其中数据是表示接收到的数据的字节字符串,地址是发送数据的套接字地址。

print(data.decode())

我们将解码后的数据打印到终端。

$ ./qotd_client.py
"Oh the nerves, the nerves; the mysteries of this machine called man!
    Oh the little that unhinges it, poor creatures that we are!"
    Charles Dickens (1812-70)

Python TCP 套接字示例

有一些提供当前时间的服务器。客户端只需连接到服务器而不发送任何命令,服务器就会响应当前时间。

注意: 时间服务器会时有时无,因此我们可能需要从 https://www.ntppool.org/en/ 查找一个可用的服务器。

time_client.py
#!/usr/bin/python

import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:

    host = "time.nist.gov"
    port = 13

    s.connect((host, port))
    s.sendall(b'')
    print(str(s.recv(4096), 'utf-8'))

该示例通过连接到时间服务器的 TCP 套接字来确定当前时间。

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:

创建一个用于 IPv4 的 TCP 套接字。

host = "time.nist.gov"
port = 13

这是可用时间服务器的主机名和端口号。

s.connect((host, port))

我们使用 connect 连接到远程套接字。

s.sendall(b'')

sendall 方法将数据发送到套接字。套接字必须已连接到远程套接字。它会继续发送字节数据,直到所有数据都已发送或发生错误。

print(str(s.recv(4096), 'utf-8'))

我们打印接收到的数据。recv 方法从套接字接收最多缓冲区大小的字节。当没有可用数据时,它会阻塞直到至少有一个字节可用或远程端关闭。当远程端关闭并且所有数据都已读取后,它返回一个空字节字符串。

Python Socket HEAD 请求

HEAD 请求是 GET 请求,但没有消息体。请求/响应的头部包含元数据,例如 HTTP 协议版本或内容类型。

head_request.py
#!/usr/bin/python

import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:

    s.connect(("webcode.me" , 80))
    s.sendall(b"HEAD / HTTP/1.1\r\nHost: webcode.me\r\nAccept: text/html\r\n\r\n")
    print(str(s.recv(1024), 'utf-8'))

在此示例中,我们向 webcode.me 发送了一个 HEAD 请求。

s.sendall(b"HEAD / HTTP/1.1\r\nHost: webcode.me\r\nAccept: text/html\r\n\r\n")

HEAD 请求是使用 HEAD 命令后跟资源 URL 和 HTTP 协议版本发出的。请注意,\r\n 是通信过程中强制要求的部分。详细信息在 RFC 7231 文档中有描述。

$ head_request.py
HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Sun, 08 Sep 2019 11:23:25 GMT
Content-Type: text/html
Content-Length: 348
Last-Modified: Sat, 20 Jul 2019 11:49:25 GMT
Connection: keep-alive
ETag: "5d32ffc5-15c"
Accept-Ranges: bytes

Python Socket GET 请求

HTTP GET 方法请求指定资源的表示。使用 GET 的请求应该只检索数据。

get_request.py
#!/usr/bin/python

import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:

    s.connect(("webcode.me" , 80))
    s.sendall(b"GET / HTTP/1.1\r\nHost: webcode.me\r\nAccept: text/html\r\nConnection: close\r\n\r\n")

    while True:

        data = s.recv(1024)

        if not data:
            break

        print(data.decode())

该示例使用 GET 请求读取 webcode.me 的主页。

s.sendall(b"GET / HTTP/1.1\r\nHost: webcode.me\r\nAccept: text/html\r\nConnection: close\r\n\r\n")

对于 HTTP 1.1 协议,连接默认可能是持久的。这就是为什么我们发送 Connection: close 头部。

while True:

    data = s.recv(1024)

    if not data:
        break

    print(data.decode())

我们使用 while 循环来处理接收到的数据。如果没有发生错误,recv 将返回接收到的字节。如果连接已正常关闭,返回值将是一个空字节字符串。recv 是一个阻塞方法,它会一直阻塞直到完成,或者达到超时时间,或者发生其他异常。

$ ./get_request.py
HTTP/1.1 200 OK
Server: nginx/1.6.2
Date: Sun, 08 Sep 2019 11:39:34 GMT
Content-Type: text/html
Content-Length: 348
Last-Modified: Sat, 20 Jul 2019 11:49:25 GMT
Connection: keep-alive
ETag: "5d32ffc5-15c"
Access-Control-Allow-Origin: *
Accept-Ranges: bytes

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>My html page</title>
</head>
<body>

    <p>
        Today is a beautiful day. We go swimming and fishing.
    </p>

    <p>
         Hello there. How are you?
    </p>

</body>
</html>

Echo 客户端服务器示例

Echo 服务器将客户端的消息发送回来。这是一个用于测试和学习的经典示例。

echo_server.py
#!/usr/bin/python

import socket
import time

with socket.socket() as s:

    host = 'localhost'
    port = 8001

    s.bind((host, port))
    print(f'socket binded to {port}')

    s.listen()

    con, addr = s.accept()

    with con:

        while True:

            data = con.recv(1024)

            if not data:
                break

            con.sendall(data)

Echo 服务器将客户端消息发回给客户端。

host = 'localhost'
port = 8001

服务器在 localhost 的 8001 端口上运行。

s.bind((host, port))

bind 方法建立通信端点。它将套接字绑定到指定的地址。套接字不能已绑定。(地址的格式取决于地址族。)

s.listen()

listen 方法使服务器能够接受连接。服务器现在可以在套接字上监听连接。listen 有一个 backlog 参数。它指定系统在拒绝新连接之前允许的未接受连接的数量。从 Python 3.5 开始,此参数是可选的。如果未指定,则选择默认的 backlog 值。

con, addr = s.accept()

使用 accept,服务器接受连接。它会阻塞并等待传入的连接。套接字必须绑定到一个地址并监听连接。返回值是一对 (con, addr),其中 con 是一个可用于在连接上发送和接收数据的新套接字对象,addr 是绑定到连接另一端套接字上的地址。

请注意,accept 创建了一个用于与客户端通信的新套接字,它与监听套接字是不同的套接字。

echo_client.py
#!/usr/bin/python

import socket

with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:

    host = "localhost"
    port = 8001

    s.connect((host, port))
    s.sendall(b'hello there')
    print(str(s.recv(4096), 'utf-8'))

客户端将消息发送到 Echo 服务器。

异步服务器示例

为了提高服务器的性能,我们可以使用 asyncio 模块。

async_server.py
#!/usr/bin/python

# from threading import current_thread

import asyncio


async def handle_client(reader, writer):

    data = (await reader.read(1024))

    writer.write(data)
    writer.close()


loop = asyncio.get_event_loop()
loop.create_task(asyncio.start_server(handle_client, 'localhost', 8001))
loop.run_forever()

我们现在可以测试阻塞和非阻塞服务器的性能。

$ ab -c 50 -n 1000 https://:8001/

例如,我们可以使用 Apache 基准测试工具来测试性能。在本例中,该命令发送 1000 个请求,每次 50 个。

来源

Python socket — 低级网络接口

在本文中,我们展示了如何使用 Python 中的套接字创建简单的网络程序。

作者

我叫 Jan Bodnar,是一名充满热情的程序员,拥有丰富的编程经验。自 2007 年以来,我一直撰写编程文章。迄今为止,我已撰写了 1,400 多篇文章和 8 本电子书。我在编程教学方面拥有十多年的经验。

列出所有 Python 教程