23.md 10.9 KB
Newer Older
W
wizardforcel 已提交
1 2 3 4
# Python 套接字教程

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

W
wizardforcel 已提交
5
Python 套接字教程展示了如何使用套接字进行 Python 网络编程。 套接字编程是低级的。 本教程的目的是介绍包括这些低级详细信息的网络编程。 有更高级的 Python API,例如 Twisted,可能更适合。
W
wizardforcel 已提交
6 7 8 9 10

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

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

W
wizardforcel 已提交
11
> **注意**:在网络中,“套接字”一词具有不同的含义。 它用于 IP 地址和端口号的组合。
W
wizardforcel 已提交
12 13 14

## 网络协议

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

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

W
wizardforcel 已提交
19
## 地址族
W
wizardforcel 已提交
20 21 22

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

W
wizardforcel 已提交
23 24 25 26 27 28
*   `AF_UNIX``AF_LOCAL` - 本地通讯
*   `AF_INET` - IPv4 互联网协议
*   `AF_INET6` - IPv6 互联网协议
*   `AF_IPX` - IPX-Novell 协议
*   `AF_BLUETOOTH` - 无线蓝牙协议
*   `AF_PACKET` - 底层数据包接口
W
wizardforcel 已提交
29

W
wizardforcel 已提交
30
对于`AF_INET`地址族,指定了一对(主机,端口)。 `host`是一个字符串,表示互联网域表示法中的主机名(如`example.com`)或 IPv4 地址(如`93.184.216.34`),并且`port`是整数。
W
wizardforcel 已提交
31 32 33 34 35 36 37

## Python 获取 IP 地址

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

`get_ip.py`

W
wizardforcel 已提交
38
```py
W
wizardforcel 已提交
39 40 41 42 43 44 45 46 47 48 49
#!/usr/bin/env python

import socket

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

```

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

W
wizardforcel 已提交
50
```py
W
wizardforcel 已提交
51 52 53 54 55 56 57 58 59 60 61 62 63
$ ./get_ip.py
93.184.216.34

```

这是输出。

## Python UDP 套接字示例

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

`qotd_client.py`

W
wizardforcel 已提交
64
```py
W
wizardforcel 已提交
65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82
#!/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 服务的客户端程序。

W
wizardforcel 已提交
83
```py
W
wizardforcel 已提交
84 85 86 87 88 89
import socket

```

我们导入`socket`模块。

W
wizardforcel 已提交
90
```py
W
wizardforcel 已提交
91 92 93 94 95 96
with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:

```

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

W
wizardforcel 已提交
97
```py
W
wizardforcel 已提交
98 99 100 101
message = b''

```

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

W
wizardforcel 已提交
104
```py
W
wizardforcel 已提交
105 106 107 108 109 110
addr = ("djxmmx.net", 17)

```

我们提供地址和端口。

W
wizardforcel 已提交
111
```py
W
wizardforcel 已提交
112 113 114 115 116 117
s.sendto(message, addr)

```

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

W
wizardforcel 已提交
118
```py
W
wizardforcel 已提交
119 120 121 122 123 124
data, address = s.recvfrom(1024)

```

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

W
wizardforcel 已提交
125
```py
W
wizardforcel 已提交
126 127 128 129 130 131
print(data.decode())

```

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

W
wizardforcel 已提交
132
```py
W
wizardforcel 已提交
133 134 135 136 137 138 139 140 141 142 143 144 145
$ ./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 套接字示例

是提供当前时间的服务器。 客户端无需任何命令即可直接连接到服务器,服务器以当前时间作为响应。

W
wizardforcel 已提交
146
> **注意**:时间服务器来来往往,因此我们可能需要在 <https://www.ntppool.org/en/> 上找到可用的服务器。
W
wizardforcel 已提交
147 148 149

`time_client.py`

W
wizardforcel 已提交
150
```py
W
wizardforcel 已提交
151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167
#!/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 套接字来确定当前时间。

W
wizardforcel 已提交
168
```py
W
wizardforcel 已提交
169 170 171 172 173 174
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:

```

创建用于 IPv4 的 TCP 套接字。

W
wizardforcel 已提交
175
```py
W
wizardforcel 已提交
176 177 178 179 180 181 182
host = "time.nist.gov"
port = 13

```

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

W
wizardforcel 已提交
183
```py
W
wizardforcel 已提交
184 185 186 187 188 189
s.connect((host, port))

```

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

W
wizardforcel 已提交
190
```py
W
wizardforcel 已提交
191 192 193 194 195 196
s.sendall(b'')

```

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

W
wizardforcel 已提交
197
```py
W
wizardforcel 已提交
198 199 200 201
print(str(s.recv(4096), 'utf-8'))

```

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

W
wizardforcel 已提交
204
## Python 套接字 HEAD 请求
W
wizardforcel 已提交
205 206 207 208 209

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

`head_request.py`

W
wizardforcel 已提交
210
```py
W
wizardforcel 已提交
211 212 213 214 215 216 217 218 219 220 221 222 223 224
#!/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 请求。

W
wizardforcel 已提交
225
```py
W
wizardforcel 已提交
226 227 228 229 230 231
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) 文档中描述了详细信息。

W
wizardforcel 已提交
232
```py
W
wizardforcel 已提交
233 234 235 236 237 238 239 240 241 242 243 244 245 246 247
$ 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

```

这是输出。

W
wizardforcel 已提交
248
## Python 套接字 GET 请求
W
wizardforcel 已提交
249 250 251 252 253

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

`get_request.py`

W
wizardforcel 已提交
254
```py
W
wizardforcel 已提交
255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276
#!/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`的主页。

W
wizardforcel 已提交
277
```py
W
wizardforcel 已提交
278 279 280 281 282 283
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`标头的原因。

W
wizardforcel 已提交
284
```py
W
wizardforcel 已提交
285 286 287 288 289 290 291 292 293 294 295
while True:

    data = s.recv(1024)

    if not data:
        break

    print(data.decode())

```

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

W
wizardforcel 已提交
298
```py
W
wizardforcel 已提交
299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340
$ ./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`

W
wizardforcel 已提交
341
```py
W
wizardforcel 已提交
342 343 344 345 346 347 348 349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373
#!/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)

```

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

W
wizardforcel 已提交
374
```py
W
wizardforcel 已提交
375 376 377 378 379 380 381
host = 'localhost'
port = 8001

```

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

W
wizardforcel 已提交
382
```py
W
wizardforcel 已提交
383 384 385 386 387 388
s.bind((host, port))

```

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

W
wizardforcel 已提交
389
```py
W
wizardforcel 已提交
390 391 392 393 394 395
s.listen()

```

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

W
wizardforcel 已提交
396
```py
W
wizardforcel 已提交
397 398 399 400
con, addr = s.accept()

```

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

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

`echo_client.py`

W
wizardforcel 已提交
407
```py
W
wizardforcel 已提交
408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430
#!/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`

W
wizardforcel 已提交
431
```py
W
wizardforcel 已提交
432 433 434 435 436 437 438 439 440 441 442 443 444 445 446 447 448 449 450 451 452
#!/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()

```

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

W
wizardforcel 已提交
453
```py
W
wizardforcel 已提交
454 455 456 457 458 459 460 461
$ ab -c 50 -n 1000 http://localhost:8001/

```

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

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

W
wizardforcel 已提交
462
您可能也对以下相关教程感兴趣: [Python 字符串](/lang/python/strings/)[Python Jinja 教程](/python/jinja/)[Python 教程](/lang/python/),或列出[所有 Python 教程](/all/#python)