终于到了中继阶段了。
SOCKS5 代理服务器续
中继阶段
这个阶段同样写在 connect() 函数里,在获取到端口后,给浏览器返回一个包之前:
dest, err := net.Dial("tcp", fmt.Sprintf("%v:%v", addr, port))
if err != nil {
return fmt.Errorf("dial dst failed:%w", err)
}
defer dest.Close()
首先是用 net.Dial() 建立一个 TCP 连接,然后记得用 defer 来关闭这个连接。
之后要在给浏览器返回一个包之后实现双向转发的功能,也就是中继的功能。别忘了我这里的服务器是 SOCKS5 代理服务器。
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
go func() {
_, _ = io.Copy(dest, reader)
cancel()
}()
go func() {
_, _ = io.Copy(conn, dest)
cancel()
}()
<-ctx.Done()
用 io.copy() 可以实现单向的数据转发,所以这里需要两个,一个是从服务器到真正的远端服务器,另一个是从服务器到浏览器。注意这里是开了两个 goroutines。
然后这里的 cancel() 是为了等待转发出错,或者有终止命令。不写这一步,整个 connect() 函数走到底就返回,然后连接就断开了。
要验证现在的代码同样是需要开两个终端,第一个运行这个代码:
$ go run proxy/v4/main.go
第二个终端 curl 一个网页:
$ curl --socks5 127.0.0.1:1080 -v baidu.com
* Trying 127.0.0.1:1080...
* SOCKS5 connect to IPv4 110.242.68.66:80 (locally resolved)
* SOCKS5 request granted.
* Connected to 127.0.0.1 (127.0.0.1) port 1080 (#0)
> GET / HTTP/1.1
> Host: baidu.com
> User-Agent: curl/7.82.0
> Accept: */*
>
* Mark bundle as not supporting multiuse
< HTTP/1.1 200 OK
< Date: Thu, 25 May 2023 15:55:16 GMT
< Server: Apache
< Last-Modified: Tue, 12 Jan 2010 13:48:00 GMT
< ETag: "51-47cf7e6ee8400"
< Accept-Ranges: bytes
< Content-Length: 81
< Cache-Control: max-age=86400
< Expires: Fri, 26 May 2023 15:55:16 GMT
< Connection: Keep-Alive
< Content-Type: text/html
<
<html>
<meta http-equiv="refresh" content="0;url=http://www.baidu.com/">
</html>
* Connection #0 to host 127.0.0.1 left intact
有 HTML 文件出来了,证明我们的连接成功了。同时之前的终端也有远端服务器的地址和端口号:
2023/05/25 23:55:15 dial 110.242.68.66 80
这样服务器的例子就初步完成了!