Python套接字教程展示了如何使用套接字进行Python网络编程。套接字编程是低级的。本教程的目标是介绍网络编程,包括这些底层细节。有更高级别的PythonAPI,例如Twisted,可能更适合。
在编程中,套接字是网络上运行的两个程序之间通信的端点。套接字用于在客户端程序和服务器程序之间创建连接。
Python的socket
模块提供了BerkeleysocketsAPI的接口。
网络协议
TCP/IP是设备用来通过Internet和大多数本地网络进行通信的一套协议。TCP更可靠,具有广泛的错误检查,并且需要更多资源。它由HTTP、SMTP或FTP等服务使用。UDP的可靠性低得多,错误检查有限,并且需要的资源较少。它被VoIP等服务使用。
socket.SOCK_STREAM
用于为TCP创建套接字,为UDP创建socket.SOCK_DGRAM
。
地址族
当我们创建一个套接字时,我们必须指定它的地址族。然后我们只能在套接字中使用该类型的地址。
- AF_UNIX、AF_LOCAL-本地通信
- AF_INET-IPv4互联网协议
- AF_INET6-IPv6互联网协议
- AF_IPX-IPX-Novell协议
- AF_BLUETOOTH-无线蓝牙协议
- AF_PACKET-低级数据包接口
对于AF_INET
地址族,指定了一对(主机,端口)。host
是一个字符串,表示主机名,如example.com
或像93.184.216.34
这样的IPv4地址,端口是一个整数。
Python获取IP地址
使用gethostbyname
,我们可以得到主机的IP地址。
#!/usr/bin/python import socket ip = socket.gethostbyname('example.com') print(ip)
该示例打印example.com
的IP地址。
$ ./get_ip.py 93.184.216.34
PythonUDP套接字示例
UDP是一种通过网络传输独立数据包的通信协议,不保证到达且不保证交付顺序。使用UDP的一项服务是每日报价(QOTD)。
#!/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)
这是一个示例输出。
PythonTCP套接字示例
这些是提供当前时间的服务器。客户端无需命令即可简单地连接到服务器,服务器会以当前时间进行响应。
#!/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
方法从套接字接收最多tobuffersize个字节。当没有数据可用时,它会阻塞,直到至少有一个字节可用或直到远程端关闭。当远程端关闭并读取所有数据时,它返回一个空字节字符串。
Python套接字HEAD请求
HEAD请求是没有消息正文的GET请求。请求/响应的标头包含元数据,例如HTTP协议版本或内容类型。
#!/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
是通信过程的必需部分。RFC7231文档中描述了详细信息。
$ 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请求
HTTPGET方法请求指定资源的表示。使用GET的请求应该只检索数据。
#!/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")
对于HTTP1.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>
回声客户端服务器示例
回显服务器将来自客户端的消息发回。它是用于测试和学习的经典示例。
#!/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)
回显服务器将客户端消息发送回客户端。
host = 'localhost' port = 8001
服务器在端口8001上的本地主机上运行。
s.bind((host, port))
bind
方法建立通信端点。它将套接字绑定到指定地址。套接字必须尚未绑定。(地址的格式取决于地址族。)
s.listen()
listen
方法使服务器能够接受连接。服务器现在可以侦听套接字上的连接。listen
有一个backlog
参数。它指定系统在拒绝新连接之前允许的未接受连接数。该参数自Python3.5起是可选的。如果未指定,则选择默认积压值。
con, addr = s.accept()
使用accept
,服务器接受一个连接。它阻止并等待传入连接。套接字必须绑定到一个地址并侦听连接。返回值是一对(con,addr),其中con是一个新套接字对象,可用于在连接上发送和接收数据,addr是绑定到连接另一端套接字的地址。
请注意,accept
创建了一个新套接字用于与客户端通信,该套接字与侦听套接字不同。
#!/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'))
客户端向回显服务器发送消息。
异步服务器示例
为了提高服务器的性能,我们可以使用asyncio
模块。
#!/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 http://localhost:8001/
例如,我们可以使用Apache基准测试工具测试性能。在我们的例子中,该命令发送1000个请求,一次发送50个。
在本教程中,我们展示了如何使用Python中的套接字创建简单的网络程序。
列出所有Python教程。