golang实现TCP代理
前言
TCP代理的使用场景有很多,比如Nginx的http代理,本质上也是TCP的转发,微服务网格istio的Envoy也是基于这种理念来实现的。
之所以写这个demo,是为了可以很方便地调试上下游服务在通信过程中到底传递了什么数据,对各种协议的通信方式可以很快的掌握它们原理,比如rpc通信、http通信、grpc通信等等。
代码路径:http://gitee.com/zqwlai/go-test/tcpProxy.go
package main
import (
"bufio"
"flag"
"fmt"
"github.com/rs/zerolog"
"net"
"os"
"strconv"
"strings"
"time"
)
var logger = zerolog.New(os.Stdout).With().Timestamp().Logger()
func main() {
help := flag.Bool("help", false, "print usage")
bind := flag.String("bind", "0.0.0.0:9528", "The address to bind to")
backend := flag.String("backend", "127.0.0.1:30000", "The backend server address")
flag.Parse()
logger.Level(zerolog.DebugLevel)
if *help {
flag.Usage()
return
}
if *backend == "" {
flag.Usage()
return
}
if *bind == "" {
//use default bind
logger.Info().Str("bind", *bind).Msg("use default bind")
}
success, err := RunProxy(*bind, *backend)
if !success {
logger.Error().Err(err).Send()
os.Exit(1)
}
}
func RunProxy(bind, backend string) (bool, error) {
listener, err := net.Listen("tcp", bind)
if err != nil {
return false, err
}
defer listener.Close()
logger.Info().Str("bind", bind).Str("backend", backend).Msg("tcp-proxy started.")
for {
conn, err := listener.Accept()
if err != nil {
logger.Error().Err(err).Send()
} else {
go ConnectionHandler(conn, backend)
}
}
}
func ConnectionHandler(conn net.Conn, backend string) {
logger.Info().Str("conn", conn.RemoteAddr().String()).Msg("client connected.")
target, err := net.Dial("tcp", backend)
defer conn.Close()
if err != nil {
logger.Error().Err(err).Send()
} else {
defer target.Close()
logger.Info().Str("conn", conn.RemoteAddr().String()).Str("backend", target.LocalAddr().String()).Msg("backend connected.")
closed := make(chan bool, 1)
go Proxy(conn, target, closed)
go Proxy2(target, conn, closed)
<-closed
logger.Info().Str("conn", conn.RemoteAddr().String()).Msg("Connection closed.")
}
}
func Proxy(from net.Conn, to net.Conn, closed chan bool) {
buffer := make([]byte, 4096)
for {
n1, err := from.Read(buffer)
if err != nil {
closed <- true
return
}
fmt.Println(1111, n1)
fmt.Println(222, string(buffer[:n1]))
n2, err := to.Write(buffer[:n1])
logger.Debug().Str("from", from.RemoteAddr().String()).Int("recv", n1).Str("to", to.RemoteAddr().String()).Int("send", n2).Send()
if err != nil {
closed <- true
return
}
}
}
func Proxy2(from net.Conn, to net.Conn, closed chan bool) {
time.Sleep(1*time.Second)
buffer := make([]byte, 4096)
for {
n1, err := from.Read(buffer)
if err != nil {
closed <- true
return
}
fmt.Println(3333, string(buffer[:n1]))
n2, err := to.Write(buffer[:n1])
logger.Debug().Str("from", from.RemoteAddr().String()).Int("recv", n1).Str("to", to.RemoteAddr().String()).Int("send", n2).Send()
if err != nil {
closed <- true
return
}
}
}
//解析HTTP报文
func parseHttp(conn net.Conn)(string){
var s = ""
reader := bufio.NewReader(conn)
firstLine,_,_ := reader.ReadLine()
s += string(firstLine) + "\n"
tempList := strings.Split(string(firstLine), " ")
method := tempList[0]
path := tempList[1]
proto := tempList[2]
fmt.Println(1111, method, path, proto)
var headers = make(map[string]string)
var contenLength int
for {
line,_,_ := reader.ReadLine()
s += string(line) + "\n"
if len(line) == 0{
break
}
tempList = strings.Split(string(line), ":")
key := tempList[0]
value := strings.Trim(tempList[1], " ")
headers[key] = value
if (key == "Content-Length"){
contenLength,_ = strconv.Atoi(value)
}
}
fmt.Println(2222, headers)
if contenLength == 0{
return s
}
pack := make([]byte, contenLength)
t,_ := reader.Read(pack)
s += string(pack[:t])
return s
}
简单说下原理,proxy在收到下游的请求后,会建立一个conn1,此时proxy会解析conn1里面的目标地址,并与之建立TCP连接conn2, 接下来会开启两个协程,一个用来从conn1里读取请求报文,并将报文发给conn2,另一个协程从conn2读取响应数据,并写到conn1返回给上游。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 如何调用 DeepSeek 的自然语言处理 API 接口并集成到在线客服系统
· 【译】Visual Studio 中新的强大生产力特性
· 2025年我用 Compose 写了一个 Todo App