如何用Go实现一个tcp代理服务器

概述

通过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的代码分析,我们已经可以实现一个简单的代理服务器。但要实现更好的代理服务器,还需要进一步修改代码。

扫描二维码关注公众号,回复: 2883235 查看本文章

猜你喜欢

转载自blog.csdn.net/zg_hover/article/details/81228182