一、使用背景
現在由於物聯網的發展,越來越多的設備,需要接入網絡,但是由於,現階段的網絡都還是,使用IPV4,導致IP網段十分緊張,因此如何利用有限的資源,發揮最大的作用越來越重要。
需要說明的是,全平台主要是PC端,包含Windows系統,Linux系統,蘋果的系統都可進行使用的。
現在我們使用NB-IOT設備聯網測試的時候,有一個需求,需要在Linux環境下,將一個端口收到的數據,轉發到另外一個IP的端口上,使用Linux自帶的工具,大部分都只能實現TCP數據的
轉發,不能實現UDP數據的轉發。最近不是在學習Python么,因此就使用Python實現了一個簡單的端口數據轉發軟件。
網絡結構:
當前網絡結構:
雲服務器 S
(有公網固定IP)
| |
| 測試機 A
| (可以連接外網)
|
NB-IOT(前端采集設備)
需要說明的是,由於電信的平台對NB-IOT卡,進行了一定的限制,需要老的卡才能支持非定向IP,具體需要咨詢運營商。
二、TCP/IP協議簡介
計算機為了聯網,就必須規定通信協議,早期的計算機網絡,都是由各廠商自己規定一套協議,IBM、Apple和Microsoft都有各自的網絡協議,互不兼容,這就好比一群人有的說英語,有的說中文,有的說德語,說同一種語言的人可以交流,不同的語言之間就不行了。
為了把全世界的所有不同類型的計算機都連接起來,就必須規定一套全球通用的協議,為了實現互聯網這個目標,互聯網協議簇(Internet Protocol Suite)就是通用協議標准。Internet是由inter和net兩個單詞組合起來的,原意就是連接“網絡”的網絡,有了Internet,任何私有網絡,只要支持這個協議,就可以聯入互聯網。
因為互聯網協議包含了上百種協議標准,但是最重要的兩個協議是TCP和IP協議,所以,大家把互聯網的協議簡稱TCP/IP協議。
通信的時候,雙方必須知道對方的標識,好比發郵件必須知道對方的郵件地址。互聯網上每個計算機的唯一標識就是IP地址,類似123.123.123.123。如果一台計算機同時接入到兩個或更多的網絡,比如路由器,它就會有兩個或多個IP地址,所以,IP地址對應的實際上是計算機的網絡接口,通常是網卡。
IP協議負責把數據從一台計算機通過網絡發送到另一台計算機。數據被分割成一小塊一小塊,然后通過IP包發送出去。由於互聯網鏈路復雜,兩台計算機之間經常有多條線路,因此,路由器就負責決定如何把一個IP包轉發出去。IP包的特點是按塊發送,途徑多個路由,但不保證能到達,也不保證順序到達。
TCP協議則是建立在IP協議之上的。TCP協議負責在兩台計算機之間建立可靠連接,保證數據包按順序到達。TCP協議會通過握手建立連接,然后,對每個IP包編號,確保對方按順序收到,如果包丟掉了,就自動重發。
許多常用的更高級的協議都是建立在TCP協議基礎上的,比如用於瀏覽器的HTTP協議、發送郵件的SMTP協議等。
一個IP包除了包含要傳輸的數據外,還包含源IP地址和目標IP地址,源端口和目標端口。
端口有什么作用?在兩台計算機通信時,只發IP地址是不夠的,因為同一台計算機上跑着多個網絡程序。一個IP包來了之后,到底是交給瀏覽器還是QQ,就需要端口號來區分。每個網絡程序都向操作系統申請唯一的端口號,這樣,兩個進程在兩台計算機之間建立網絡連接就需要各自的IP地址和各自的端口號。
一個進程也可能同時與多個計算機建立鏈接,因此它會申請很多端口。
了解了TCP/IP協議的基本概念,IP地址和端口的概念,我們就可以開始進行網絡編程了。
三、UDP端口數據轉發的實現。
使用UDP協議時,不需要建立連接,只需要知道對方的IP地址和端口號,就可以直接發數據包。但是,能不能到達就不知道了。
雖然用UDP傳輸數據不可靠,但它的優點是和TCP比,速度快,對於不要求可靠到達的數據,就可以使用UDP協議。
我們來看看如何通過UDP協議傳輸數據。使用UDP的通信雙方分為客戶端和服務器。
由於我們使用的是接收UDP端口上的數據,轉發到另外一台電腦上,因此,這里接收端口的程序為服務端,轉發到另外一台電腦上的程序為客戶端。
服務器首先需要綁定端口:
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(('', 8080)) # 綁定同一個域名下的所有機器
創建Socket時,SOCK_DGRAM
指定了這個Socket的類型是UDP。
接下來就是接收數據了
while True: recvData, (remoteHost, remotePort) = sock.recvfrom(1024) log_file = open("forward_message.log", "a") sys.stdout = log_file print("****************************") print(time.strftime("%Y-%m-%d %X",time.localtime(time.time()))) print("[%s:%s] connect" % (remoteHost, remotePort)) # 接收客戶端的ip, port if len(recvData)>6: showHex(recvData) print("recvData :", recvData) sendPort = dest_port # ord(recvData[4])+ord(recvData[5])*256 sendAddr = dest_host #"%d.%d.%d.%d"%(ord(recvData[0]),ord(recvData[1]),ord(recvData[2]),ord(recvData[3])) print("to:%s:%d"%(sendAddr,sendPort)) #rx after tx sock_send_recv(sendAddr,sendPort,recvData,remoteHost, remotePort,sock) else: print("recvData: ", recvData) sendDataLen = sock.sendto(recvData, (remoteHost, remotePort)) print("sendDataLen: ", sendDataLen) #print("sendData(%3d):%s"%(sendDataLen,recvData)) print("****************************\n")
然后就是需要將接收到的數據,轉發到指定的IP和端口上。
def sock_send_recv(sendAddr, sendPort, recvData, remoteHost, remotePort,sock): try: sock2 = socket.socket(socket.AF_INET,type=socket.SOCK_DGRAM) sock2.settimeout(5) sock2.sendto(recvData, (sendAddr, sendPort)) data2 = sock2.recv(512) #combine head and data #data2 = '%s%s'%(recvData,data2) sock2.close() print('forward ok') sendDataLen = sock.sendto(data2, (remoteHost, remotePort)) print("return length:%d"%(len(data2))) return data2 except socket.error as d: print(d) return None except BaseException as e: print(e) return None
完善可以直接使用的代碼為:
#!/usr/bin/env python # -*- coding:utf8 -*- import sys import time import os from time import sleep import socket reload(sys) sys.setdefaultencoding('utf-8') # make a copy of original stdout route stdout_backup = sys.stdout # define the log file that receives your log info log_file = open("forward_message.log", "a") # redirect print output to log file sys.stdout = log_file log_file.close() dest_host = '192.168.5.234' dest_port = 8080 def showHex(s): for c in s: print("%x"%(ord(c))), print("\nreceive length :%d"%(len(s))) def sock_send_recv(sendAddr, sendPort, recvData, remoteHost, remotePort,sock): try: sock2 = socket.socket(socket.AF_INET,type=socket.SOCK_DGRAM) sock2.settimeout(5) sock2.sendto(recvData, (sendAddr, sendPort)) data2 = sock2.recv(512) #combine head and data #data2 = '%s%s'%(recvData,data2) sock2.close() print('forward ok') sendDataLen = sock.sendto(data2, (remoteHost, remotePort)) print("return length:%d"%(len(data2))) return data2 except socket.error as d: print(d) return None except BaseException as e: print(e) return None class UdpServer(object): def tcpServer(self): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(('', 8080)) # 綁定同一個域名下的所有機器 while True: recvData, (remoteHost, remotePort) = sock.recvfrom(1024) log_file = open("forward_message.log", "a") sys.stdout = log_file print("****************************") print(time.strftime("%Y-%m-%d %X",time.localtime(time.time()))) print("[%s:%s] connect" % (remoteHost, remotePort)) # 接收客戶端的ip, port if len(recvData)>6: showHex(recvData) print("recvData :", recvData) sendPort = dest_port # ord(recvData[4])+ord(recvData[5])*256 sendAddr = dest_host #"%d.%d.%d.%d"%(ord(recvData[0]),ord(recvData[1]),ord(recvData[2]),ord(recvData[3])) print("to:%s:%d"%(sendAddr,sendPort)) #rx after tx sock_send_recv(sendAddr,sendPort,recvData,remoteHost, remotePort,sock) else: print("recvData: ", recvData) sendDataLen = sock.sendto(recvData, (remoteHost, remotePort)) print("sendDataLen: ", sendDataLen) #print("sendData(%3d):%s"%(sendDataLen,recvData)) print("****************************\n") log_file.close() sys.stdout = stdout_backup sock.close() if __name__ == "__main__": sys.stdout = sys.__stdout__ if len(sys.argv) != 2: print("cmd : python udp_nc.py [IP]") print( ("參數個數: %d ") % len(sys.argv)) print( ("沒有識別到數據的輸入,將使用默認IP ") ) else: print( ("識別到輸入的IP: " % sys.argv[1]) ) dest_host = sys.argv[1] print ('接收到的數據將會轉發到IP: %s ' % dest_host) udpServer = UdpServer() udpServer.tcpServer()
接下來我們看看實際效果:
實際效果滿足實際使用需求。
四、TCP端口數據轉發的實現
TCP端口接收到的數據轉發和UDP端口轉發的數據類似,也是需要一個服務端,一個客戶端,我們首先來實現服務端(服務器)。
首先要綁定一個端口並監聽來自其他客戶端的連接。如果某個客戶端連接過來了,服務器就與該客戶端建立Socket連接,隨后的通信就靠這個Socket連接了。
所以,服務器會打開固定端口(比如80)監聽,每來一個客戶端連接,就創建該Socket連接。由於服務器會有大量來自客戶端的連接,所以,服務器要能夠區分一個Socket連接是和哪個客戶端綁定的。一個Socket依賴4項:服務器地址、服務器端口、客戶端地址、客戶端端口來唯一確定一個Socket。
但是服務器還需要同時響應多個客戶端的請求,所以,每個連接都需要一個新的進程或者新的線程來處理,否則,服務器一次就只能服務一個客戶端了。
我們來編寫一個簡單的服務器程序,它接收客戶端連接,把客戶端發過來的數據,轉發到指定的IP和端口上。
首先,創建一個基於IPv4和TCP協議的Socket:
server= socket.socket(socket.AF_INET, socket.SOCK_STREAM)
然后,我們要綁定監聽的地址和端口。服務器可能有多塊網卡,可以綁定到某一塊網卡的IP地址上,也可以用0.0.0.0
綁定到所有的網絡地址,還可以用127.0.0.1
綁定到本機地址。127.0.0.1
是一個特殊的IP地址,表示本機地址,如果綁定到這個地址,客戶端必須同時在本機運行才能連接,也就是說,外部的計算機無法連接進來。
端口號需要預先指定。請注意,小於1024
的端口號必須要有管理員權限才能綁定:
# 監聽端口:
server.bind(('127.0.0.1', 8080))
緊接着,調用listen()
方法開始監聽端口,傳入的參數指定等待連接的最大數量:
server.listen(5)
print('Waiting for connection...')
接下來,服務器程序通過一個永久循環來接受來自客戶端的連接,accept()
會等待並返回一個客戶端的連接:
while True: c, addr = s.accept() # 建立客戶端連接。 print ('連接地址:', addr) c.close() # 關閉連接
好了,初步的TcpServer功能已經完成,但是我們的要求不僅僅如此,我們需要一個完整的功能,接下來就是TcpServer功能的完整實現。
class TcpServer(object): def tcpServer(self): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 創建一個socket server.bind(('', 8080)) # 綁定同一個域名下的所有機器 server.listen(5) #傳入的參數指定等待連接的最大數量 print ('Waiting for connection...') while True: sock, addr = sock.accept() recvData = sock.recv(1024) log_file = open("forward_message.log", "a") sys.stdout = log_file print("****************************") print(time.strftime("%Y-%m-%d %X",time.localtime(time.time()))) print("connect from: %s" % (addr)) # 接收客戶端的ip, port if len(recvData)>6: showHex(recvData) print("recvData :", recvData) sendPort = dest_port # ord(recvData[4])+ord(recvData[5])*256 sendAddr = dest_host #"%d.%d.%d.%d"%(ord(recvData[0]),ord(recvData[1]),ord(recvData[2]),ord(recvData[3])) print("to:%s:%d"%(sendAddr,sendPort)) #rx after tx tcp_send_recv(sendAddr,sendPort,recvData) else: print("recvData: ", recvData) tcp_send_recv(sendAddr,sendPort,recvData) #print("sendData(%3d):%s"%(sendDataLen,recvData)) print("****************************\n") log_file.close() sys.stdout = stdout_backup sock.close()
最后剩下的就是Tcp客戶端了,也就是數據轉發的實現。
def tcp_send_recv(sendAddr,sendPort,recvData): try: client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 創建一個socket client.connect((sendAddr,sendPort)) client.send(recvData) client.close() except BaseException as e: print(e) return None
五、數據轉發測試
看到這里,恭喜你,你耐力夠強,將來一定會成為技術大神,為了方便小白們入門也方便抓手黨們,就放一下完整的程序。
#!/usr/bin/env python # -*- coding:utf8 -*- import sys import time import os from time import sleep import socket reload(sys) sys.setdefaultencoding('utf-8') # make a copy of original stdout route stdout_backup = sys.stdout # define the log file that receives your log info log_file = open("forward_message.log", "a") # redirect print output to log file sys.stdout = log_file log_file.close() dest_host = '192.168.5.234' dest_port = 8080 def showHex(s): for c in s: print("%x"%(ord(c))), print("\nreceive length :%d"%(len(s))) def tcp_send_recv(sendAddr,sendPort,recvData): try: client = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 創建一個socket client.connect((sendAddr,sendPort)) client.send(recvData) client.close() except BaseException as e: print(e) return None class TcpServer(object): def tcpServer(self): server = socket.socket(socket.AF_INET, socket.SOCK_STREAM) # 創建一個socket server.bind(('', 8080)) # 綁定同一個域名下的所有機器 server.listen(5) #傳入的參數指定等待連接的最大數量 print ('Waiting for connection...') while True: sock, addr = sock.accept() recvData = sock.recv(1024) log_file = open("forward_message.log", "a") sys.stdout = log_file print("****************************") print(time.strftime("%Y-%m-%d %X",time.localtime(time.time()))) print("connect from: %s" % (addr)) # 接收客戶端的ip, port if len(recvData)>6: showHex(recvData) print("recvData :", recvData) sendPort = dest_port # ord(recvData[4])+ord(recvData[5])*256 sendAddr = dest_host #"%d.%d.%d.%d"%(ord(recvData[0]),ord(recvData[1]),ord(recvData[2]),ord(recvData[3])) print("to:%s:%d"%(sendAddr,sendPort)) #rx after tx tcp_send_recv(sendAddr,sendPort,recvData) else: print("recvData: ", recvData) tcp_send_recv(sendAddr,sendPort,recvData) #print("sendData(%3d):%s"%(sendDataLen,recvData)) print("****************************\n") log_file.close() sys.stdout = stdout_backup sock.close() def udp_send_recv(sendAddr, sendPort, recvData, remoteHost, remotePort,sock): try: sock2 = socket.socket(socket.AF_INET,type=socket.SOCK_DGRAM) sock2.settimeout(5) sock2.sendto(recvData, (sendAddr, sendPort)) data2 = sock2.recv(512) #combine head and data #data2 = '%s%s'%(recvData,data2) sock2.close() print('forward ok') sendDataLen = sock.sendto(data2, (remoteHost, remotePort)) print("return length:%d"%(len(data2))) return data2 except socket.error as d: print(d) return None except BaseException as e: print(e) return None class UdpServer(object): def udpServer(self): sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) sock.bind(('', 8080)) # 綁定同一個域名下的所有機器 while True: recvData, (remoteHost, remotePort) = sock.recvfrom(1024) log_file = open("forward_message.log", "a") sys.stdout = log_file print("****************************") print(time.strftime("%Y-%m-%d %X",time.localtime(time.time()))) print("[%s:%s] connect" % (remoteHost, remotePort)) # 接收客戶端的ip, port if len(recvData)>6: showHex(recvData) print("recvData :", recvData) sendPort = dest_port # ord(recvData[4])+ord(recvData[5])*256 sendAddr = dest_host #"%d.%d.%d.%d"%(ord(recvData[0]),ord(recvData[1]),ord(recvData[2]),ord(recvData[3])) print("to:%s:%d"%(sendAddr,sendPort)) #rx after tx udp_send_recv(sendAddr,sendPort,recvData,remoteHost, remotePort,sock) else: print("recvData: ", recvData) sendDataLen = sock.sendto(recvData, (remoteHost, remotePort)) print("sendDataLen: ", sendDataLen) #print("sendData(%3d):%s"%(sendDataLen,recvData)) print("****************************\n") log_file.close() sys.stdout = stdout_backup sock.close() if __name__ == "__main__": sys.stdout = sys.__stdout__ if len(sys.argv) != 2: print("cmd : python udp_nc.py [IP]") print( ("參數個數: %d ") % len(sys.argv)) print( ("沒有識別到數據的輸入,將使用默認IP ") ) else: print( ("識別到輸入的IP: " % sys.argv[1]) ) dest_host = sys.argv[1] print ('接收到的數據將會轉發到IP: %s ' % dest_host) udpServer = UdpServer() udpServer.udpServer()
數據收發測試:
啟動程序:
查看數據收發日志:
數據收發測試:
嗯,數據收發正常,滿足功能需求。
有不懂的問題,加企鵝群交流吧:98556420。