教你几行代码实现全平台端口数据的转发


一、使用背景

  现在由于物联网的发展,越来越多的设备,需要接入网络,但是由于,现阶段的网络都还是,使用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。