教你几行代码实现全平台端口数据的转发
一、使用背景
现在由于物联网的发展,越来越多的设备,需要接入网络,但是由于,现阶段的网络都还是,使用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。
下一篇: python(十)、进程