11.1.1 寻址,协议簇和套接字类型 套接字是程序在本地或者通过互联网来回传递数据所用通信通道的一个端点。 套接字主要有两个属性来控制如何发送数据:协议簇(address family)控制所用的OSI网络层协议,套接字(socket type)控制传输层协议。 Python支持三个地址簇。最常用的是AF_INET,用于IPv4 Internet寻址。AF_INET6应用IPv6 Internet寻址。 AF_UNIX是UNIX域套接字(UNIX Domain Sockets, UDS)的地址簇,这是一种POSIX兼容系统上的进程间通信协议。UDS的实现通常允许操作系统直接从进程向进程传递数据,而不通过网络栈。 UDS仅限于同一系统上的进程。相比其他IPC,使用UDS的优势在于,它与IP网络应用的编程接口是一样的。 套接字类型往往是SOCK_DGRAM或SOCK_STREAM,SOCK_DGRAM用于UDP,SOCK_stream用于TCP。UDP不需要传输握手过程,可靠性较低,TCP确保每条消息指传输一次,而且按照正确的顺序传送。由于增加了可靠性,可能引入额外的延迟。大多数传送大量数据的应用协议(HTTP)都是建立在TCP基础上,UDP通常用于顺序不太重要的协议,如DNS或者广播。 在网络上查找主机 socket包含一些函数与网络上的域名服务交互,使得程序可以将服务器的主机名转换为其数字网络地址。应用使用地址来连接一个服务器前并不需要显示的转换地址,不过报错是提供数字地址很有用。 import socket print socket.gethostname() 返回的名字取决于当前系统的网络设置。 >>> ================================ RESTART ================================ >>> ruby 使用gethostbyname()访问操作系统主机名解析API,将服务器名称转换为其数字地址。 import socket for host in ['homer', 'www', 'www.python.org','www.caoqing123.com']: try: print '%s : %s' % (host,socket.gethostbyname(host)) except socket.error, msg: print '%s : %s' % (host, msg) 如果当前系统的DNS配置在搜索中包括一个或多个域,名字参数不要求完全限定名。如果名字无法找到,会返回socket error类型的异常。 >>> ================================ RESTART ================================ >>> homer : [Errno 11004] getaddrinfo failed www : 121.14.228.36 www.python.org : 82.94.164.162 www.caoqing123.com : [Errno 11004] getaddrinfo failed 要访问服务器的更多信息,可以使用函数gethostname_ex(),可以返回服务器的标准主机名,所有别名,以及可以用来到达这个主机的所有可用IP地址。 import socket for host in ['homer', 'www', 'www.python.org','www.caoqing123.com']: print host try: hostname, alias, address = socket.gethostbyname_ex(host) print 'Hostname :', hostname print 'alias :', alias print 'address :', address except socket.error as msg: print 'ERROR:', msg print 如果得到一个服务器的所有已知地址,客户可以实现自己的负载均衡或故障恢复算法。 >>> ================================ RESTART ================================ >>> homer ERROR: [Errno 11004] getaddrinfo failed www Hostname : 1st.dtwscache.glb0.lxdns.com alias : ['www.zte.com.cn', 'www.zte.com.cn.cdn20.com'] address : ['61.146.152.58', '121.14.228.36', '121.14.35.233'] www.python.org Hostname : www.python.org alias : [] address : ['82.94.164.162'] www.caoqing123.com ERROR: [Errno 11004] getaddrinfo failed 使用getfqdn()可以将一个部分名转换为完全限定域名。 import socket for host in ['homer', 'www', 'www.python.org','www.caoqing123.com']: print '%6s : %s' % (host, socket.getfqdn(host)) 如果输入的是一个别名,返回的名字不一定与输入参数一致。 >>> ================================ RESTART ================================ >>> homer : homer www : www www.python.org : dinsdale.python.org www.caoqing123.com : www.caoqing123.com 如果得到一个服务器地址,可以使用gethostbyaddr()完成一个逆向查找来得到主机名。 import socket hostname, alias, address = socket.gethostbyaddr('192.168.27.91') print 'Hostname :', hostname print 'Alias :', alias print 'Address :', address 查找服务信息 除了IP地址外,每个套接字地址还包括一个端口号,很多应用可以在同一个端口上运行并监听一个IP地址,不过只有一个套接字可以使用该地址的端口。 通过结合IP地址,协议和端口号,可以唯一的标识一个通信通道,确保一个套接字发送的消息达到正确的目标。 网络服务的端口号和标准名可以使用getservbyname()查找。 import socket from urlparse import urlparse for url in ['http://www.python.org', 'https://caoqing.com', 'pop3://xiaohuan.com', 'smtp://xiaobao.com', ]: parse_url = urlparse(url) port = socket.getservbyname(parse_url.scheme) print '%6s : %s' % (parse_url.scheme, port) >>> ================================ RESTART ================================ >>> http : 80 https : 443 pop3 : 110 smtp : 25 要逆向完成服务端口查找,可以使用getservbyport()。 import socket import urlparse for port in [80, 443, 110, 25]: print urlparse.urlunparse( (socket.getservbyport(port), 'caoqing.com', '/', '', '', '') ) 要从任意地址构造服务URL,这个逆向查找就很有用。 >>> ================================ RESTART ================================ >>> http://caoqing.com/ https://caoqing.com/ pop3://caoqing.com/ smtp://caoqing.com/ 可以使用getprotobyname()获取分配给一个传输协议的端口号。 import socket def get_constants(prefix): return dict( (getattr(socket, n), n) for n in dir(socket) if n.startswith(prefix) ) protocols = get_constants('IPPROTO_') for name in ['icmp', 'udp', 'tcp']: proto_num = socket.getprotobyname(name) const_name = protocols[proto_num] print '%4s -> %2d (socket.%-12s = %2d)' % \ (name, proto_num, const_name, getattr(socket, const_name)) 协议码值是标准化的,作为常量在socket中定义,这些协议码都有前缀IPPROTO_。 >>> ================================ RESTART ================================ >>> icmp -> 1 (socket.IPPROTO_ICMP = 1) udp -> 17 (socket.IPPROTO_UDP = 17) tcp -> 6 (socket.IPPROTO_TCP = 6) 查找服务器地址 getaddrinfo()将一个服务的基本地址转换为一个元组列表,其中包含建议一个连接所需的全部信息。每个元组的内容会有变化,包含不同的网络簇或协议。 import socket def get_constants(prefix): return dict( (getattr(socket, n), n) for n in dir(socket) if n.startswith(prefix) ) families = get_constants('AF_') types = get_constants('SOCK_') protocols = get_constants('IPPROTO_') for response in socket.getaddrinfo('www.python.org', 'http'): family, socktype, proto, canonname, sockaddr = response print 'Family :', families[family] print 'Type :', types[socktype] print 'Protocol :', protocols[proto] print 'Canoniocal name :', canonname print 'Socket address :', sockaddr print 展示了如何查找www.python.org的连接信息。 >>> ================================ RESTART ================================ >>> Family : AF_INET Type : SOCK_STREAM Protocol : IPPROTO_IP Canoniocal name : Socket address : ('82.94.164.162', 80) getaddrinfo()有多个参数来过滤结果列表。host和port是必要参数。可选参数是family,socktype,proto和flags。这些可选值可以取0或socket定义的某个常量。 用IP地址表示 用C编写的网络程序使用数据类型struct sockaddr将IP地址表示为二进制值。在C表示和Python表示之间转换IPV4地址,可以使用inet_aton()和inet_ntoa()。 import socket import binascii import struct import sys for string_address in ['127.0.0.1']: packed = socket.inet_aton(string_address) print 'Original :', string_address print 'Packed :', binascii.hexlify(packed) print 'Unpacked :', socket.inet_ntoa(packed) print 数据包格式中的四个字节可以传递到C库,通过网络完全的传输。 >>> ================================ RESTART ================================ >>> Original : 127.0.0.1 Packed : 7f000001 Unpacked : 127.0.0.1 函数inet_aton和inet_ntoa都能处理IPv4和IPv6地址,根据传入的地址簇参数生成合适的格式。