实现全平台端口数据的转发
零、预备知识
- socket
有时,一个IP地址和一个端口号也称为一个插口(socket)。这个术语出现在最早的TCP规范(RFC793)中,后来它也作为表示伯克利版的编程接口(参见1.15节)。插口对(socketpair)(包含客户IP地址、客户端口号、服务器IP地址和服务器端口号的四元组)可唯一确定互联网络中每个TCP连接的双方。
一、使用背景
现在由于物联网的发展,越来越多的设备,需要接入网络,但是由于,现阶段的网络都还是,使用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的端口号必须要有管理员权限才能绑定:
1
# 监听端口:
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
五、数据转发测试
看到这里,恭喜你,你耐力够强,将来一定会成为技术大神,为了方便小白们入门也方便抓手党们,就放一下完整的程序。
转载自https://www.cnblogs.com/kmust/p/9193080.html
对原文做了部分修改