# Python 套接字教程

> 原文： [http://zetcode.com/python/socket/](http://zetcode.com/python/socket/)

Python 套接字教程展示了如何使用套接字进行 Python 网络编程。 套接字编程是低级的。 本教程的目的是介绍包括这些低级详细信息的网络编程。 有更高级的 Python API，例如 Twisted，可能更适合。

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

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

> **注意**：在网络中，“套接字”一词具有不同的含义。 它用于 IP 地址和端口号的组合。

## 网络协议

TCP/IP 是设备用于在互联网和大多数本地网络上进行通信的一组协议。 TCP 更可靠，具有大量错误检查并需要更多资源。 HTTP，SMTP 或 FTP 等服务使用它。 UDP 的可靠性要差得多，错误检查的能力也有限，所需资源也更少。 VoIP 等服务使用它。

`socket.SOCK_STREAM`用于为 TCP 创建套接字，而`socket.SOCK_DGRAM`为 UDP 创建套接字。

## 地址族

创建套接字时，必须指定其地址族。 然后，我们只能在套接字中使用该类型的地址。

*   `AF_UNIX`，`AF_LOCAL` - 本地通讯
*   `AF_INET` - IPv4 互联网协议
*   `AF_INET6` - IPv6 互联网协议
*   `AF_IPX` - IPX-Novell 协议
*   `AF_BLUETOOTH` - 无线蓝牙协议
*   `AF_PACKET` - 底层数据包接口

对于`AF_INET`地址族，指定了一对（主机，端口）。 `host`是一个字符串，表示互联网域表示法中的主机名（如`example.com`）或 IPv4 地址（如`93.184.216.34`），并且`port`是整数。

## Python 获取 IP 地址

使用`gethostbyname()`，我们获得主机的 IP 地址。

`get_ip.py`

```py
#!/usr/bin/env python

import socket

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

```

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

```py
$ ./get_ip.py
93.184.216.34

```

这是输出。

## Python UDP 套接字示例

UDP 是一种通信协议，它通过网络传输独立的数据包，不保证到达且也不保证传递顺序。 使用 UDP 的一项服务是每日报价（QOTD）。

`qotd_client.py`

```py
#!/usr/bin/env 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 服务的客户端程序。

```py
import socket

```

我们导入`socket`模块。

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

```

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

```py
message = b''

```

我们发送空消息； QOTD 服务通过向套接字发送任意数据来工作； 它只是用引号引起来。 为了通过 TCP/UDP 进行通信，我们使用二进制字符串。

```py
addr = ("djxmmx.net", 17)

```

我们提供地址和端口。

```py
s.sendto(message, addr)

```

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

```py
data, address = s.recvfrom(1024)

```

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

```py
print(data.decode())

```

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

```py
$ ./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`

```py
#!/usr/bin/env 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 套接字来确定当前时间。

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

```

创建用于 IPv4 的 TCP 套接字。

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

```

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

```py
s.connect((host, port))

```

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

```py
s.sendall(b'')

```

`sendall()`方法将数据发送到套接字。 套接字必须连接到远程套接字。 它继续从字节发送数据，直到发送完所有数据或发生错误为止。

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

```

我们打印接收到的数据。 `recv()`方法从套接字接收最多`buffersize`个字节。 当没有数据可用时，它将阻塞，直到至少一个字节可用或直到远端关闭为止。 当远端关闭并读取所有数据时，它将返回一个空字节字符串。

## Python 套接字 HEAD 请求

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

`head_request.py`

```py
#!/usr/bin/env 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 请求。

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

```

用`HEAD`命令发出头请求，后跟资源 URL 和 HTTP 协议版本。 请注意，`\r\n`是通信过程中必不可少的部分。 在 [RFC 7231](https://tools.ietf.org/html/rfc7231) 文档中描述了详细信息。

```py
$ 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 套接字 GET 请求

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

`get_request.py`

```py
#!/usr/bin/env 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`的主页。

```py
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`标头的原因。

```py
while True:

    data = s.recv(1024)

    if not data:
        break

    print(data.decode())

```

我们使用`while`循环来处理接收到的数据。 如果没有错误发生，则`recv()`返回接收到的字节。 如果连接已正常关闭，则返回值为空字节字符串。 `recv()`是一种阻止方法，直到完成它，或者达到超时或发生另一个异常为止。

```py
$ ./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_server.py`

```py
#!/usr/bin/env 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)

```

回显服务器将客户端消息发送回客户端。

```py
host = 'localhost'
port = 8001

```

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

```py
s.bind((host, port))

```

`bind()`方法建立通信端点。 它将套接字绑定到指定的地址。 套接字必须尚未绑定。 （地址的格式取决于地址系列。）

```py
s.listen()

```

`listen()`方法使服务器可以接受连接。 服务器现在可以监听套接字上的连接。 `listen()`具有`backlog`参数。 它指定系统在拒绝新连接之前允许的不可接受的连接数。 自 Python 3.5 起，此参数是可选的。 如果未指定，则选择默认的积压值。

```py
con, addr = s.accept()

```

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

请注意，`accept()`创建了一个新的套接字，用于与客户端进行通信，该套接字与监听套接字不同。

`echo_client.py`

```py
#!/usr/bin/env 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'))

```

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

## 异步服务器示例

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

`async_server.py`

```py
#!/usr/bin/env 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()

```

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

```py
$ ab -c 50 -n 1000 http://localhost:8001/

```

例如，我们可以使用 Apache 基准测试工具测试性能。 在我们的例子中，命令发送 1000 个请求，一次发送 50 个。

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

您可能也对以下相关教程感兴趣： [Python 字符串](/lang/python/strings/)， [Python Jinja 教程](/python/jinja/)和 [Python 教程](/lang/python/)，或列出[所有 Python 教程](/all/#python)。