概述
通过linux和C/C++来实现一个代理服务器并不是那么容易,不仅要考虑内存管理和性能,还需要考虑同步和线程/进程管理等问题。为了保证性能,有时候还需要考虑和被代理的服务器保持长连接,这样就需要对链接进行管理。那么使用Go来实现一个代理服务器会如何呢?
首先,通过Go来实现后台服务时至少有以下一些优势:协程可随用随起,内存管理和回收不需要自己来做。这样,再加上Go提供的一些工具函数,极大的简化了实现后台服务的难度。
本文通过查看一些源码,介绍实现一个最简单的Tcp代理服务器的核心思想和代码实现。
Tcp代理服务器介绍
|-----------------| |---------------| client <-----> | proxy server | <-----> | server 1 | |-----------------| |---------------| /|\ ... ... | |---------------| |---------------->| server N | |---------------|
该图简单的表达了一个代理服务器的作用,proxy server接收client的tcp链接请求,并把client端的数据发送给代理服务器,代理服务器把数据发送给后端的被代理服务器server1…serverN,并把server1…serverN的执行结果再返回给client。
当然,proxy server还可以实现很多其他的功能:比如说:负载均衡等。
代码实现
创建一个ProxyServer结构体
type ProxyServer struct {
host string
port uint16
beattime int
listener net.Listener
proxy proxy.Proxy
on bool
}
实现代理服务器方法
这是代理服务器的核心流程:
func (server *ProxyServer) Start() {
// 监听一个端口和地址
local, err := net.Listen("tcp", server.Address())
if err != nil {
log.Panic("proxy server start error:", err)
}
log.Println("easyproxy server start ok")
server.listener = local
server.on = true
server.heartBeat()
for server.on {
// 等待客户端发送tcp连接请求
con, err := server.listener.Accept()
if err == nil {
// 连接成功,现起一个goroutine处理,来处理已经完成的连接
go server.proxy.Dispatch(con)
} else {
log.Println("client connect server error:", err)
}
}
defer server.listener.Close()
}
实现数据传输和结果获取
以下代码选择一个可用的后端server,并和该server建立连接,把从client端得到的数据传输给后端server。
func (proxy *EasyProxy) Dispatch(con net.Conn) {
if proxy.isBackendAvailable() {
servers := proxy.data.BackendUrls()
url := proxy.strategy.Choose(con.RemoteAddr().String(), servers)
proxy.transfer(con, url)
} else {
con.Close()
log.Println("no backends available now,please check your server!")
}
}
以下代码,先连接后端server(这里可以优化),并把client端的数据复制到后端server,再把后端server的返回数据复制给client端,完成整个代理流程。
func (proxy *EasyProxy) transfer(local net.Conn, remote string) {
remoteConn, err := net.DialTimeout("tcp", remote, DefaultTimeoutTime * time.Second)
if err != nil {
local.Close()
proxy.Clean(remote)
log.Println("connect backend error:%s", err)
return
}
sync := make(chan int, 1)
channel := structure.Channel{SrcConn:local, DstConn:remoteConn}
go proxy.putChannel(&channel)
go proxy.safeCopy(local, remoteConn, sync)
go proxy.safeCopy(remoteConn, local, sync)
go proxy.closeChannel(&channel, sync)
}
实现说明
以上代码实现了一个简单的Tcp代理服务器,后端的服务器可以是多个。当然,以上代码只是一个最简单的实现,可以优化的地方还有很多。比如:
* 实现网络的异步IO,提升请求处理的性能
* 实现负载均衡,把client的请求均匀的分配到后端服务
* 实现一致性hash算法,根据后端服务的配置比例来把请求路由到其中一个服务器
* 实现加密传输
* … …
但不管怎样,以上代码已经具备了一个代理服务器的雏形。
总结
通过对EasyProxy的代码分析,我们已经可以实现一个简单的代理服务器。但要实现更好的代理服务器,还需要进一步修改代码。