套接字名
域名服務(DNS, Domain Name Service): 域名和真實IP地址的映射尚揣。
套接字的5個坐標
- 地址族(Adress Family),比如
AF_INET
,AF_UNIX
- 套接字類型(Socket Type), 比如
SOCK_DGRAM
,SOCK_STREAM
- 協議 (protocol), 前兩個確定后協議可選很少求豫,所以一般不寫,或者寫0反肋,表示自動選擇七咧,比如
IPPROTO_TCP
,IPPROTO_UDP
- 主機名
- 端口號
IPV6的特殊情況:
IPV6中套接字名不止包括主機名、端口號靶庙,還包括“流”信息问畅,“范圍”標識等額外坐標。
現代地址解析-getaddrinfo()的使用
套接字需要的5個坐標都可以得到六荒。生產環(huán)境代碼很少使用 AF_INET
這類socket模塊的常量护姆,采用的是 getaddrinfo()
>>> from pprint import pprint // 打印元組列表好看
// 調用
>>> infolist = socket.getaddrinfo('gatech.edu', 'www')
// 等同于
>>> infolist = socket.getaddrinfo('gatech.edu', 80)
// 返回
>>> pprint(infolist)
[(2, 1, 6, '', ('130.208.77.55', 80)),
(2, 2, 17, '', ('130.208.77.55', 80)]
>>> info = infolist[0]
// 元組前三項分別為地址族、套接字類型掏击、協議卵皂,用來初始化套接字
>>> s = socket.socket(*info[0:3])
// 元組第5項為(主機名,端口), 可用來調用
>>> s.connect(info[4])
使用 getaddrinfo()
為服務器綁定端口
參數:getaddrinfo(host, port, address_family, sock_type, protocl, flag)
如果某個字段為數字砚亭,可以使用0代表通配符
場景:正在創(chuàng)建套接字或者希望客戶端從一個可預計的地址連接至其他主機, 需要bind()
灯变。
用法:把主機名設為 None
, 127.0.0.1
, localhost
等殴玛,提供端口號、套接字類型等柒凉。
>>> from socket import getaddrinfo
// 想用 TCP 來支持 smtp 數據傳輸的話族阅,應該通過 bind() 把套接字綁定到哪個地址
>>> getaddrinfo(None, 'smtp', 0, socket.SOCK_STREAM, 0, socket.AI_PSASSIVE)
// 返回告訴我們可以bind到本機的任何IPV4或IPV6地址
[(2, 1, 6, '', ('0.0.0.0', 25)), (10, 1, 6, '', ('::', 25, 0, 0))]
使用 getaddrinfo()
連接服務
場景:connect()
或 sendto()
參數:
-
AI_ADDRCONFIG
標記: 過濾計算機無法連接的地址 -
AI_V4MAPPED
標記:本機只有IPV6,但是連接的服務只支持IPV4膝捞,指定后坦刀,會將服務的IPV4地址編碼為可用的IPV6地址。
用法:
>>> getaddrinfo('ftp.kernel.org', 'ftp', 0, socket.SOCK_STREAM, 0,
... socket.AI_ADDRCONFIG | socket.AI_V4MAPPED)
[(2, 1, 6, '', ('204.13.33.3', 21)),
(2, 1, 6, '', ('204.13.33.4', 21)]
返回的列表是有順序的蔬咬,經過了負載均衡鲤遥,一般選擇第一個。
使用 getaddrinfo()
請求規(guī)范主機名
反向DNS查詢:IP映射到主機名林艘。
風險:
- IP地址擁有者可以隨意設置主機名盖奈。因此要驗證。
- 耗時狐援。
用法:設置 AI_CANNONNAME
標志, 返回項第四項為規(guī)范主機名钢坦。
其他 getaddrinfo()
標記
總結下之前的:
-
AI_PASSIVE
標記:被動的,用于bind()
啥酱,節(jié)點為null時爹凹,返回通配地址,否則返回回環(huán)地址镶殷。 -
AI_ADDRCONFIG
標記: 過濾計算機無法連接的地址 -
AI_V4MAPPED
標記:本機只有IPV6禾酱,但是連接的服務只支持IPV4,指定后绘趋,會將服務的IPV4地址編碼為可用的IPV6地址颤陶。 -
AI_CANNONNAME
標記, 返回項第四項為規(guī)范主機名。
其他:
-
AI_ALL
: 與AI_V4MAPPED
結合使用陷遮,表示包含已知的與目標主機的所有地址滓走。 -
AI_NUMERICHOST
: 調用的節(jié)點名必須是 IPV4 或者 IPV6 地址,不是字符串 -
AI_NUMRICSERV
: 禁用"www"這種端口帽馋,只用"80"這種闲坎。
在 getaddrinfo()
之前經常使用的原始名稱服務程序
// 返回主機名
socket.gethostname()
socket.getfqdn()
// 對IPV4主機名和IP地址互換
socket.gethostbyname('cern.h')
socket.gethostbyaddr('128.189.22.1')
// 查詢協議號和端口號
>>> socket.getprotobyname('UDP')
17
>>> socket.getservbyname('www')
80
>>> socket.getservbyport(80)
'www'
// 獲取機器主IP地址
socket.gethostbyname(socket.getfqdn())
// 調用都可能失敗,要做好二手準備茬斧。
代碼
// 使用 `getaddrinfo()`創(chuàng)建并連接套接字
#!/usr/bin/env python3
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter04/www_ping.py
# Find the WWW service of an arbitrary host using getaddrinfo().
import argparse, socket, sys
def connect_to(hostname_or_ip):
try:
infolist = socket.getaddrinfo(
hostname_or_ip, 'www', 0, socket.SOCK_STREAM, 0,
socket.AI_ADDRCONFIG | socket.AI_V4MAPPED | socket.AI_CANONNAME,
)
// getaddrinfo失敗后特殊錯誤 gaierror
except socket.gaierror as e:
print('Name service failure:', e.args[1])
sys.exit(1)
info = infolist[0] # per standard recommendation, try the first one
socket_args = info[0:3]
address = info[4]
s = socket.socket(*socket_args)
try:
s.connect(address)
except socket.error as e:
print('Network failure:', e.args[1])
else:
print('Success: host', info[3], 'is listening on port 80')
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Try connecting to port 80')
parser.add_argument('hostname', help='hostname that you want to contact')
connect_to(parser.parse_args().hostname)
DNS 協議
- 目的:解析主機名腰懂,返回IP地址
- 標準:RFC 1034和 RFC 1035
- 傳輸層協議:UDP/IP 與 TCP/IP
- 端口號:53
- 庫:第三方,包括 dnspython3
DNS查詢只有在緩存项秉、多播DNS等失敗后才會啟動DNS服務器绣溜,畢竟這很耗時。
使用 Python 進行 DNS 查詢
// 一個包含遞歸的簡單 DNS 查詢
#!/usr/bin/env python3
# Foundations of Python Network Programming, Third Edition
# https://github.com/brandon-rhodes/fopnp/blob/m/py3/chapter04/dns_basic.py
# Basic DNS query
import argparse, dns.resolver
def lookup(name):
for qtype in 'A', 'AAAA', 'CNAME', 'MX', 'NS':
answer = dns.resolver.query(name, qtype, raise_on_no_answer=False)
if answer.rrset is not None:
print(answer.rrset)
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Resolve a name using DNS')
parser.add_argument('name', help='name that you want to look up in DNS')
lookup(parser.parse_args().name)
answer.rrset
返回格式:查詢名稱娄蔼,有效時間怖喻,類型(IN, 表示互聯網地址響應), 記錄的類型qtype(A: IPV4, AAAA: IPV6, NS: name service, MX: 郵件服務器)