今天從Python/ target=_blank class=infotextkey>Python的角度來聊下計算機網絡這行基礎中的基礎的話題:網絡和IP地址計算(注:本文里的IP指的是IPv4,不涉及IPv6)。相信幾乎每位網工讀者在平時的工作和學習中都用過類似下圖的在線網絡和IP地址計算器吧:
這類前人(或者說碼農們)造出的輪子的確很好用,但是很少有網工明白它們背后的工作原理(也就是代碼是怎么寫出來的)。作為有志成?.NETDevOps Engineer的我們有必要深入的從代碼的角度來學習一下,不妨自己也用Python從零寫一個交互式的網絡和IP地址計算器,重新造一遍輪子,一來可以溫故知新,二來可以幫助我們更深入的了解二進制和十進制在Python里是怎么玩的。該交互式計算器的作用是讓用戶輸入一個合法的IP地址及子網掩碼,然后根據用戶輸入的信息自動給出用戶查詢的網段的網絡IP、廣播IP、網段內可用的IP地址數、反掩碼以及用戶輸入的子網掩碼對應的“/”格式的掩碼位(比如用戶輸入的掩碼是255.255.128.0,計算器會自動在結果中給出/17的掩碼位)。
因為是所有網工必須掌握的基本功,為了節約篇幅,下面我只高度概括一下網絡和IP地址計算的理論要點,我們重點要關注的是如何在Python中實現它們(所有演示我都將在解釋器里實時完成,讓讀者更清楚的看到十進制和二進制的相互轉換在Python中是怎樣完成的)大致可以歸納為A,B,C,D,E總共5個點,分述如下:
A.
我們知道任何一個合法的IP地址和子網掩碼都可以用32位的二進制(binary)表示,這32位二進制又被分為4個八位位組(octet),比如192.168.1.1用二進制可以寫成11000000.10101000.00000001.00000001,這個轉換步驟在Python中實現的方法如下:
- 這里我們用字符串自帶的split()函數將ip地址(字符串‘192.168.1.1')轉換成列表ip_octets,然后創建一個空列表ip_octets_binary,隨后用for循環遍歷ip_octets里的元素,將它們每個用bin()函數轉換成二進制形式,然后一一寫入剛才創建的空列表ip_octets_binary里面。
- 關于binary_octet = bin(int(octet)).lstrip('0b'),bin()只能將數據類型為整數的十進制數轉換成二進制,因此這里我們要先將字符串用int()轉換成整數后再來調用bin()函數,而bin()函數本身會在轉化后的二進制數字前面加上'0b',我們必須調用lstrip('0b')將其拿掉,演示如下:
- 再來看ip_octets_binary.Append(binary_octet.zfill(8))中的zfill(8),它的作用是自動幫我們填充八位數的二進制數,什么意思呢?比如我們有個IP地址192.168.0.1,它的第三個八位組為0,寫成二進制的話應該為00000000,如果我們將0用bin()轉換成二進制后會怎么樣呢?演示如下:
是不是只得到了一位數的二進制數0?加上zfill(8)后即得到八位組的二進制00000000,效果如下:
同樣的代碼也適用于子網掩碼,比如在Python中要將255.255.255.0這個掩碼轉換成二進制形式,代碼可以這么寫:
B.
知道如何在Python里將十進制的IP地址和子網掩碼轉換成二進制后,我們再來看下如何將二進制的IP地址和子網掩碼轉換回十進制(代碼接續前文):
- 這里只講一點,在Python中我們可以借助int()函數里的2這個參數將數據類型為字符串的二進制數字轉換成數據類型為整數的十進制數字,舉例如下:
- 同樣的道理,我們可以將二進制形式的子網掩碼轉換回十進制:
C.
我們知道要算出一個網段內有多少可用的IP地址需要知道該網段的子網掩碼以二進制表達時里面有多少個0 (number of zeros,在Python中我們將其賦值給變量no_of_zeros),然后套用公式2 ** no_of_zeros - 2即可算出,比如這里給定的子網掩碼255.255.255.0,將其轉化為二進制為1111111.1111111.11111111.00000000,總共8個0, 那么2**8-2 = 254,即為我們要的結果,這個運算過程在Python中的計算方式如下(代碼接前文):
- 這里我們用abs()這個絕對值函數來計算有多少可用的IP地址,原因是當子網掩碼為/32 (255.255.255.255)時,no_of_zeros = 0,如果不用abs()的話, 2 ** 0 - 2 結果為負1,用abs()則可以將其轉換成正1, 演示如下:
D.
我們知道網絡IP和廣播IP是兩個很重要的概念,在給定一個IP地址及其子網掩碼后,計算該網段的網絡IP和廣播IP的方法想必大家都知道,即將IP地址和子網掩碼分別轉換成二進制,然后將兩者對比,看子網掩碼的二進制有多少個1,那么IP地址的二進制就從左至右保留多少位,剩下的部分全部以0填充,即可得到網絡IP的二進制地址,如果剩下部分全部以1填充,則得到廣播IP的二進制地址(這里就不畫圖演示了,這些都是網工最最最基礎的知識點,不懂的回去把CCENT或CCNA的書重新翻出來讀)。下面我們在Python中演示如何實現找出一個指定IP所在網段的網絡IP和廣播IP(代碼接前文,以前文給定的IP地址192.168.1.1和子網掩碼255.255.255.0為例):
- 在使用上面提到的填充0的方法得到了網絡IP的二進制地址(11000000101010000000000100000000)后,為了將它轉換成四個八位組的十進制形式,這里我們巧用for循環配合range(0,32,8)來將該網絡IP的二進制地址切成四段,每段含8個二進制數字,作為元素被依次添加進net_ip_octets這個空列表,最后使用帶參數2的int()函數將它們轉換成十進制,然后再將這四個十進制數字作為元素依次添加進net_ip_address這個空列表,最后配合".".join()將給列表轉化為字符串,即得到了網絡IP:192.168.1.0
依葫蘆畫瓢,從下面這段代碼中我們又得到了廣播IP: 192.168.1.255
E.
最后我們來談談反掩碼,所謂反掩碼就是將子網掩碼的二進制里的1換成0,將0換成1,比如255.255.255.0的二進制為11111111.11111111.11111111.00000000,它的反掩碼即為00000000.00000000.00000000.11111111,也就是0.0.0.255。在Python里我們可以這樣表示(代碼接上文):
最后來看下該交互式的網絡和IP地址計算器的最終代碼:
#coding=utf-8
import sys
try: while True: #判斷用戶輸入的IP是否符合規范,如果不規范則while循環反復詢問,直到用戶輸入正確IP地址為止。 ip_address = input("輸入要查詢的IP地址: ")
ip_octets = ip_address.split('.') #將IP地址用split()轉換成列表,該列表有4個元素,分別代表用戶輸入的IP地址的4個8位字段。
#0.0.0.0/8, 127.0.0.0/8, 169.254.0.0/16以及Class D這些保留IP地址均不是有效的IP
if (len(ip_octets) == 4) and (1 <= int(ip_octets[0]) <= 223) and (int(ip_octets[0]) != 127) and (int(ip_octets[0]) != 169 or int(ip_octets[1]) != 254) and (0 <= int(ip_octets[1]) <= 255 and 0 <= int(ip_octets[2]) <= 255 and 0 <= int(ip_octets[3]) <= 255):
break
else:
print("n不是有效的IP地址,請重新輸入n")
continue
masks = [255, 254, 252, 248, 240, 224, 192, 128, 0] #將所有有效的子網掩碼的十進制數字歸納進一個列表,用于驗證用戶輸入的子網掩碼是否合乎規范
while True: #判斷用戶輸入的子網掩碼是否符合規范,如果不規范則while循環反復詢問,直到用戶輸入正確子網掩碼為止。 subnet_mask = input("輸入子網掩碼: ")
mask_octets = subnet_mask.split('.') #將子網掩碼用split()轉換成列表,該列表有4個元素,分別代表用戶輸入的子網掩碼的4個8位字段。
#支持/0 - /32所有子網掩碼
if (len(mask_octets) == 4) and (int(mask_octets[0]) in masks) and (int(mask_octets[1]) in masks) and (int(mask_octets[2]) in masks) and (int(mask_octets[3]) in masks) and (int(mask_octets[0]) >= int(mask_octets[1]) >= int(mask_octets[2]) >= int(mask_octets[3])):
break
else:
print("n不是有效的子網掩碼,請重新輸入n")
continue
mask_octets_binary = [] for octet in mask_octets:
binary_octet = bin(int(octet)).lstrip('0b')
#print(binary_octet)
mask_octets_binary.append(binary_octet.zfill(8))
#print(mask_octets_binary)
binary_mask = "".join(mask_octets_binary)
#print(decimal_mask)
no_of_zeros = binary_mask.count("0")
no_of_ones = 32 - no_of_zeros
no_of_hosts = abs(2 ** no_of_zeros - 2) #當掩碼為/32時,2的0次方減1等于-1,需要用abs()函數將其轉換成正數1.
#print(no_of_zeros)
#print(no_of_ones)
#print(no_of_hosts)
wildcard_octets = [] for octet in mask_octets:
wild_octet = 255 - int(octet)
wildcard_octets.append(str(wild_octet))
#print(wildcard_octets)
wildcard_mask = ".".join(wildcard_octets)
#print(wildcard_mask)
ip_octets_binary = [] for octet in ip_octets:
binary_octet = bin(int(octet)).lstrip('0b')
#print(binary_octet)
ip_octets_binary.append(binary_octet.zfill(8))
#print(ip_octets_binary)
binary_ip = "".join(ip_octets_binary)
#print(binary_ip)
network_address_binary = binary_ip[:(no_of_ones)] + "0" * no_of_zeros
#print(network_address_binary)
broadcast_address_binary = binary_ip[:(no_of_ones)] + "1" * no_of_zeros
#print(broadcast_address_binary)
net_ip_octets = [] for bit in range(0, 32, 8):
net_ip_octet = network_address_binary[bit: bit + 8]
net_ip_octets.append(net_ip_octet)
#print(net_ip_octets)
net_ip_address = [] for each_octet in net_ip_octets:
net_ip_address.append(str(int(each_octet, 2)))
#print(net_ip_address)
network_address = ".".join(net_ip_address)
#print(network_address)
bst_ip_octets = [] for bit in range(0, 32, 8):
bst_ip_octet = broadcast_address_binary[bit: bit + 8]
bst_ip_octets.append(bst_ip_octet)
#print(bst_ip_octets)
bst_ip_address = [] for each_octet in bst_ip_octets:
bst_ip_address.append(str(int(each_octet, 2)))
#print(bst_ip_address)
broadcast_address = ".".join(bst_ip_address)
#print(broadcast_address)
print("n")
print("該網段的網絡地址為: %s" % network_address)
print("該網段的廣播地址為: %s" % broadcast_address)
print("該網段可用的IP地址數量為: %s" % no_of_hosts)
print("反掩碼: %s" % wildcard_mask)
print("掩碼位: %s" % no_of_ones)
print("n")
print(input())
except KeyboardInterrupt: print("nn程序終止n")
sys.exit()
- 代碼前面用來判斷用戶所輸入的IP地址和子網掩碼是否合法的部分,我已經在相應位置做了備注幫助大家理解,這里就不再贅述了。
最后運行該程序看效果: