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

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。

相關文章
相關標籤/搜索