教你幾行代碼實現全平臺端口數據的轉發

一、使用背景

  現在由於物聯網的發展,越來越多的設備,需要接入網絡,但是由於,現階段的網絡都還是,使用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。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章