教你幾行程式碼實現全平臺埠資料的轉發

badwell發表於2018-06-17

一、使用背景

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

相關文章