1、使用背景前端
如今因爲物聯網的發展,愈來愈多的設備,須要接入網絡,可是因爲,現階段的網絡都仍是,使用IPV4,致使IP網段十分緊張,所以如何利用有限的資源,發揮最大的做用愈來愈重要。python
須要說明的是,全平臺主要是PC端,包含Windows系統,Linux系統,蘋果的系統均可進行使用的。編程
如今咱們使用NB-IOT設備聯網測試的時候,有一個需求,須要在Linux環境下,將一個端口收到的數據,轉發到另一個IP的端口上,使用Linux自帶的工具,大部分都只能實現TCP數據的瀏覽器
轉發,不能實現UDP數據的轉發。最近不是在學習Python麼,所以就使用Python實現了一個簡單的端口數據轉發軟件。ruby
網絡結構:服務器
當前網絡結構:
雲服務器 S
(有公網固定IP)
| |
| 測試機 A
| (能夠鏈接外網)網絡
|socket
NB-IOT(前端採集設備)tcp
須要說明的是,因爲電信的平臺對NB-IOT卡,進行了必定的限制,須要老的卡才能支持非定向IP,具體須要諮詢運營商。工具
2、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地址和端口的概念,咱們就能夠開始進行網絡編程了。
3、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()
接下來咱們看看實際效果:
實際效果知足實際使用需求。
4、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
5、數據轉發測試
看到這裏,恭喜你,你耐力夠強,未來必定會成爲技術大神,爲了方便小白們入門也方便抓手黨們,就放一下完整的程序。
#!/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。