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地址,根據傳入的地址簇參數生成合適的格式。