socket 是 Python 标准库中的一个模块,它提供了低级别的网络通信接口。使用 socket 模块,你可以创建客户端和服务器应用程序,以便在网络上进行数据交换。
接着上文我们介绍下socket模块其他的一些函数。
目录
gettimeout() 和settimeout()
socket.error套接字错误
inet_aton() 和inet_ntoa()
inet_pton()和inet_ntop()
socket.getsockopt()
socket.setsockopt()
socket.setblocking()
gethostname()
gethostbyname(hostname)
getaddrinfo(host, port, family=0, type=0, proto=0, flags=0)
getnameinfo(sockaddr, flags)
gettimeout() 和settimeout()
调用gettimeout()方法获取默认的套接字超时时间,调用settimeout()方法设定一个套接字超时时间。默认无超时时间,当设置超时时间后,当服务端启动socket套接字时,超过了超时时间没有客户端建立连接,会报错。
import socket
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print('Waiting for a connection...')
print("Default socket timeout: %s" % server_socket.gettimeout())
server_socket.settimeout(10)
print("Current socket timeout: %s" % server_socket.gettimeout())
举例:设置超时时间10s,执行程序后,10s后程序会报错
Waiting for a connection...
Default socket timeout: None
Current socket timeout: 10.0
Traceback (most recent call last):File "/Users/htsc/Desktop/test.py", line 43, in <module>connection, client_address = server_socket.accept()File "/Library/Frameworks/Python.framework/Versions/3.10/lib/python3.10/socket.py", line 293, in acceptfd, addr = self._accept()TimeoutError: timed out
socket.error套接字错误
在网络应用中,经常会遇到一方尝试连接,但另一方由于网络媒介失效或者其他
原因无法响应。Python的socket库提供了一个方法,能通过socket.error异常优雅地处理套接
字错误。
# 创建一个 socket 对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
# 连接到服务器
server_address = ('localhost', 8080)
try:client_socket.connect(server_address)
except socket.error as e:print(f'客户端连接报错 {e}')
# 发送数据
try:message = 'hello world!'client_socket.sendall(message.encode())
except socket.error as e:print(f'客户端发送错误 {e}')
服务端没有启动时执行程序报错:
客户端连接报错 [Errno 61] Connection refused
客户端发送错误 [Errno 32] Broken pipe
inet_aton() 和inet_ntoa()
inet_aton() 将十进制格式的 IPv4 地址转换为二进制格式。
inet_ntoa() 将二进制格式的 IPv4 地址转换回点分十进制格式的字符串。
print(socket.inet_aton('192.168.1.1'))
print(socket.inet_ntoa(b'\xc0\xa8\x01\x01'))
b'\xc0\xa8\x01\x01'
192.168.1.1
inet_pton()和inet_ntop()
同时支持IPv4和IPv6地址的转换。
socket.inet_pton(socket.AF_INET6, ip_str) 来转换IPv6地址的字符串为二进制格式,以及 socket.inet_ntop(socket.AF_INET6, packed_ip) 来转换二进制格式为IPv6地址的字符串。
print(socket.inet_pton(socket.AF_INET6,'A2DD:E543::A234:12AE'))
print(socket.inet_ntop(socket.AF_INET6,b'\xa2\xdd\xe5C\x00\x00\x00\x00\x00\x00\x00\x00\xa24\x12\xae'))'
b'\xa2\xdd\xe5C\x00\x00\x00\x00\x00\x00\x00\x00\xa24\x12\xae'
a2dd:e543::a234:12ae
socket.inet_pton(socket.AF_INET, ip_str) 来转换IPv4地址的字符串为二进制格式,以及 socket.inet_ntop(socket.AF_INET, packed_ip) 来转换二进制格式为IPv4地址的字符串。
print(socket.inet_pton(socket.AF_INET,'192.168.1.1'))
print(socket.inet_ntop(socket.AF_INET,b'\xc0\xa8\x01\x01'))
b'\xc0\xa8\x01\x01'
192.168.1.1
注意事项:
- inet_aton() 和 inet_ntoa() 是非线程安全的,并且在多线程环境中使用时可能会出现问题。在多线程环境中,建议使用 inet_pton() 和 inet_ntop() 函数,它们提供了更好的错误处理和线程安全性。
- inet_ntoa() 返回的字符串指针指向一个静态内存区域,因此每次调用都会覆盖上一次的结果。在多次调用 inet_ntoa() 时,需要注意不要依赖之前的结果。
socket.getsockopt()
是一个用于获取套接字选项值的函数。这个函数在 Python 的 socket 模块中定义,用于检索与套接字相关的各种参数和设置。
socket.getsockopt(level, option, buflen=0) |
参数说明:
level:表示要获取的选项所在的协议层级。常见的值有:
socket.SOL_SOCKET:通用套接字选项。
socket.IPPROTO_IP:IP选项。
socket.IPPROTO_TCP:TCP选项。
option:表示要获取的选项的名称。例如 socket.SO_REUSEADDR 用于确定套接字地址是否可以在关闭后立即重用,socket.SO_SNDBUF 和 socket.SO_RCVBUF 分别用于获取发送和接收缓冲区的大小等。
buflen:表示获取选项值的缓冲区的长度。在某些情况下,这个参数可能是可选的,并且默认值为0,表示自动分配足够的空间来存储选项值。
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#发送缓冲区大小
print(client_socket.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF))
socket.setsockopt()
用于修改套接字的设置选项值,比上面的setsockopt()方法,多一个value参数,传入要修改的选项值。
举例:修改发送缓冲区大小
# 创建一个 socket 对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
print(client_socket.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF))
#修改发送缓冲区大小 传入value参数=1024102
client_socket.setsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF,1024102)
print(client_socket.getsockopt(socket.SOL_SOCKET, socket.SO_SNDBUF))
socket.setblocking()
默认情况下,TCP套接字处于阻塞模式中。
阻塞模式:在阻塞模式下,套接字操作(如发送和接收数据)将等待直到操作完成或发生错误。比如调用recv() 方法并且没有数据可读,程序将会挂起(即暂停执行),直到有数据可读或者连接关闭。
非阻塞模式:参数为 False,则套接字被设置为非阻塞模式。在非阻塞模式下,套接字操作会立即返回,而不会等待操作完成。比如recv() 方法可能会返回一个空字符串,或者引发一个异常(如 socket.error),具体取决于操作系统的行为。
举例:
- 客户端连接设置为阻塞模式
# 创建一个 socket 对象
client_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
#设置为阻塞模式
client_socket.setblocking(True)
#client_socket.settimeout(0.5)
# 连接到服务器
server_address = ('localhost', 8080)
client_socket.connect(server_address)
print('等待接收数据')
data = client_socket.recv(1024)
print(f'Received {len(data)} bytes {data.decode()}')
执行后程序显示:等待接收数据
- 客户端设置为非阻塞模式:
以上代码将client_socket.setblocking(True)改为client_socket.setblocking(False)设置为非阻塞模式。重新执行程序后会报错:
BlockingIOError: [Errno 36] Operation now in progress
这个错误是在使用非阻塞套接字(non-blocking socket)时常见的错误之一。当你尝试在一个非阻塞套接字上执行一个需要等待的操作(比如读取或写入数据),而这个操作不能立即完成时,就会抛出这个错误。
在非阻塞模式下,套接字操作不会等待直到数据可用或操作完成。相反,它们会立即返回,并可能抛出 BlockingIOError 异常来表明操作不能立即完成。这意味着你需要检查操作是否成功,并在需要时重试。
gethostname()
用于获取当前主机名。
import socket
print(socket.gethostname())
192.168.1.8
gethostbyname(hostname)
用于解析主机名,返回相应的IP地址。这个函数只支持 IPv4 地址。
print(socket.gethostbyname('www.baidu.com'))
print(socket.gethostbyname('localhost'))
180.101.50.242
127.0.0.1
getaddrinfo(host, port, family=0, type=0, proto=0, flags=0)
用于解析主机名和服务名,并返回相应的套接字地址信息。它支持 IPv4 和 IPv6 地址,并可以根据需要选择适当的协议。
函数的参数包括:
- host:主机名或 IP 地址,以字符串形式给出。
- port:服务名或端口号,以字符串或整数形式给出。为字符串,通常代表一个服务名(如 "http" 或 "ftp");为整数,代表一个具体的端口号(比如80 或者443)。
- family:地址族,用于指定 IP 地址版本。常见的值包括 socket.AF_INET(IPv4)、socket.AF_INET6(IPv6)和 socket.AF_UNSPEC(不指定,让系统选择)。默认值为 0,表示让系统选择。
- socktype:套接字类型,指定使用的套接字类型。常见的值有 socket.SOCK_STREAM(流套接字,如 TCP)和 socket.SOCK_DGRAM(数据报套接字,如 UDP)。默认值为 0,表示让系统选择。
- proto:协议类型。通常可以设置为 0,让系统根据 family 和 socktype 选择合适的协议。
- flags:标志位,用于控制地址信息的获取方式。例如,socket.AI_PASSIVE 用于获取用于绑定的地址,socket.AI_CANONNAME 用于获取主机名的规范形式等。
举例:分别获取百度域名443端口的一些地址信息
print(socket.getaddrinfo('www.baidu.com',443,family=socket.AF_INET6,type=socket.SOCK_STREAM))
print(socket.getaddrinfo('www.baidu.com',443,family=socket.AF_INET,type=socket.SOCK_DGRAM))
#结果
[(<AddressFamily.AF_INET6: 30>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('240e:e9:6002:15a:0:ff:b05c:1278', 443, 0, 0)), (<AddressFamily.AF_INET6: 30>, <SocketKind.SOCK_STREAM: 1>, 6, '', ('240e:e9:6002:15c:0:ff:b015:146f', 443, 0, 0))]
[(<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('180.101.50.188', 443)), (<AddressFamily.AF_INET: 2>, <SocketKind.SOCK_DGRAM: 2>, 17, '', ('180.101.50.242', 443))]
返回结果list中每个元组解释如下:
这个五元组的意义如下:
第一个切片:2表示 IPv4 协议(socket.AF_INET)
30表示 IPv6 协议(socket.AF_INET6)
第二个切片: 1表示流套接字(socket.SOCK_STREAM),通常用于 TCP 连接。2表示数据报套接字(Socket.SOCK_DGRAM:)
第三个切片:6:这是 TCP 的协议号,17是UDP的协议号
第四个切片: 主机的规范名称,可能为空字符串,
第五个切片:('93.184.216.34', 80):这是实际的 IP 地址和端口号,可以用这些信息来创建套接字并进行连接。
getnameinfo(sockaddr, flags)
是 getaddrinfo() 的逆操作,它将套接字地址信息转换为主机名和服务名。
参数解释:
sockaddr:套接字地址的元组。对于 IPv4,它通常是一个 (host, port) 的形式,其中 host 是 IP 地址(字符串形式),port 是端口号(整数形式)。
flags:标志位,用于控制解析的方式。例如,socket.NI_NUMERICHOST 和 socket.NI_NUMERICSERV 可以用来强制返回数字形式的主机名和服务名,而不是尝试解析它们。
getnameinfo 函数返回一个包含两个元素的元组:(host, serv),其中 host 是主机名(字符串形式),serv是服务名或端口号(字符串形式)。如果 IP 地址没有与之关联的主机名(即它没有被反向解析),getnameinfo 可能会返回 IP 地址本身作为主机名。
print(socket.getnameinfo(('180.101.50.242', 443),socket.NI_NUMERICSERV))
('180.101.50.242', '443')
共勉: 东汉·班固《汉书·枚乘传》:“泰山之管穿石,单极之绠断干。水非石之钻,索非木之锯,渐靡使之然也。”
-----指水滴不断地滴,可以滴穿石头;
-----比喻坚持不懈,集细微的力量也能成就难能的功劳。
----感谢读者的阅读和学习,谢谢大家。