底層的socket庫可以直接訪問本地Csocket庫並且可與任何網絡服務通信。select同時監控多個sockets,支持網絡服務和多個客戶端通信。
SocketServer框架抽象了很多創建網絡服務器的重複工作。該類來可以使用fork或者線程創建服務器,支持TCP或UDP,用戶只需要處理應用實際的消息處理。
asyncore實現了一個基於回調API的異步網絡棧。它封裝了輪詢循環和緩衝,當接收數據時調用相應的處理程序。asynchat框架在asyncore的基礎上簡化了創建的雙向基於消息協議的過程。
socket模塊展示了使用BSD socketAPI在網絡上進行通信的低級的C API。它包括用於處理實際數據信道socket類,還包括用於與網絡相關的任務的功能,比如轉換一個服務器的名字爲地址和格式化要發送的數據
11.1 socket– 網絡通信
套接字是程序使用通信信道用來本地或網絡上來回傳送數據的一個端點。套接字有兩個基本屬性用來控制發送數據:地址族控制的OSI網絡層使用的協議,套接字類型控制輸送層協議。
Python支持的3種地址族。最常見的AF_INET,用於IPv4的互聯網尋址。幾乎目前所有互聯網聯網使用IP版本4。
AF_INET6用於IPv6互聯網尋址。IPv6是“下一代”版本的互聯網協議。它支持128位的地址,流量控制和IPv4不支持的路由功能。IPv6有限使用,但持續增長。
AF_UNIX是UNIX域套接字(UDS),是POSIX兼容的系統上進程間的通信協議。UDS的實現通常允許操作系統不用通過網絡堆棧在進程間直接通信。這比使用AF_INET更高效,但使用文件系統被作爲命名空間進行尋址,UDS限制在同一系統。吸引力在於在IPC使用UDS,比如命名管道或共享內存的編程接口和IP網絡一致。這應用程序可以使用網絡通信同樣的代碼在單機上實現有效的通信機制。
套接字類型通常是爲SOCK_DGRAM用戶數據報協議(UDP)或SOCK_STREAM傳輸控制協議(TCP)。 tcp一般用戶傳送大量數據,udp一般用於傳送少量數據或者多播。
Socket展示了使用BSDsocket接口進行網絡通信的低層CAPI。它包括socket類,用於處理實際數據通道,還包含網絡相關的功能,比如轉換服務器名爲地址,格式化要發送的數據。
11.1.1 尋址、協議家族和socket類型
套接字是程序在本地或者跨因特網來回傳遞數據的通信通道的端點。Socket有兩個控制發送數據的基本屬性:addressfamil控制使用OSI網絡層協議,sockettype控制傳輸層協議。
Python支持三種地址家族。最常見的AF_INET用於IPv4的互聯網尋址。 IPv4地址是4個字節長,爲四個數字,以點分隔(例如,10.1.1.5和127.0.0.1),這些值通常稱爲“IP地址。”幾乎目前所有的互聯網網絡是使用IPv4。
AF_INET6是用於IPv6互聯網尋址。 IPv6是“下一代”版本在Internet協議,採用128位的地址,它支持IPv4下不支持的流量整形和路由功能。IPv6使用依然有限的,但繼續增長。
AF_UNIX是UNIX域套接字(UDSUNIXDomain Sockets),是POSIX兼容的系統進程交互的通信協議。它常允許操作系統在進程間傳遞數據,無需通過網絡棧,這是比使用POSIX兼容的系統進行更有效率。由於使用了文件系統作爲namespace來尋址,UDS
限制在同一系統上的進程。建議在其他的IPC機制上使用UDS,如命名管道或共享內存機制,這樣編程接口就和IP網絡相同。這意味着應用程序本地可以有效的通信,且使用相同的代碼可在網絡上發送數據。
套接字是程序使用通信信道用來本地或網絡上來回傳送數據的一個端點。套接字有兩個基本屬性用來控制
發送數據:地址族控制的OSI網絡層使用的協議,套接字類型控制輸送層協議。
Python支持的3種地址族。最常見的AF_INET,用於IPv4的互聯網尋址。幾乎目前所有互聯網聯網使用IP版本4。
AF_INET6用於IPv6互聯網尋址。 IPv6是“下一代”版本的互聯網協議。它支持128位的地址,流量控制和IPv4不支持的路由功能。IPv6有限使用,但繼續
增長。
AF_UNIX是UNIX域套接字(UDS),是POSIX兼容的系統上進程間的通信協議。UDS的實現通常允許操作系統不用通過網絡堆棧在進程間直接通信。這比使用AF_INET更高效,但使用文件系統被作爲命名空間進行尋址,UDS限制在同一系統。吸引力在於在IPC使用UDS,比如命名管道或共享內存的編程接口和IP網絡一致。這應用程序可以使用網絡通信同樣的代碼在單機上實現有效的通信機制。
套接字類型通常是爲SOCK_DGRAM用戶數據報協議(UDP)或SOCK_STREAM傳輸控制協議(TCP)。 tcp一般用戶傳送大量數據,udp一般用於傳送少量數據或者多播。
11.1.1.1查找主機:
socet.gethostbyname(hostname)
翻譯的主機名IPv4地址格式。以字符串形式返回的IPv4地址,如'100.50.200.5“。如果是一個IPv4地址的主機名,它原封不動地返回。更完整的接口參見gethostbyname_ex()。gethostbyname()的不支持IPv6名稱解析,可以使用getaddrinfo()獲取IPv4/v6雙協議棧支持。
import socket
for host in [ ’homer’, ’www’, ’www.python.org’, ’nosuchname’ ]:
try:
print ’%s : %s’ % (host, socket.gethostbyname(host))
except socket.error, msg:
print ’%s : %s’ % (host, msg)
執行結果:
# python socket_gethostbyname.py
homer : [Errno -2] Name or service not known
www : [Errno -2] Name or service not known
www.python.org : 82.94.164.162
nosuchname : [Errno -2] Name or service not known
socket.gethostbyname_ex(hostname)
翻譯的主機名IPv4地址格式的擴展接口。返回一個三元組(hostname,aliaslist,ipaddrlist),gethostbyname_ex()不支持IPv6名稱解析,可以使用getaddrinfo()獲取IPv4/v6雙協議棧支持。
import socket
for host in [ 'homer', 'www', 'www.python.org', 'nosuchname' ]:
print host
try:
hostname, aliases, addresses =socket.gethostbyname_ex(host)
print ' Hostname:', hostname
print ' Aliases :', aliases
print ' Addresses:', addresses
except socket.error as msg:
print 'ERROR:', msg
執行結果:
# python socket_gethostbyname_ex.py
homer
ERROR: [Errno -2] Name or service not known
www
ERROR: [Errno -2] Name or service not known
www.python.org
Hostname: www.python.org
Aliases : []
Addresses: ['82.94.164.162']
nosuchname
ERROR: [Errno -2] Name or service not known
socket.getfqdn([name])
返回一個完全的域名。如果名字被省略或爲空,默認爲本地主機。首先使用gethostbyaddr()返回的主機名來查找名稱,然後是主機的別名。被選中的第一名稱,其中包括一個時期。如果沒有完全合格的域名,返回的gethostname()返回的主機名。
import socket
for host in [ 'homer', 'www' ]:
print '%6s : %s' % (host, socket.getfqdn(host))
執行結果:
$ python socket_getfqdn.py
homer : homer.hellfly.net
www : homer.hellfly.net
socket.gethostbyaddr(ip_address)
返回一個三元組(hostname,aliaslist,ipaddrlist),支持IPv4和IPv6。
import socket
hostname, aliases, addresses = socket.gethostbyaddr('172.23.191.53')
print 'Hostname :', hostname
print 'Aliases :', aliases
print 'Addresses:', addresses
執行結果:
$ python socket_gethostbyaddr.py
Hostname : homer.hellfly.net
Aliases : [‘8.1.168.192.in-addr.arpa’]
Addresses: [‘192.168.1.8’]
試試google
# pingwww.google.com.hk
PINGwww.google.com.hk (173.194.65.103) 56(84) bytes of data.
64 bytes fromee-in-f103.1e100.net (173.194.65.103): icmp_seq=1 ttl=35 time=421 ms
# python
Python 2.6.6(r266:84292, Jun 18 2012, 14:10:23)
[GCC 4.4.620110731 (Red Hat 4.4.6-3)] on linux2
Type"help", "copyright", "credits" or"license" for more information.
>>>import socket
>>>socket.gethostbyaddr('173.194.65.103')
('ee-in-f103.1e100.net',[], ['173.194.65.103']
可見返回地址'ee-in-f103.1e100.net'不等於’www.google.com.hk’,不知google被和諧的時候是否可以用'ee-in-f103.1e100.net'來訪問,百度實在太垃圾了。
11.1.1.2 查找服務信息:
socket.getservbyname(servicename[,protocolname])
翻譯的互聯網服務名稱和協議的名稱爲端口號。協議名稱,如果有,應該是“TCP”或“UDP”,否則,任何協議都將匹配。
import socket
from urlparse import urlparse
for url in [ 'http://www.python.org',
'https://www.mybank.com',
'ftp://prep.ai.mit.edu',
'gopher://gopher.micro.umn.edu',
'smtp://mail.example.com',
'imap://mail.example.com',
'imaps://mail.example.com',
'pop3://pop.example.com',
'pop3s://pop.example.com',
]:
parsed_url = urlparse(url)
port = socket.getservbyname(parsed_url.scheme)
print '%6s : %s' % (parsed_url.scheme,port)
執行結果:
# python socket_getservbyname.py
http : 80
https : 443
ftp : 21
gopher : 70
smtp : 25
imap : 143
imaps : 993
pop3 : 110
pop3s : 995
socket.getservbyport(port[,protocolname])
翻譯的Internet端口號和協議名稱爲服務名,服務。協議名稱,如果有,應該是“TCP”或“UDP”,否則,任何協議都將匹配。
import socket
import urlparse
for port in [ 80, 443, 21, 70, 25, 143,993, 110, 995 ]:
print urlparse.urlunparse(
(socket.getservbyport(port),'example.com', '/', '', '', '')
)
執行結果:
# python socket_getservbyport.py
http://example.com/
https://example.com/
ftp://example.com/
gopher://example.com/
smtp://example.com/
imap://example.com/
imaps://example.com/
pop3://example.com/
pop3s://example.com/
socket.getprotobyname(protocolname)
翻譯的互聯網協議名(例如,“ICMP”),爲適合的socket()函數的第三個參數(可選)的常量。這通常只是“原始”模式(SOCK_RAW)需要,普通socket模式會自動選擇正確的協議,設置爲0或者或略就可。
import socket
def get_constants(prefix):
"""Create a dictionarymapping socket module
constants to their names.
"""
return dict( (getattr(socket, n), n)
for n in dir(socket)
if n.startswith(prefix)
)
protocols = get_constants('IPPROTO_')
print protocols
for name in [ 'icmp', 'udp', 'tcp' ]:
proto_num = socket.getprotobyname(name)
print proto_num
const_name = protocols[proto_num]
print '%4s -> %2d (socket.%-12s =%2d)' % \
(name, proto_num, const_name,getattr(socket, const_name))
執行結果:
# python socket_getprotobyname.py
{0: 'IPPROTO_IP', 1: 'IPPROTO_ICMP', 2:'IPPROTO_IGMP', 4: 'IPPROTO_IPIP', 6: 'IPPROTO_TCP', 8: 'IPPROTO_EGP', 12:'IPPROTO_PUP', 17: 'IPPROTO_UDP', 22: 'IPPROTO_IDP', 29: 'IPPROTO_TP', 41:'IPPROTO_IPV6', 43: 'IPPROTO_ROUTING', 44: 'IPPROTO_FRAGMENT', 46:'IPPROTO_RSVP', 47: 'IPPROTO_GRE', 50: 'IPPROTO_ESP', 51: 'IPPROTO_AH', 58:'IPPROTO_ICMPV6', 59: 'IPPROTO_NONE', 60: 'IPPROTO_DSTOPTS', 103:'IPPROTO_PIM', 255: 'IPPROTO_RAW'}
1
icmp -> 1 (socket.IPPROTO_ICMP = 1)
17
udp -> 17 (socket.IPPROTO_UDP = 17)
6
tcp -> 6 (socket.IPPROTO_TCP = 6)
協議的數值是標準化的,在socket中以前綴IPPROTO_開頭的常量存在。
11.1.1.3 查找服務器地址:
socket.getaddrinfo(host, port, family=0,socktype=0, proto=0, flags=0)
翻譯host/port參數爲成一個5元組(包含創建連接至該服務的socket的必需參數)構成的序列。主機是一個域名,IPv4/v6的地址“或None。端口是一個字符串:服務的名稱,如'http',數字端口號或None。host和port都爲None,傳遞的就是底層cAPI的NULL。
family可以填寫socktype和proto參數以縮寫返回地址的列表。0爲最大範圍。可以有1或者多個AI_*常量構成。比如AI_NUMERICHOST將禁止域名解析。如果主機是一個域名將引發一個錯誤。
該函數返回一個具有以下結構的5- 元組列表:
(family, socktype, proto, canonname,sockaddr)
family,socktype,proto都是傳遞給的整數。如果flags包含AI_CANONNAME,canonname表示canonical名,否則就爲空。sockaddr是描述套接字地址的元組:AF_INET返回爲(address,port),AF_INET6返回爲AF_INET6。其格式依賴於返回的家庭((地址,port)2AF_INET,元組(地址,端口流量信息,範圍ID)四元AF_INET6),並會傳遞給Socket.connect()的方法。
下例爲www.python.org的地址信息。
>>> import socket
>>>socket.getaddrinfo("www.python.org", 80, 0, 0, socket.SOL_TCP)
[(10, 1, 6, '', ('2001:888:2000:d::a2',80, 0, 0)), (2, 1, 6, '', ('82.94.164.162', 80))]
import socket
def get_constants(prefix):
"""Create a dictionary mapping socket module
constants to their names.
"""
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'):
# Unpack the response tuple
family, socktype, proto, canonname, sockaddr = response
print 'Family :',families[family]
print 'Type :',types[socktype]
print 'Protocol :',protocols[proto]
print 'Canonical name:', canonname
print 'Socket address:', sockaddr
執行結果:
# python socket_getaddrinfo.py
Family : AF_INET6
Type : SOCK_STREAM
Protocol : IPPROTO_TCP
Canonical name:
Socket address: ('2001:888:2000:d::a2', 80, 0, 0)
Family : AF_INET6
Type : SOCK_DGRAM
Protocol : IPPROTO_UDP
Canonical name:
Socket address: ('2001:888:2000:d::a2', 80, 0, 0)
Family : AF_INET6
Type : SOCK_STREAM
Protocol :
Traceback (most recent call last):
File "socket_getaddrinfo.py", line 32, in <module>
print 'Protocol :',protocols[proto]
KeyError: 132
設置了AI_CANONNAME的實例:
import socket
def get_constants(prefix):
"""Create a dictionary mapping socket module
constants to their names.
"""
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.doughellmann.com', 'http',
socket.AF_INET, # family
socket.SOCK_STREAM, # socktype
socket.IPPROTO_TCP, # protocol
socket.AI_CANONNAME, # flags
):
# Unpack the response tuple
family, socktype, proto, canonname, sockaddr = response
print 'Family :',families[family]
print 'Type :',types[socktype]
print 'Protocol :',protocols[proto]
print 'Canonical name:', canonname
print 'Socket address:', sockaddr
執行結果:
# python socket_getaddrinfo_extra_args.py
Family : AF_INET
Type : SOCK_STREAM
Protocol : IPPROTO_TCP
Canonical name: www.doughellmann.com
Socket address: ('208.97.185.20', 80)
11.1.1.4 IP地址表示:
C網絡程序使用structsockaddr來表示IP地址,是二進制,而不是python中常見的二進制。IPv4在python和c之間的切換使用inet_aton()和inet_ntoa()。
socket.inet_aton(ip_string)
把字符串格式的ip地址轉換爲c語言格式,比如‘192.168.1.1’->c0a80101。支持IPV6需要使用inet_pton。
socket.inet_ntoa(packed_ip)和上面的剛好相反:
import binascii
import socket
import struct
import sys
for string_address in [ '192.168.1.1','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)
注意hexlify是返回2進制數據的16進製表示。
執行結果:
# python socket_address_packing.py
Original: 192.168.1.1
Unpacked: 192.168.1.1
Original: 127.0.0.1
Unpacked: 127.0.0.1
適用於ipv6的例子:
import binascii
import socket
import struct
import sys
string_address ='2002:ac10:10a:1234:21e:52ff:fe74:40e'
packed = socket.inet_pton(socket.AF_INET6,string_address)
print 'Original:', string_address
print 'Packed :', binascii.hexlify(packed)
print 'Unpacked:',socket.inet_ntop(socket.AF_INET6, packed)
執行結果:
# python socket_ipv6_address_packing.py
Original:2002:ac10:10a:1234:21e:52ff:fe74:40e
Packed : 2002ac10010a1234021e52fffe74040e
Unpacked:2002:ac10:10a:1234:21e:52ff:fe74:40e
注意IPV6本身就是二進制表示的,且inet_pton()and inet_ntop()只適用於linux平臺。