Python 套接字教程
Python Socket 教程 展示了如何使用套接字进行 Python 网络编程。 套接字编程是低级的,本教程的目的是介绍包括这些低级详细信息的网络编程。 有更高级的 Python API,例如 Twisted,可能更适合。
在编程中,套接字是网络上运行的两个程序之间通信的端点。 套接字用于在客户端程序和服务器程序之间创建连接。
Python 的socket
模块提供了与 Berkeley 套接字 API 的接口。
注意: 在网络中,术语socket有不同的含义,它用于IP地址和端口号的组合。
网络协议
TCP / IP 是设备用于在 Internet 和大多数本地网络上进行通信的一组协议。 TCP 更可靠,具有大量错误检查并需要更多资源。 HTTP,SMTP 或 FTP 等服务使用它。 UDP 的可靠性要差得多,错误检查的能力也有限,所需资源也更少。 VoIP 等服务使用它。
socket.SOCK_STREAM
用于为 TCP 创建套接字,而socket.SOCK_DGRAM
为 UDP 创建套接字。
地址族
创建套接字时,必须指定其地址族。 然后,我们只能在套接字中使用该类型的地址。
- AF_UNIX,AF_LOCAL-本地通讯
- AF_INET-IPv4 Internet 协议
- AF_INET6-IPv6 Internet 协议
- 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
| #!/usr/bin/env python
import socket
ip = socket.gethostbyname('example.com')
print(ip)
|
该示例打印example.com
的 IP 地址。
| $ ./get_ip.py
93.184.216.34
|
这是输出。
Python UDP 套接字示例
UDP 是一种通信协议,它通过网络传输独立的数据包,不保证到达且也不保证传递顺序。 使用 UDP 的一项服务是每日报价(QOTD)。
qotd_client.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 服务的客户端程序。
我们导入socket
模块。
| with socket.socket(socket.AF_INET, socket.SOCK_DGRAM) as s:
|
创建了用于 IPv4 的数据报套接字。
我们发送空消息; QOTD 服务通过向套接字发送任意数据来工作; 它只是用引号引起来。 为了通过 TCP / UDP 进行通信,我们使用二进制字符串。
| addr = ("djxmmx.net", 17)
|
我们提供地址和端口。
我们使用sendto()
方法发送数据。
| data, address = s.recvfrom(1024)
|
UDP 套接字使用recvfrom()
接收数据。 它的参数是缓冲区大小。 返回值是一对(数据,地址),其中数据是代表接收到的数据的字节字符串,而地址是发送数据的套接字的地址。
我们将解码后的数据打印到终端。
| $ ./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/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 套接字来确定当前时间。
| with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
创建用于 IPv4 的 TCP 套接字。
| host = "time.nist.gov"
port = 13
|
这是工作时间服务器的主机名和端口号。
我们使用connect()
连接到远程套接字。
sendall()
方法将数据发送到套接字。 套接字必须连接到远程套接字。 它继续从字节发送数据,直到发送完所有数据或发生错误为止。
| print(str(s.recv(4096), 'utf-8'))
|
我们打印接收到的数据。 recv()
方法从套接字接收最多 buffersize 个字节。 当没有数据可用时,它将阻塞,直到至少一个字节可用或直到远端关闭为止。 当远端关闭并读取所有数据时,它将返回一个空字节字符串。
Python 套接字 HEAD 请求
HEAD 请求是没有消息正文的 GET 请求。 请求/响应的标头包含元数据,例如 HTTP 协议版本或内容类型。
head_request.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 请求。
| 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 文档中描述了详细信息。
| $ 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
| #!/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
的主页。
| 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_server.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)
|
回显服务器将客户端消息发送回客户端。
| host = 'localhost'
port = 8001
|
服务器在端口 8001 上的 localhost 上运行。
bind()
方法建立通信端点。 它将套接字绑定到指定的地址。 套接字必须尚未绑定。 (地址的格式取决于地址系列。)
listen()
方法使服务器可以接受连接。 服务器现在可以侦听套接字上的连接。 listen()
具有backlog
参数。 它指定系统在拒绝新连接之前允许的不可接受的连接数。 自 Python 3.5 起,此参数是可选的。 如果未指定,则选择默认的积压值。
使用accept()
,服务器接受连接。 它阻止并等待传入连接。 套接字必须绑定到一个地址并监听连接。 返回值是一对(con,addr),其中 con 是可用于在连接上发送和接收数据的新套接字对象,而 addr 是绑定到连接另一端上的套接字的地址。
请注意,accept()
创建了一个新的套接字,用于与客户端进行通信,该套接字与侦听套接字不同。
echo_client.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
| #!/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()
|
现在,我们可以测试阻塞服务器和非阻塞服务器的性能。
| $ ab -c 50 -n 1000 http://localhost:8001/
|
例如,我们可以使用 Apache 基准测试工具测试性能。 在我们的例子中,命令发送 1000 个请求,一次发送 50 个。
在本教程中,我们展示了如何在 Python 中使用套接字创建简单的网络程序。