前言

本文主要给大家介绍了关于Golang实现TCP连接的双向拷贝的相关内容,分享出来供大家参考学习,下面话不多说了,来一起看看详细的介绍吧。
最简单的实现
每次来一个Server的连接,就新开一个Client的连接。用一个goroutine从server拷贝到client,再用另外一个goroutine从client拷贝到server。任何一方断开连接,双向都断开连接。
func main() {
runtime.GOMAXPROCS(1)
listener, err := net.Listen("tcp", "127.0.0.1:8848")
if err != nil {
panic(err)
}
for {
conn, err := listener.Accept()
if err != nil {
panic(err)
}
go handle(conn.(*net.TCPConn))
}
}
func handle(server *net.TCPConn) {
defer server.Close()
client, err := net.Dial("tcp", "127.0.0.1:8849")
if err != nil {
fmt.Print(err)
return
}
defer client.Close()
go func() {
defer server.Close()
defer client.Close()
buf := make([]byte, 2048)
io.CopyBuffer(server, client, buf)
}()
buf := make([]byte, 2048)
io.CopyBuffer(client, server, buf)
}
一个值得注意的地方是io.Copy的默认buffer比较大,给一个小的buffer可以支持更多的并发连接。
这两个goroutine并序在一个退出之后,另外一个也退出。这个的实现是通过关闭server或者client的socket来实现的。因为socket被关闭了,io.CopyBuffer 就会退出。
Client端实现连接池
一个显而易见的问题是,每次Server的连接进来之后都需要临时去建立一个新的Client的端的连接。这样在代理的总耗时里就包括了一个tcp连接的握手时间。如果能够让Client端实现连接池复用已有连接的话,可以缩短端到端的延迟。
var pool = make(chan net.Conn, 100)
func borrow() (net.Conn, error) {
select {
case conn := <- pool:
return conn, nil
default:
return net.Dial("tcp", "127.0.0.1:8849")
}
}
func release(conn net.Conn) error {
select {
case pool <- conn:
// returned to pool
return nil
default:
// pool is overflow
return conn.Close()
}
}
func handle(server *net.TCPConn) {
defer server.Close()
client, err := borrow()
if err != nil {
fmt.Print(err)
return
}
defer release(client)
go func() {
defer server.Close()
defer release(client)
buf := make([]byte, 2048)
io.CopyBuffer(server, client, buf)
}()
buf := make([]byte, 2048)
io.CopyBuffer(client, server, buf)
}
这个版本的实现是显而易见有问题的。因为连接在归还到池里的时候并不能保证是还保持连接的状态。另外一个更严重的问题是,因为client的连接不再被关闭了,当server端关闭连接时,从client向server做io.CopyBuffer的goroutine就无法退出了。
所以,有以下几个问题要解决:
通过SetDeadline中断Goroutine
一个普遍的观点是Goroutine是无法被中断的。当一个Goroutine在做conn.Read时,这个协程就被阻塞在那里了。实际上并不是毫无办法的,我们可以通过conn.Close来中断Goroutine。但是在连接池的情况下,又无法Close链接。另外一种做法就是通过SetDeadline为一个过去的时间戳来中断当前正在进行的阻塞读或者阻塞写。
var pool = make(chan net.Conn, 100)
type client struct {
conn net.Conn
inUse *sync.WaitGroup
}
func borrow() (clt *client, err error) {
var conn net.Conn
select {
case conn = <- pool:
default:
conn, err = net.Dial("tcp", "127.0.0.1:18849")
}
if err != nil {
return nil, err
}
clt = &client{
conn: conn,
inUse: &sync.WaitGroup{},
}
return
}
func release(clt *client) error {
clt.conn.SetDeadline(time.Now().Add(-time.Second))
clt.inUse.Done()
clt.inUse.Wait()
select {
case pool <- clt.conn:
// returned to pool
return nil
default:
// pool is overflow
return clt.conn.Close()
}
}
func handle(server *net.TCPConn) {
defer server.Close()
clt, err := borrow()
if err != nil {
fmt.Print(err)
return
}
clt.inUse.Add(1)
defer release(clt)
go func() {
clt.inUse.Add(1)
defer server.Close()
defer release(clt)
buf := make([]byte, 2048)
io.CopyBuffer(server, clt.conn, buf)
}()
buf := make([]byte, 2048)
io.CopyBuffer(clt.conn, server, buf)
}
通过SetDeadline实现了goroutine的中断,然后通过sync.WaitGroup来保证这些使用方都退出了之后再归还给连接池。否则一个连接被复用的时候,之前的使用方可能还没有退出。
连接有效性
为了保证在归还给pool之前,连接仍然是有效的。连接在被读写的过程中如果发现了error,我们就要标记这个连接是有问题的,会释放之后直接close掉。但是SetDeadline必然会导致读取或者写入的时候出现一次timeout的错误,所以还需要把timeout排除掉。
var pool = make(chan net.Conn, 100)
type client struct {
conn net.Conn
inUse *sync.WaitGroup
isValid int32
}
const maybeValid = 0
const isValid = 1
const isInvalid = 2
func (clt *client) Read(b []byte) (n int, err error) {
n, err = clt.conn.Read(b)
if err != nil {
if !isTimeoutError(err) {
atomic.StoreInt32(&clt.isValid, isInvalid)
}
} else {
atomic.StoreInt32(&clt.isValid, isValid)
}
return
}
func (clt *client) Write(b []byte) (n int, err error) {
n, err = clt.conn.Write(b)
if err != nil {
if !isTimeoutError(err) {
atomic.StoreInt32(&clt.isValid, isInvalid)
}
} else {
atomic.StoreInt32(&clt.isValid, isValid)
}
return
}
type timeoutErr interface {
Timeout() bool
}
func isTimeoutError(err error) bool {
timeoutErr, _ := err.(timeoutErr)
if timeoutErr == nil {
return false
}
return timeoutErr.Timeout()
}
func borrow() (clt *client, err error) {
var conn net.Conn
select {
case conn = <- pool:
default:
conn, err = net.Dial("tcp", "127.0.0.1:18849")
}
if err != nil {
return nil, err
}
clt = &client{
conn: conn,
inUse: &sync.WaitGroup{},
isValid: maybeValid,
}
return
}
func release(clt *client) error {
clt.conn.SetDeadline(time.Now().Add(-time.Second))
clt.inUse.Done()
clt.inUse.Wait()
if clt.isValid == isValid {
return clt.conn.Close()
}
select {
case pool <- clt.conn:
// returned to pool
return nil
default:
// pool is overflow
return clt.conn.Close()
}
}
func handle(server *net.TCPConn) {
defer server.Close()
clt, err := borrow()
if err != nil {
fmt.Print(err)
return
}
clt.inUse.Add(1)
defer release(clt)
go func() {
clt.inUse.Add(1)
defer server.Close()
defer release(clt)
buf := make([]byte, 2048)
io.CopyBuffer(server, clt, buf)
}()
buf := make([]byte, 2048)
io.CopyBuffer(clt, server, buf)
}
判断 error 是否是 timeout 需要类型强转来实现。
对于连接池里的conn是否仍然是有效的,如果用后台不断ping的方式来实现成本比较高。因为不同的协议要连接保持需要不同的ping的方式。一个最简单的办法就是下次用的时候试一下。如果连接不好用了,则改成新建一个连接,避免连续拿到无效的连接。通过这种方式把无效的连接给淘汰掉。
关于正确性
本文在杭州机场写成,完全不保证内容的正确性
总结
以上就是这篇文章的全部内容了,希望本文的内容对大家的学习或者工作具有一定的参考学习价值,如果有疑问大家可以留言交流,谢谢大家对的支持。
# go
# tcp
# 连接
# golang
# 双向
# golang之tcp自动重连实现方法
# golang 实现tcp转发代理的方法
# Golang 实现Socket服务端和客户端使用TCP协议通讯
# golang中net的tcp服务使用
# Golang通过包长协议处理TCP粘包的问题解决
# Golang 编写Tcp服务器的解决方案
# golang实现简单的tcp数据传输
# Golang实现自己的Redis(TCP篇)实例探究
# Golang TCP网络编程的具体实现
# 另外一个
# 连接池
# 仍然是
# 出了
# 问题是
# 显而易见
# 最简单
# 来实现
# 复用
# 就会
# 还没有
# 相关内容
# 是有
# 说了
# 毫无办法
# 已有
# 不多
# 较高
# 有一定
# 我们可以
相关文章:
黑客如何通过漏洞一步步攻陷网站服务器?
建站之星后台管理系统如何操作?
青浦网站制作公司有哪些,苹果官网发货地是哪里?
企业在线网站设计制作流程,想建设一个属于自己的企业网站,该如何去做?
网页设计网站制作软件,microsoft office哪个可以创建网页?
如何通过云梦建站系统实现SEO快速优化?
如何快速启动建站代理加盟业务?
网站按钮制作软件,如何实现网页中按钮的自动点击?
ui设计制作网站有哪些,手机UI设计网址吗?
常州自助建站工具推荐:低成本搭建与模板选择技巧
c# 在高并发场景下,委托和接口调用的性能对比
购物网站制作公司有哪些,哪个购物网站比较好?
深入理解Android中的xmlns:tools属性
logo在线制作免费网站在线制作好吗,DW网页制作时,如何在网页标题前加上logo?
正规网站制作公司有哪些,目前国内哪家网页网站制作设计公司比较专业靠谱?口碑好?
建站之星下载版如何获取与安装?
整蛊网站制作软件,手机不停的收到各种网站的验证码短信,是手机病毒还是人为恶搞?有这种手机病毒吗?
矢量图网站制作软件,用千图网的一张矢量图做公司app首页,该网站并未说明版权等问题,这样做算不算侵权?应该如何解决?
阿里云网站制作公司,阿里云快速搭建网站好用吗?
企业微网站怎么做,公司网站和公众号有什么区别?
简单实现Android验证码
专业网站设计制作公司,如何制作一个企业网站,建设网站的基本步骤有哪些?
宝塔新建站点报错如何解决?
香港服务器网站测试全流程:性能评估、SEO加载与移动适配优化
SAX解析器是什么,它与DOM在处理大型XML文件时有何不同?
如何在建站之星网店版论坛获取技术支持?
中山网站制作网页,中山新生登记系统登记流程?
打鱼网站制作软件,波克捕鱼官方号怎么注册?
最好的网站制作公司,网购哪个网站口碑最好,推荐几个?谢谢?
建站之星×万网:智能建站系统+自助建站平台一键生成
高防网站服务器:DDoS防御与BGP线路的AI智能防护方案
c# 在高并发下使用反射发射(Reflection.Emit)的性能
制作ppt免费网站有哪些,有哪些比较好的ppt模板下载网站?
专业制作网站的公司哪家好,建立一个公司网站的费用.有哪些部分,分别要多少钱?
高端云建站费用究竟需要多少预算?
零基础网站服务器架设实战:轻量应用与域名解析配置指南
专业型网站制作公司有哪些,我设计专业的,谁给推荐几个设计师兼职类的网站?
ppt制作免费网站有哪些,ppt模板免费下载网站?
已有域名如何快速搭建专属网站?
韩国服务器如何优化跨境访问实现高效连接?
淘宝制作网站有哪些,淘宝网官网主页?
如何选择长沙网站建站模板?H5响应式与品牌定制哪个更优?
义乌企业网站制作公司,请问义乌比较好的批发小商品的网站是什么?
Java解压缩zip - 解压缩多个文件或文件夹实例
高性能网站服务器部署指南:稳定运行与安全配置优化方案
网站制作价目表怎么做,珍爱网婚介费用多少?
如何快速选择适合个人网站的云服务器配置?
昆明网站制作哪家好,昆明公租房申请网上登录入口?
如何注册花生壳免费域名并搭建个人网站?
建站之星2.7模板快速切换与批量管理功能操作指南
*请认真填写需求信息,我们会在24小时内与您取得联系。