Python网络编程与套接字全解析
1. UDP消息客户端示例
以下是一个向服务器发送消息的UDP客户端示例代码:
# UDP message client
import socket
s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
s.sendto(b"Hello World", ("", 10000))
resp, addr = s.recvfrom(256)
print(resp)
s.sendto(b"Spam", ("", 10000))
resp, addr = s.recvfrom(256)
print(resp)
s.close()
在使用时需要注意以下几点:
- 并非所有常量和套接字选项在所有平台上都可用。如果追求可移植性,应仅依赖于主流资源中记录的选项。
- 套接字模块中显著缺失
recvmsg()
和
sendmsg()
系统调用,若需要这些功能,必须安装第三方模块,如
PyXAPI
。
- 非阻塞套接字操作和涉及超时的操作之间存在细微差别。非阻塞模式下,若操作会阻塞,套接字函数将立即返回错误;设置超时后,仅当操作在指定超时时间内未完成时,函数才返回错误。
2. SSL模块
SSL模块用于使用安全套接字层(SSL)包装套接字对象,提供数据加密和对等身份验证。Python使用OpenSSL库实现该模块。以下是
wrap_socket()
函数的介绍:
wrap_socket(sock [, **opts])
该函数用于将现有套接字
sock
包装为支持SSL的套接字,并返回
SSLSocket
实例。此函数应在后续的
connect()
或
accept()
操作之前使用。
opts
表示用于指定额外配置数据的多个关键字参数,具体如下表所示:
| 关键字参数 | 描述 |
| — | — |
|
server_side
| 布尔标志,指示套接字是否作为服务器(
True
)或客户端(
False
)运行,默认值为
False
。 |
|
keyfile
| 用于标识本地连接端的密钥文件,应为PEM格式文件,通常仅在
certfile
指定的文件不包含密钥时使用。 |
|
certfile
| 用于标识本地连接端的证书文件,应为PEM格式文件。 |
|
cert_reqs
| 指定是否需要来自连接另一端的证书以及是否对其进行验证。
CERT_NONE
表示忽略证书,
CERT_OPTIONAL
表示证书可选但会验证,
CERT_REQUIRED
表示需要证书并进行验证。若要验证证书,必须同时提供
ca_certs
参数。 |
|
ca_certs
| 用于验证的证书颁发机构证书文件的文件名。 |
|
ssl_version
| 要使用的SSL协议版本,可能的值有
PROTOCOL_TLSv1
、
PROTOCOL_SSLv2
、
PROTOCOL_SSLv23
或
PROTOCOL_SSLv3
,默认协议为
PROTOCOL_SSLv3
。 |
|
do_handshake_on_connect
| 布尔标志,指定连接时是否自动执行SSL握手,默认值为
True
。 |
|
suppress_ragged_eofs
| 指定
read()
方法如何处理连接上的意外EOF。若为
True
(默认值),则发出正常EOF信号;若为
False
,则引发异常。 |
SSLSocket
实例除了继承自
socket.socket
外,还支持以下操作:
-
s.cipher()
:返回一个元组
(name, version, secretbits)
,其中
name
是正在使用的密码名称,
version
是SSL协议,
secretbits
是正在使用的秘密位数。
-
s.do_handshake()
:执行SSL握手。通常情况下,除非在
wrap_socket()
函数中将
do_handshake_on_connect
选项设置为
False
,否则会自动执行。若底层套接字
s
是非阻塞的,且操作无法完成,将引发
SSLError
异常。
-
s.getpeercert([binary_form])
:返回连接另一端的证书(如果有)。若无证书则返回
None
;若有证书但未验证,返回空字典;若收到已验证的证书,返回包含
'subject'
和
'notAfter'
键的字典。若
binary_form
设置为
True
,则以DER编码的字节序列形式返回证书。
-
s.read([nbytes])
:读取最多
nbytes
字节的数据并返回。若省略
nbytes
,则最多返回1024字节。
-
s.write(data)
:写入字节字符串
data
,返回写入的字节数。
-
s.unwrap()
:关闭SSL连接并返回底层套接字对象,可在该对象上进行进一步的未加密通信。
此外,该模块还定义了以下实用函数:
-
cert_time_to_seconds(timestring)
:将证书中使用的字符串格式的时间转换为与
time.time()
函数兼容的浮点数。
-
DER_cert_to_PEM_cert(derbytes)
:将包含DER编码证书的字节字符串转换为PEM编码的字符串版本的证书。
-
PEM_cert_to_DER_cert(pemstring)
:将包含PEM编码字符串版本证书的字符串转换为DER编码的字节字符串版本的证书。
-
get_server_certificate(addr [, ssl_version [, ca_certs]])
:检索SSL服务器的证书并以PEM编码的字符串形式返回。
addr
是
(hostname, port)
形式的地址,
ssl_version
是SSL版本号,
ca_certs
是包含证书颁发机构证书的文件的名称。
-
RAND_status()
:如果SSL层认为伪随机数生成器已用足够的随机性进行了种子设定,则返回
True
,否则返回
False
。
-
RAND_egd(path)
:从熵收集守护进程读取256字节的随机性,并将其添加到伪随机数生成器中。
path
是守护进程的UNIX域套接字的名称。
-
RAND_add(bytes, entropy)
:将字节字符串
bytes
中的字节添加到伪随机数生成器中。
entropy
是一个非负浮点数,表示熵的下限。
3. SSL使用示例
以下是使用该模块打开SSL客户端连接的示例:
import socket, ssl
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
ssl_s = ssl.wrap_socket(s)
ssl_s.connect(('gmail.google.com',443))
print(ssl_s.cipher())
# Send a request
ssl_s.write(b"GET / HTTP/1.0\r\n\r\n")
# Get the response
while True:
data = ssl_s.read()
if not data: break
print(data)
ssl_s.close()
以下是一个SSL安全时间服务器的示例:
import socket, ssl, time
s = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR,1)
s.bind(('',12345))
s.listen(5)
while True:
client, addr = s.accept() # Get a connection
print("Connection from", addr)
client_ssl = ssl.wrap_socket(client,
server_side=True,
certfile="timecert.pem")
client_ssl.sendall(b"HTTP/1.0 200 OK\r\n")
client_ssl.sendall(b"Connection: Close\r\n")
client_ssl.sendall(b"Content-type: text/plain\r\n\r\n")
resp = time.ctime() + "\r\n"
client_ssl.sendall(resp.encode('latin-1'))
client_ssl.close()
client.close()
要运行此服务器,需要在
timecert.pem
文件中有一个签名的服务器证书。为了进行测试,可以使用以下UNIX命令创建一个:
% openssl req –new –x509 –days 30 –nodes –out timecert.pem –keyout timecert.pem
要测试此服务器,可以使用浏览器通过
https://localhost:1234
这样的URL进行连接。如果成功,浏览器将发出关于使用自签名证书的警告消息。如果同意,应该可以看到服务器的输出。
4. SocketServer模块
在Python 3中,该模块名为
socketserver
。它提供了简化TCP、UDP和UNIX域套接字服务器实现的类。
4.1 处理程序(Handlers)
使用该模块时,需要定义一个继承自
BaseRequestHandler
基类的处理程序类。
BaseRequestHandler
实例
h
实现了以下一个或多个方法:
-
h.finish()
:在
handle()
方法完成后调用,用于执行清理操作。默认情况下,不执行任何操作。如果
setup()
或
handle()
方法引发异常,则不会调用该方法。
-
h.handle()
:用于执行请求的实际工作。调用时无参数,但几个实例变量包含有用的值。
h.request
包含请求,
h.client_address
包含客户端地址,
h.server
包含调用处理程序的服务器实例。对于TCP等流服务,
h.request
属性是一个套接字对象;对于数据报服务,它是包含接收到的数据的字节字符串。
-
h.setup()
:在
handle()
方法之前调用,用于执行初始化操作。默认情况下,不执行任何操作。如果希望服务器实现进一步的连接设置,如建立SSL连接,可以在此处实现。
以下是一个实现简单时间服务器的处理程序类示例,该服务器可处理流或数据报:
try:
from socketserver import BaseRequestHandler # Python 3
except ImportError:
from SocketServer import BaseRequestHandler # Python 2
import socket
import time
class TimeServer(BaseRequestHandler):
def handle(self):
resp = time.ctime() + "\r\n"
if isinstance(self.request, socket.socket):
# A stream-oriented connection
self.request.sendall(resp.encode('latin-1'))
else:
# A datagram-oriented connection
self.server.socket.sendto(resp.encode('latin-1'), self.client_address)
如果处理程序仅用于处理面向流的连接(如TCP),可以让它继承自
StreamRequestHandler
,该类设置了两个属性:
h.wfile
是一个类似文件的对象,用于向客户端写入数据;
h.rfile
是一个类似文件的对象,用于从客户端读取数据。示例如下:
try:
from socketserver import StreamRequestHandler # Python 3
except ImportError:
from SocketServer import StreamRequestHandler # Python 2
import time
class TimeServer(StreamRequestHandler):
def handle(self):
resp = time.ctime() + "\r\n"
self.wfile.write(resp.encode('latin-1'))
如果处理程序仅处理数据包并总是向发送者返回响应,可以让它继承自
DatagramRequestHandler
,它提供了与
StreamRequestHandler
相同的类似文件的接口。示例如下:
try:
from socketserver import DatagramRequestHandler # Python 3
except ImportError:
from SocketServer import DatagramRequestHandler # Python 2
import time
class TimeServer(DatagramRequestHandler):
def handle(self):
resp = time.ctime() + "\r\n"
self.wfile.write(resp.encode('latin-1'))
在这种情况下,写入
self.wfile
的所有数据将收集到一个数据包中,并在
handle()
方法返回后返回。
4.2 服务器(Servers)
要使用处理程序,需要将其插入到服务器对象中。定义了以下四种基本服务器类:
| 服务器类 | 描述 |
| — | — |
|
TCPServer(address, handler)
| 使用IPv4支持TCP协议的服务器。
address
是
(host, port)
形式的元组,
handler
是
BaseRequestHandler
类子类的实例。 |
|
UDPServer(address, handler)
| 使用IPv4支持Internet UDP协议的服务器。
address
和
handler
与
TCPServer()
相同。 |
|
UnixStreamServer(address, handler)
| 使用UNIX域套接字实现面向流协议的服务器,继承自
TCPServer
。 |
|
UnixDatagramServer(address, handler)
| 使用UNIX域套接字实现数据报协议的服务器,继承自
UDPServer
。 |
所有四种服务器类的实例都具有以下基本方法:
-
s.fileno()
:返回服务器套接字的整数文件描述符。该方法的存在使得可以将服务器实例用于
select()
等轮询操作。
-
s.serve_forever()
:处理无限数量的请求。
-
s.shutdown()
:停止
serve_forever()
循环。
以下属性提供了有关正在运行的服务器配置的一些基本信息:
-
s.RequestHandlerClass
:传递给服务器构造函数的用户提供的请求处理程序类。
-
s.server_address
:服务器正在监听的地址,如
('127.0.0.1', 80)
这样的元组。
-
s.socket
:用于传入请求的套接字对象。
以下是将
TimeHandler
作为TCP服务器运行的示例:
from SocketServer import TCPServer
serv = TCPServer(('', 10000), TimeHandler)
serv.serve_forever()
以下是将处理程序作为UDP服务器运行的示例:
from SocketServer import UDPServer
serv = UDPServer(('', 10000), TimeHandler)
serv.serve_forever()
SocketServer
模块的一个关键方面是处理程序与服务器解耦。即编写好处理程序后,可以将其插入到许多不同类型的服务器中,而无需更改其实现。
4.3 自定义服务器(Defining Customized Servers)
服务器通常需要特殊配置以适应不同的网络地址族、超时、并发等功能。可以通过继承前面描述的四种基本服务器之一来进行定制。以下类属性可用于自定义底层网络套接字的基本设置:
-
Server.address_family
:服务器套接字使用的地址族,默认值为
socket.AF_INET
。若要使用IPv6,可使用
socket.AF_INET6
。
-
Server.allow_reuse_address
:布尔标志,指示套接字是否应重用地址。在程序终止后希望立即在同一端口上重启服务器时很有用(否则需要等待几分钟),默认值为
False
。
-
Server.request_queue_size
:传递给套接字
listen()
方法的请求队列大小,默认值为5。
-
Server.socket_type
:服务器使用的套接字类型,如
socket.SOCK_STREAM
或
socket.SOCK_DGRAM
。
-
Server.timeout
:服务器等待新请求的超时时间(以秒为单位)。超时发生时,服务器调用
handle_timeout()
方法(下面描述),然后继续等待。此超时不用于设置套接字超时,但如果已设置套接字超时,则使用其值代替此值。
以下是创建允许重用端口号的服务器的示例:
from SocketServer import TCPServer
class TimeServer(TCPServer):
allow_reuse_address = True
serv = TimeServer(('', 10000), TimeHandler)
serv.serve_forever()
如果需要,可以在继承自服务器的类中扩展以下方法。在自定义服务器中定义这些方法时,确保调用超类中的相同方法:
-
Server.activate()
:在服务器上执行
listen()
操作的方法,服务器套接字引用为
self.socket
。
-
Server.bind()
:在服务器上执行
bind()
操作的方法。
-
Server.handle_error(request, client_address)
:处理处理过程中发生的未捕获异常的方法。要获取有关最后一个异常的信息,可以使用
sys.exc_info()
或
traceback
模块中的函数。
-
Server.handle_timeout()
:服务器超时发生时调用的方法。通过重新定义此方法并调整超时设置,可以将其他处理集成到服务器事件循环中。
-
Server.verify_request(request, client_address)
:如果要在进行任何进一步处理之前验证连接,可以重新定义此方法。例如,实现防火墙或执行其他类型的验证时可以使用。
此外,还可以通过使用混合类来添加服务器功能,例如通过线程或进程分叉实现并发。定义了以下类用于此目的:
-
ForkingMixIn
:一个混合类,为服务器添加UNIX进程分叉功能,使其能够服务多个客户端。类变量
max_children
控制最大子进程数,
timeout
变量确定收集僵尸进程尝试之间的时间间隔。实例变量
active_children
跟踪正在运行的活动进程数。
-
ThreadingMixIn
:一个混合类,修改服务器使其能够使用线程服务多个客户端。创建的线程数量没有限制。默认情况下,线程是非守护线程,除非将类变量
daemon_threads
设置为
True
。
以下是一个分叉时间服务器的示例:
from SocketServer import TCPServer, ForkingMixIn
class TimeServer(ForkingMixIn, TCPServer):
allow_reuse_address = True
max_children = 10
serv = TimeServer(('', 10000), TimeHandler)
serv.serve_forever()
由于并发服务器比较常见,
SocketServer
预定义了以下服务器类:
-
ForkingUDPServer(address, handler)
-
ForkingTCPServer(address, handler)
-
ThreadingUDPServer(address, handler)
-
ThreadingTCPServer(address, handler)
这些类实际上是根据混合类和服务器类定义的。例如,
ForkingTCPServer
的定义如下:
class ForkingTCPServer(ForkingMixIn, TCPServer): pass
4.4 应用服务器的定制(Customization of Application Servers)
其他库模块通常使用
SocketServer
类来实现HTTP和XML - RPC等应用层协议的服务器。这些服务器也可以通过继承和扩展基本服务器操作定义的方法进行定制。以下是一个仅接受源自回环接口连接的分叉XML - RPC服务器示例:
try:
from xmlrpc.server import SimpleXMLRPCServer # Python 3
from socketserver import ForkingMixIn
except ImportError: # Python 2
from SimpleXMLRPCServer import SimpleXMLRPCServer
from SocketServer import ForkingMixIn
class MyXMLRPCServer(ForkingMixIn, SimpleXMLRPCServer):
def verify_request(self, request, client_address):
host, port = client_address
if host != '127.0.0.1':
return False
return SimpleXMLRPCServer.verify_request(self, request, client_address)
# Sample use
def add(x, y):
return x + y
server = MyXMLRPCServer(("", 45000))
server.register_function(add)
server.serve_forever()
要测试此服务器,需要使用
xmlrpclib
模块。运行上述服务器,然后启动一个单独的Python进程:
>>> import xmlrpclib
>>> s = xmlrpclib.ServerProxy("http://localhost:45000")
>>> s.add(3, 4)
7
>>>
要测试连接拒绝功能,可以在网络中的不同机器上尝试相同的代码。此时,需要将
“localhost”
替换为运行服务器的机器的主机名。
Python网络编程与套接字全解析(续)
5. 总结与实际应用建议
在实际的网络编程中,我们可以根据不同的需求选择合适的技术和方法。以下是一些总结和建议:
5.1 UDP与TCP选择
- UDP :适用于对实时性要求较高、对数据准确性要求相对较低的场景,如视频流、音频流传输等。UDP消息客户端示例展示了其简单的数据发送和接收方式,但要注意其不可靠性。
-
TCP
:适用于对数据准确性要求高的场景,如文件传输、网页浏览等。
SocketServer模块中的TCPServer类可以方便地实现TCP服务器。
5.2 SSL加密
当需要对数据进行加密和身份验证时,SSL模块是一个很好的选择。通过
wrap_socket()
函数可以轻松地将普通套接字包装为支持SSL的套接字。在实际应用中,要确保正确配置证书和密钥,以保证通信的安全性。
5.3 SocketServer模块
该模块提供了强大的服务器实现功能,通过定义处理程序类和服务器类,可以灵活地实现各种类型的服务器。处理程序与服务器的解耦设计使得代码的可维护性和可扩展性大大提高。同时,通过继承和扩展基本服务器类,可以实现自定义的服务器配置,如并发处理、连接验证等。
6. 流程图示例
以下是一个简单的TCP服务器处理请求的流程图:
graph TD;
A[服务器启动] --> B[监听端口];
B --> C{有新连接?};
C -- 是 --> D[接受连接];
D --> E[创建处理程序实例];
E --> F[调用处理程序的setup()方法];
F --> G[调用处理程序的handle()方法];
G --> H[处理请求];
H --> I[调用处理程序的finish()方法];
I --> C;
C -- 否 --> B;
7. 常见问题及解决方法
在网络编程过程中,可能会遇到一些常见问题,以下是一些解决方法:
| 问题 | 解决方法 |
|---|---|
| 端口被占用 |
可以通过设置
Server.allow_reuse_address
为
True
来允许重用端口,或者更换其他端口。
|
| SSL证书问题 | 确保证书文件路径正确,并且证书是有效的。对于自签名证书,客户端可能会发出警告,需要进行相应的处理。 |
| 并发处理问题 |
可以使用
ForkingMixIn
或
ThreadingMixIn
混合类来实现并发处理,根据实际情况选择合适的并发方式。
|
8. 未来发展趋势
随着网络技术的不断发展,Python网络编程也在不断演进。未来可能会出现更多的高级网络协议和功能,如IPv6的广泛应用、更强大的加密算法等。同时,对于并发处理和性能优化的需求也会越来越高。开发者需要不断学习和掌握新的技术,以适应不断变化的网络环境。
9. 实践建议
为了更好地掌握Python网络编程,建议进行以下实践:
- 编写更多的网络应用程序,如简单的聊天程序、文件传输程序等。
- 尝试不同的服务器配置和并发处理方式,比较它们的性能和优缺点。
- 学习和研究最新的网络技术和协议,关注行业动态。
通过不断的实践和学习,相信你可以成为一名优秀的Python网络开发者。
超级会员免费看
1054

被折叠的 条评论
为什么被折叠?



