1.主机名与套接字。
-
主机名。
我们浏览网页时很少直接输入原始IP地址,大多时候都是输入主机名。如:www.baidu.com。当我们输入主机名时,我们的请求并没有直接转到所请求的服务器而是转到了自己的DNS服务器,DNS服务器通过计算再将你的主机名解析成原始的IP地址,如将百度主机名解析成原始IP:115.239.210.27。DNS解析主机名的过程叫做域名解析,世界各地使用该系统来对名称查询做出响应的服务期集合提供了域名服务(DNS)。 -
套接字。
为了查询方便这里列出了所有需要提供某种形式的套接字名作为参数的套接字方法。- mysocket.accept():该方法由TCP流的监听套接字调用。它会返回一个二元组,二元组的第一项是新建的连接至远程地址的套接字,第二项是已连接的远程地址。
- mysocket.bind(address):该方法将特定的本地地址分配给套接字,即给服务器绑定地址和端口。
- mysocket.connect(address):该方法说明,通过套接字发送的数据会被传输到特定的远程地址。它会设置一个默认地址,以供send()和recv()使用。
- mysocket.getpeername():该方法返回了与套接字连接的远程地址。
- mysocket.getsockname():该方法返回了套接字本身的本地端点地址。
- mysocket.recvfrom():用于UDP套接字,该方法返回一个二元组,包含了返回数据的字符串和数据的来源地址。
- mysocket.sendto():未连接的UDP端口使用该方法向特定远程地址发送数据。
2.套接字的5个坐标。
创建和部署套接字的过程:
import socket
sock = socket.socket(socket.AF_INET,socket.SOCK_DGRAM,0)
sock.bind(('127.0.0.1',port))
这五个坐标就是socket()和bind()各自的参数,它们各自的意义是AF_INET表示协议族,SOCK_DGRAM数据报类型 SOCK_DGRAM它表示在IP网络上使用UDP协议,0表示自动选择协议由于已经设定了前两个参数,协议已经确定所以这个参数一般不设定。最后两个bind()参数IP地址和端口号。
3.现代地址解析。
getaddrinfo()函数是socket模块中涉及地址的众多操作之一。除非一些特定工作,否则这个函数可能是我们用来将用户指定主机名和端口号转换为可供套接字方法使用的地址时所需的唯一方法。下面是对getaddrinfo()函数得简单说明。
图中infolist变量包含了用来创建套接字发起一个连接需要的所有信息。它提供了地址族、类型、协议、规范名称、地址信息。从变量的返回值中我们可以看出发起该连接需要创建一个使用IPPROTO_TCP
(协议代号6)的SOCK_STREAM套接字(套接字类型1)。我们还将getaddrinfo()返回值的前三项作为socket()构造函数参数,然后使用返回值的第五项作为传入地址作为connect()的参数。而提供给getaddrinfo()函数的参数一般除了允许提供主机名之外,还允许提供www这样的符号作为端口号(不是数字)。
4.getaddrinfo()函数的用法。
-
使用getaddrinfo()为服务器绑定端口:将主机名设为None,但提供端口号和套接字类型,如果某个字段位数字使用0来表示通配符。
这里一共两个查询,第一个查询使用字符串作为端口标识符,第二个使用数字。第一个查询的目的是,想知道如果使用TCP来支持SMTP数据传输的话,应该使用bind()把套接字绑定在那个地址。返回值是一个通配符地址表示可以绑定在主机的任何IPv4及IPv6接口上。
上图演示的是如何通过bind()绑定到本机的一个特定IP地址,并指定主机名。如果是使用IPv4地址发起连接那么久只会接受通过IPv4发起的连接;而如果使用符号名的话,IPv4和IPv6的本地名在该机器上均可用。 -
使用getaddrinfo()连接服务:查询服务时,可以用一个空字符串来表示要通过自环接口来连接回本机,也可提供一个IPv4地址、IPv6地址或者是主机名的字符串来指定目标主机。
返回值中包含了通过TCP方式连接ftp.wems.net主机FTP端口的所有方式。 -
使用getaddrinfo()请求规范主机名:因为对规范主机名的查询会将IP地址映射到一个主机名,而不是将主机名映射到IP地址,所以称之为反向DNS查询。这种方法只在IP拥有者正好定义了反向主机名时才适用。
-
其他getaddrinfo()标记
- AI_ALL:它会将IPv4地址重写为与之对应的IPv6网络。
- AI_NUMERICHOST:禁止对主机名参数以cern.ch这样的文本的方式进行解析,只会解析IPv4地址和IPv6地址。
- AI_NUMERICSERV:该选项禁用www这类符号形式的端口名。
-
原始的名称服务。
- 这两个调用返回当前机器的主机名。
- 这两个调用能够对IPv4主机名和IPv4地址进行相互转换。
- 这三个调用通过操作系统已知的符号名查询协议号以及端口号。
- 获取主机IP地址。
- 这两个调用返回当前机器的主机名。
5.在代码中使用getaddrinfo()。
代码:
#!/usr/bin/python
#coding:utf-8
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)
except socket.gaierror as e:
print('Name service failure:',e.args[1])
sys.exit(1)
info = infolist[0]
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)
测试结果:
注意:
- 该脚本完全通用,根据用户输入的主机名脚本会自动选择连接方式。
- getaddrinfo()调用失败会引起一个特定的名称服务错误——gaierror错误。
- 我们使用星号传入了参数列表,表示socket_args列表中的3个元素会被当作3个单独的参数传入到构造函数中。
6.DNS协议。
域名系统(DNS)是成千上万互联网主机相互协作,对主机名与IP地址映射关系查询做出响应的一种机制。假设我们在机器上输入一个www.python.org的域名,我们的机器之前从来没有访问过它,运行浏览器我们的机器无法从本地缓存上发现与该域名对应的IP地址。这时计算机会查询本地DNS服务器,它通常会发送一个基于UDP的DNS查询数据包。现在,问题就交给真正的DNS服务器了。首先它会检查它自己最近查询域名的缓存,看看www.python.org是否最近几分钟或者几小时内由其他机器向DNS服务器查询过,如果有会立即返回IP地址否则需要DNS服务器从头开始查询与主机名对应的IP地址。查询过程需要4次独立的网络往返。我们的机器向我们自己的DNS服务器发送请求,并获取响应。而为了得到查询结果,我们的DNS服务器必须进行递归查询。
7.使用python进行DNS查询。
需要安装dnspython3的第三方DNS库。
pip install dnspython3 安装方法
该库使用其自己的方法来获取我们Windows或POSIX(可移植操作系统接口)操作系统正在使用的域名服务器,然后请求这些服务器代表其进行递归查询。
代码:
#!/usr/bin/python
#coding:utf-8
import argparse,dns.resolver
def lookup(name):
for qtype in 'A','AAAA','CNAME','MX','NS':
'''
ns.resolver.query()函数参数为域名和记录类型
AAAA记录用于IPV6
'''
answer = dns.resolver.query(name,qtype,raise_on_no_answer=False)
if answer.rrset is not None:
print(answer.rrset)
if __name__ == '__main__':
paraer = argparse.ArgumentParser(description="Resolve a name using DNS")
paraer.add_argument('name',help='name that you want to look up in DNS')
lookup(paraer.parse_args().name)
测试结果:
注意:
- A记录:将主机转换成IPv4地址。
- AAAA记录:将主机转换成IPv6地址。
- CNAME记录:通常称别名指向。您可以为一个主机设置别名。比如设置test.mydomain.com用来指向一个主机www.rddns.com那么以后就可以用test.mydomain.com来代替访问www.rddns.com了。
- MX记录:邮件交换记录,实现域名间的映射。
- NS记录:解析服务器记录。用来表明由哪台服务器对该域名进行解析。
8.解析邮箱域名。
代码:
#!/usr/bin/python
#coding:utf-8
import argparse,dns.resolver
def resolve_hostname(hostname,indent=''):
indent = indent + ' '
answer = dns.resolver.query(hostname,'A')
if answer.rrset is not answer:
for record in answer:
print(indent,hostname,'has A address',record.address)
return
answer = dns.resolver.query(hostname,'AAAA')
if answer.rrset is not None:
for record in answer:
print(indent,hostname,'has AAAA address',record.address)
return
answer = dns.resolver.query(hostname,'CNAME')
if answer.rrset is not None:
record = answer[0]
cname = record.address
print(indent,hostname,'is a CNAME alias for',cname)
resolve_hostname(cname,indent)
return
print(indent,'ERROR: no A,AAAA,CNAME records for',hostname)
def resolve_email_domain(domain):
try:
answer = dns.resolver.query(domain,'MX',raise_on_no_answer=False)
except dns.resolver.NXDOMAIN:
print('Error:NO such domain',domain)
return
if answer.rrset is not None:
records = sorted(answer,key=lambda record:record.preference)
print('This domain has', len(records), 'MX records')
for record in records:
name = record.exchange.to_text(omit_final_dot = True)
print('Priority',record.preference)
resolve_hostname(domain)
if __name__ == '__main__':
paraer = argparse.ArgumentParser(description="Find mailserver IP address")
paraer.add_argument('domain', help='domain that you want to send mail to')
resolve_email_domain(paraer.parse_args().domain)
测试结果: