利用Golang实现TCP连接的双向拷贝详解

  

  

本文主要给大家介绍了关于Golang实现TCP连接的双向拷贝的相关内容,分享出来供大家参考学习、下面话不多说了,来一起看看详细的介绍吧。

  

  

每次来一个服务器的连接,就新开一个客户机的连接。用一个goroutine从服务器拷贝到客户端,再用另外一个goroutine从客户机拷贝到服务器。任何一方断开连接,双向都断开连接。

        函数main () {   runtime.GOMAXPROCS (1)   侦听器,犯错:=净。听(“tcp”、“127.0.0.1:8848”)   如果犯错!=nil {   恐慌(err)   }   为{   康涅狄格州,犯错:=listener.Accept ()   如果犯错!=nil {   恐慌(err)   }   去处理(康涅狄格州。(* net.TCPConn))   }   }      函数处理(服务器* net.TCPConn) {   推迟server.Close ()   客户端,犯错:=净。刻度盘(“tcp”、“127.0.0.1:8849”)   如果犯错!=nil {   fmt.Print (err)   返回   }   推迟client.Close ()   去func () {   推迟server.Close ()   推迟client.Close ()   缓冲区:=([]字节,2048)   io。CopyBuffer(服务器、客户机buf)   }()   缓冲区:=([]字节,2048)   io。buf CopyBuffer(客户端,服务器)   }      

一个值得注意的地方是io.Copy的默认缓冲区比较大,给一个小的缓冲可以支持更多的并发连接。

  

这两个goroutine并序在一个退出之后,另外一个也退出。这个的实现是通过关闭服务器或客户机的套接字者来实现的。因为套接字被关闭了,io。CopyBuffer就会退出。

  

  

一个显而易见的问题是,每次服务器的连接进来之后都需要临时去建立一个新客户的的端的连接。这样在代理的总耗时里就包括了一个tcp连接的握手时间。如果能够让客户端实现连接池复用已有连接的话,可以缩短端到端的延迟。

        var池=(陈净。康涅狄格州,100)      func()(净借款。康涅狄格州,错误){   选择{   康涅狄格州:=& lt; -池:   返回康涅狄格州,零   默认值:   返回净。刻度盘(“tcp”、“127.0.0.1:8849”)   }   }      函数释放(康涅狄格州net.Conn)错误{   选择{   池& lt; -康涅狄格州://返回到池中   返回nil   默认值://池溢出   返回conn.Close ()   }   }      函数处理(服务器* net.TCPConn) {   推迟server.Close ()   客户端,犯错:=借()   如果犯错!=nil {   fmt.Print (err)   返回   }   推迟发布(客户端)   去func () {   推迟server.Close ()   推迟发布(客户端)   缓冲区:=([]字节,2048)   io。CopyBuffer(服务器、客户机buf)   }()   缓冲区:=([]字节,2048)   io。buf CopyBuffer(客户端,服务器)   }      

这个版本的实现是显而易见有问题的。因为连接在归还到池里的时候并不能保证是还保持连接的状态。另外一个更严重的问题是,因为客户的连接不再被关闭了,当服务器端关闭连接时,从客户机向服务器做io.CopyBuffer的goroutine就无法退出了。

  

所以,有以下几个问题要解决:

  
      <李>如何在一个goroutine时退出时另外一个goroutine也退出?李   <李>怎么保证归还给池的连接是有效的?李   <李>怎么保持在池中的连接仍然是一直有效的?李   
  

  

一个普遍的观点是Goroutine是无法被中断的。当一个Goroutine在做conn.Read时,这个协程就被阻塞在那里了。实际上并不是毫无办法的,我们可以通过conn.Close来中断Goroutine。但是在连接池的情况下,又无法闭链接。另外一种做法就是通过SetDeadline为一个过去的时间戳来中断当前正在进行的阻塞读或者阻塞写。

        var池=(陈净。康涅狄格州,100)      客户类型结构{   康涅狄格州net.Conn   inUse * sync.WaitGroup   }      func借()(此时此地*客户,犯错错误){   var康涅狄格州net.Conn   选择{   康涅狄格州=& lt; -池:   默认值:   康涅狄格州,呃=净。刻度盘(“tcp”、“127.0.0.1:18849”)   }   如果犯错!=nil {   返回nil,犯错   }   解释水平理论=,客户{   康涅狄格州:康涅狄格州,   inUse:, sync.WaitGroup {},   }   返回   }      func释放此时此地*客户)错误{   阀门clt.conn.SetDeadline (time.Now () (-time.Second))   clt.inUse.Done ()   clt.inUse.Wait ()   选择{   池& lt; - clt.conn://返回到池中   返回nil   默认值://池溢出   返回clt.conn.Close ()   }   }      函数处理(服务器* net.TCPConn) {   推迟server.Close ()   解释水平理论,犯错:=借()   如果犯错!=nil {   fmt.Print (err)   返回   }   clt.inUse.Add (1)   推迟发布此时此地)   去func () {   clt.inUse.Add (1)   推迟server.Close ()   推迟发布此时此地)   缓冲区:=([]字节,2048)   io。CopyBuffer(服务器,此时此地。康涅狄格州,buf)   }()   缓冲区:=([]字节,2048)   io.CopyBuffer此时此地。康涅狄格州,服务器,buf)   }

利用Golang实现TCP连接的双向拷贝详解