Python網路編程中的套接字名和DNS解析。
距離上一次TCP的文章,這一次要講的是套接字名和DNS,並且還會涉及到網路數據的發送接受和網路錯誤的發生和處理。
下面說套接字名,在創建和部署每個套接字對象時總共需要做5個主要的決定,主機名和IP地址是其中的最後兩個。
一般創建和部署套接字的步驟如下:
import sockets = socket.socket(socket.AF_INET, socket.SOCK_DREAM)s.bind((localhost, 1088))
可以看到我們指定了4個值,兩個用來做對套接字做配置,另外兩個提供bind()調用所需要的地址。第5個坐標則是因為socket()方法有第3個可選參數。
下面我們依次說這5個參數。
首先,第1個參數是地址族的選擇,某個特定的機器可能連接到多個不同類型的網路。對地址族的選擇指定了想要進行通信的網路類型。
這裡面選擇的是AF_INET作為地址族,即在IP網路層編寫程序。這樣對與Python程序員來說也是最有益的。
第2個參數就是套接字類型,然後我們解釋一下套接字類型,儘管TCP和UDP是AF_INET協議族特有的,但是套接字介面的設計者決定基於數據報的套接字這一宏觀的概念創建一些更通用的名字,這就是SOCK_DGRAM,而提供可靠傳輸與流量控制的數據流概念我們用SOCK_STREAM。這兩個符號就可以覆蓋不同地址族的很多協議了。
socket()調用的第3個參數是協議,但是一旦確定了協議族和套接字類型,可能使用的協議範圍就被縮到了一個主要的選項。如果設置成0。在IP上使用流的時候自動選擇TCP,設置數據報的時候自動選擇UDP。
至於第4個和第5個參數就是IP地址和埠號。
當然現在如果要是使用IPV6地址族的話,那你可以看看AF_INET6。
下面說一下現代地址解析,使用socket模塊中的一些舊式程序來解決地址問題的方法是相當瑣碎的。
而下面要說getaddrinfo()這個工具,這個工具除了一些特定的工作,否則這個函數將是我們用來將用戶指定的主機名和埠號轉換為可供套接字方法使用的地址時所需的唯一方法。
這個工具還可以用來為伺服器綁定埠,然後連接服務或者是請求規範主機名。當然這是3個最重要getaddrinfo()的標記操作。
至於其他的標記,不同的操作系統上可用標記有所不同,但是也有一些是跨平台的。比如AI_ALL,AI_NUMERICHOST和AI_NUMERICSERV等等。
至於更詳細的一些東西,可以看相關的文檔。
下面這段代碼是把上面內容結合起來,設計了一個簡單的例子。下面是使用getaddrinfo()創建並連接套接字。
import argparse, socket, sysdef 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] # 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)
下面這三點要引起注意:
- 代碼中沒有提到使用IP協議,也沒有提到TCP作為傳輸方式。如果用戶正好輸入了一個主機名,而系統認為該主機AppleTalk連接的。
- getaddrinfo()調用失敗會引起一個特定的名稱服務錯誤。而不是在腳本末尾檢測的普通網路故障,這個Python把這個錯誤叫做gaierror。
- 我們並沒有為socket()構造函數傳入3個單獨的參數。我們使用星號傳入了參數列表。表示socket_args列表中的3個元素會被當作3個單獨的參數傳入構造函數中。使用實際返回的地址時的做法則恰恰相反。
下面說一下DNS解析。人們習慣記憶域名,但機器間互相只認IP地址,域名與IP地址之間是多對一的關係,一個ip地址不一定只對應一個域名,且一個域名只可以對應一個ip地址,它們之間的轉換工作稱為域名解析,域名解析需要由專門的域名解析伺服器來完成,整個過程是自動進行的。
下面給出一個包含遞歸的簡單DNS查詢。
import argparse, dns.resolverdef 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)
按照列印的順序,每行列印出來的鍵如下。
- 查詢的名稱
- 能夠將該名稱存入緩存的有效時間,以s為單位
- 類,比如返回互聯網地址響應的IN
- 記錄的類型,常見的比如表示IPV4地址的A,IPV6地址的AAAA
- 最後是數據
下面給出最後的一段代碼,解析電子郵件域名。解析郵箱域名是多數Python程序中對原始DNS查詢的一個應用。
下面拿郵箱域名解析規則RFC5321來說,如果存在MX記錄,則必須嘗試與SMTP來進行通信。如果SMTP伺服器沒有響應,就返回一個錯誤,如果有響應就進入消息隊列,按照優先順序順序從小到大嘗試發起連接。如果提供了A和AAAA記錄,就直接向對應地址發起連接。如果沒有,但是給出了CNAME,就按照對應域名的MX記錄和A記錄。
import argparse, dns.resolverdef resolve_hostname(hostname, indent=): "Print an A or AAAA record for `hostname`; follow CNAMEs if necessary." indent = indent + answer = dns.resolver.query(hostname, A) if answer.rrset is not None: 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, or CNAME records for, hostname)def resolve_email_domain(domain): "For an email address `name@domain` find its mail server IP addresses." 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(name) else: print(This domain has no explicit MX records) print(Attempting to resolve it as an A, AAAA, or CNAME) resolve_hostname(domain)if __name__ == __main__: parser = argparse.ArgumentParser(description=Find mailserver IP address) parser.add_argument(domain, help=domain that you want to send mail to) resolve_email_domain(parser.parse_args().domain)
上述代碼就是演算法可能的實現過程,進行一系列的查詢,該演算法得到了可能的目標地址,然後列印出來決定,像這樣不斷調整策略並返回地址,而不簡單的列印出來,就可以實現一個Python的郵件分發工具。將郵件發送到遠程地址。
到這裡,文章就結束了,下一篇文章就是說網路數據和網路錯誤的處理。
寒假到這裡,也就快過了一半了,大家加油。
最後的最後,大家注意身體。
提前祝大家,新年快樂。
推薦閱讀:
※Python做selenium自動化測試,HTMLTestRunner無法生成
※Python3入門精講,十五分鐘認識Python基礎
※python中多進程+協程的使用
※2017學什麼編程語言好找工作?