1. what is a proxy server?什么是代理?
A proxy server is a system or router that provides a gateway between users and the internet. Therefore, it helps prevent cyber attackers from entering a private network. It is a server, referred to as an “intermediary” because it goes between end-users and the web pages they visit online.
When a computer connects to the internet, it uses an IP address. This is similar to your home’s street address, telling incoming data where to go and marking outgoing data with a return address for other devices to authenticate. A proxy server is essentially a computer on the internet that has an IP address of its own.
译:
代理服务器是在用户和internet之间提供网关的系统或路由器。因此,它有助于防止网络攻击者进入私有网络。它是一个服务器,被称为“中介”,因为它位于终端用户和他们在线访问的网页之间。
当计算机连接到因特网时,它使用一个IP地址。这类似于你家的街道地址,告诉传入的数据到哪里去,并用返回地址标记传出的数据,以便其他设备进行身份验证。代理服务器本质上是一台在互联网上拥有自己IP地址的计算机。
因此我们想要在客户端和外网之间交换数据就需要用到代理服务器来实现
那么我们为啥要使用TCP来通信呢?我们使用wireshark来抓取客户端和外网的数据包就会发现用的是TCP协议传输,同时TCP具有高可靠的特性,并且能够使用较为低级的套接字(socket)来打包发送数据,因此用TCP何乐不为呢?
Tips:如果你不知道tcp是如何工作的(三次握手四次分手)→传送门
2. How to build?如何实现?
首先我们的代理服务器需要开放一个端口以便用户(终端/客户端)的接入,同时我们也想连接到外网中真正的服务器,那麽在这二者之间就需要专发数据包,也就是socket的传输
- 首先用户先发送套接字到代理服务器,代理服务器解包查看内容得到里面的数据包
- 然后在把数据装到发往服务器的套接字中,传输到服务器中去
- 服务器打开套接字读取信息,同理,服务器想用户发送套接字一样的过程,这样就实现了双向通信。
3. Software Design 软件实现
3.1. 软件架构
首先我们要监听用户是否发发送了数据到我们的代理服务器,通过套接字与用户建立连接,同时代理服务器建立和真正服务器的连接。
注意:这是两个不同的进程,因此我们需要把他们放在两个不同的线程中进行
第一个线程(用户<->代理服务器)叫做game2proxy thread
第二个线程(代理服务器<-> 服务器)叫做proxy2server thread
每个线程的进行都是通过socket来实现的,二者信息的交互也不例外
3.2. 软件实现(python)
首先是如何使用线程(thread)——实现两个连接进程
threading — Thread-based parallelism — Python 3.11.0 documentationhttps://docs.python.org/3/library/threading.html以及套接字(socket)——实现网络通信
socket — Low-level networking interface — Python 3.11.0 documentationhttps://docs.python.org/3/library/socket.html socket例程代码,懂的大佬可以跳过
下面是两个使用TCP/IP协议的最小示例程序:一个服务器响应它接收回来的所有数据(仅服务于一个客户机),一个客户机使用它。注意,服务器必须执行序列socket()、bind()、listen()、accept()(可能重复accept()来服务多个客户机),而客户机只需要序列socket()、connect()。还要注意,服务器不会在它监听的套接字上发送all()/recv(),而是在accept()返回的新套接字上发送。前两个示例只支持IPv4。
#---------------------------------------------------
# Echo server program
#---------------------------------------------------
import socket
HOST = '' # Symbolic name meaning all available interfaces
PORT = 50007 # Arbitrary non-privileged port
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.bind((HOST, PORT))
s.listen(1)
conn, addr = s.accept()
with conn:
print('Connected by', addr)
while True:
data = conn.recv(1024)#最大传输1k
if not data: break
conn.sendall(data)
#--------------------------------------------------
# Echo client program
#--------------------------------------------------
import socket
HOST = 'daring.cwi.nl' # The remote host
PORT = 50007 # The same port as used by the server
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
s.connect((HOST, PORT))
s.sendall(b'Hello, world')
data = s.recv(1024)
print('Received', repr(data))
现在就让我们开始写
#https://www.youtube.com/watch?v=iApNzWZG-10
import socket
from threading import Thread
import os
#线程2
class Proxy2Server(Thread):
#首先设置服务器连接(用_init_方法来构造)
#参考https://www.cnblogs.com/ant-colonies/p/6718388.html
def __init__(self, host, port):#如果没有在__init__中初始化对应的实例变量的话,导致后续引用实例变量会出错
super(Proxy2Server,self).__init__()
self.game = None #设置为连接用户的套接字,但是该套接字是由Game2Proxy线程创建的
self.port = port
self.host = host #连接服务器的ip和端口
self.server = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
self.server.connect((host,port))
#在这个线程中执行的函数
def run(self):
#创建一个循环来执行数据处理和网络连接
while True:
data = self.server.recv(4096)#最多接收4k的数据
if data:
#转发所有数据到用户
print("[{}] <- {}").format(self.port,data[:100].encode('hex'))#用作测试,可以打印出数据的流向
self.game.sendall(data)
#线程1(监听用户是否与代理服务器连接)
class Game2Proxy(Thread):
def __init__(self,host,port):
super(Game2Proxy,self).__init__()
self.server = None #设置为连接服务器的套接字,但是该套接字是由线程2创建的
self.port = port
self.host = host #连接用户的ip和端口
sock = socket.socket(socket.AF_INET,socket.SOCK_STREAM)
sock.setsockopt(socket.SOL_SOCKET,socket.SO_REUSEADDR,1)
sock.bind((host,port))
sock.listen(1)#这些都是上面官方文档里面调用的例程实现的
#等待用户的连接
self.game ,addr = sock.accept() #sock.accept接收套接字
#当客户端连接之后我们将获得代理服务器与客户端通信的套接字,并将其分配给self.game,然后在下面的线程中利用永久循环来接收用户端的数据
def run(self):
while True: #死循环接收用户的数据
data = self.game.recv(4096)#最大数据量4k
if data: #如果真的接收到了用户发送过来的数据,那麽我们会尝试将此数据转发到服务器的套接字,即另外一个线程的套接字
#转发给服务器
print("[{}] -> {}").format(self.port,data[:100].encode('hex'))#用作测试,可以打印出数据的流向
self.server.sendall(data)
#上面的两个线程创建完毕之后,需要为每一个线程提供对另外一个套接字的引用
#为此,我创建了一个更通用的类,命名为Proxy
class Proxy(Thread):
def __init__(self,from_host,to_host,port):#如果没有在__init__中初始化对应的实例变量的话,导致后续引用实例变量会出错
super(Proxy, self).__init__()
self.from_host = from_host
self.to_host = to_host
self.port = port
def run(self):
while True:
#print ("[proxy({})] setting up")
print ("代理服务器设置完毕,等待设备接入...")
#用户会连接到下面这个
self.g2p = Game2Proxy(self.from_host, self.port) #运行我们创建的这个线程,它等待用户端连接到指定端口
#如果代理服务器与用户建立连接之后,另外一个线程将建立到服务器的转发连接
self.p2s = Proxy2Server(self.to_host, self.port)
#print ("[proxy({})] connection established")
print ("代理服务器已和设备连接,正在传输...")
#现在两个线程都创建了套接字,我们接下来要做的就是交换他们
self.g2p.server = self.p2s.server #将与客户端建立的套接字转发给真实服务器
self.p2s.game = self.g2p.game #将服务器传回的套接字转发到客户端
#线程设置完毕,现在我们来真正启动它
self.g2p.start()
self.p2s.start()
#写到这里的时候,唯一缺少的就是创建一个或多个代理线程,我们先从主服务器开始
master_server = Proxy('0.0.0.0', '192.168.2.222', 4444)
#监听自己所有本机端口4444,并将它转发到真实的服务器ip 192.168.178.54
#注意:两个线程用到的端口是一样的,因此这里我们就用一个变量port来代替两个线程不同的端口
master_server.start() #启动
客户端,服务器的代码网上一大堆,我就不过多赘述了哈。接下来就是测试和debug环节
Test
- ConnectionRefusedError: [WinError 10061] 由于目标计算 机积极拒绝,无法连接。
接下来我们让客户端传输视频到代理并通过代理转发到服务器,同时计算端到端时延
我们先运行局域网中真正的服务器,接着运行代理服务器,再最后运行用户端
- 服务器运行截图如下:
代理服务器接入
客户端接入并传输数据
Over!