11.2 socket:网络通信
socket模块提供了一个底层 C API,可以使用BSD套接字接口实现网络通信。它包括socket类,用于处理具体的数据通道,还包括用来完成网络相关任务的函数,如将一个服务器名转换为一个地址以及格式化数据以便在网络上发送。
11.2.1 寻址、协议簇和套接字类型
套接字(socket)是程序在本地或者通过互联网来回传递数据时所用通信通道的一个端点。套接字有两个注意属性用于控制如何发送数据:地址簇(address family)控制所用的OSI网络层协议;套接字类型(soekt type)控制传输层协议。Python支持3个地址簇。最常用的是AF_INET,用于IPv4 Internet寻址。IPv4地址长度为4个字节,通常表示为4个数的序列,每个字节对应一个数,用点号分隔。这些值通常被称为"IP地址"。目前几乎所有互联网网络通信都使用IPv4。AF_INET6用于IPv6 Internet寻址。IPv6是“下一代”Internet协议,它支持128位地址和通信流调整,还支持IPv4不支持的一些路由特性。采用IPv6的应用在不断增多,特别是随着云计算的大量普及以及由于物联网项目而为网络增加了很多额外的设备,都促使IPv6得到更广泛的应用。
AF_UNIX是UNIX域套接字(UNIX Domain Socket,UDS)的地址簇,这是一种POSIX兼容系统上的进程通信协议。UDS的实现通常允许操作系统直接从进程向进程传递数据,而不用通过网络栈。这比使用AF_INET更高效,但是由于要用文件系统作为寻址的命名空间,所以UDS仅限于同一个系统上的进程。相比其他IPC机制(如命名管道或共享内存),使用UDS的优势在于它于IP网络应用的编程接口是一样的。这说明,应用在单个主机上运行时可以利用高效的通信,在网络上发送数据时仍然可以使用同样的代码。
说明:AF_UNIX常量仅在支持UDS的系统上定义。
套接字类型往往是SOCK_DGRAM或SOCK_STREAM,其中SOCK_DGRAM对于面向消息的数据报传输,而SOCK_STREAM对应面向流的传输。数据报套接字通常与UDP关联,即用户数据报协议(user datagram protocol).这些套接字能提供不可靠的消息传达。面向流的套接字与TCP相关,即传输控制协议(transmission control protocol)。它们可以在客户和服务器之间提供字节流,通过超时管理、重传和其他特性确保提供消息传输或失败通知。大多数传送大量数据的应用协议(如HTTP)都建立在TCP基础上,因为这样可以更容易地创建自动处理消息排序和传输的复杂应用。UDP通常用于顺序不太重要的协议(因为消息是自包含的,而且通常很小、如通过DNS的名字查找),或者用于组播(向多个主机发送相同的数据)。UDP和TCP都可以用于IPv4或IPv6寻址。
说明:Python的socket模块还支持其他套接字类型,不过它们不太常用。
11.2.1.1 在网络上查找主机
socket包含一些与网络上的域名服务交互的函数,这使得程序可以将服务器的主机名转换为其数字网络地址。应该使用地址连接服务器之前并不需要显式地转换地址,不过报告错误时除了报告所用的名字之外,如何还能包含这个数字地址,那么便会很有用。要查找当前主机的正式名,可以使用gethostname()。
import socket
print(socket.gethostname())
所返回的名字取决于当前系统的网络设置,在不同的网络上返回的名字可能有变化(如连接到无线LAN的一个笔记本电脑)。
运行结果:
这里使用gethostbyname()访问操作系统主机名解析API,并且将服务器名转换为其数字地址。
import socket
HOSTS = [
'apu',
'pymotw.com',
'baidu.com',
'sina.com',
]
for host in HOSTS:
try:
print('{}:{}'.format(host,socket.gethostbyname(host)))
except socket.error as msg:
print('{}:{}'.format(host,msg))
如果当前系统的DNS配置在搜索中包括一个或多个域,那么不要求名字(name)参数是完全限定名(也就是说,不需要包含域名以及基主机名)。如果无法找到一个名字,则会产生一个socket.error类型的异常。
运行结果:
要访问有关服务器的更多命名信息,可以使用函数gethostbyname_ex()。它会返回服务器的标准主机名、所有别名,以及可以到达这个主机的所有可用IP地址。
import socket
HOSTS = [
'apu',
'pymotw.com',
'baidu.com',
'sina.com',
]
for host in HOSTS:
print(host)
try:
name,aliases,addresses = socket.gethostbyname_ex(host)
print(' Hostname:',name)
print(' Aliases :',aliases)
print(' Addresses:',addresses)
except socket.error as msg:
print('ERROR:',msg)
print()
如果能得到一个服务器的所有已知IP地址,客户就可以实现自己的负载平衡或故障恢复算法。
运行结果:
使用getfqdn()可以将一个部分名转换为完全限定域名。
import socket
for host in ['sina.com','pymotw.com']:
print('{:>10}:{}'.format(host,socket.getfqdn(host)))
如果输入是一个别名(如这里的www),那么返回的名字不一定与输入参数一致。
运行结果:
如果得到一个服务器的地址,那么可以使用gethostbyaddr()完成一个“逆向”查找得到主机名。
import socket
hostname,aliases,addresses = socket.gethostbyaddr('66.33.211.242')
print('Hostname :',hostname)
print('Aliases :',aliases)
print('Addresses:',addresses)
返回值是一个元组,其中包括完全主机名、所有别名,以及与这个名关联的所有IP地址。
运行结果: