chore: simplify tuic client

This commit is contained in:
wwqgtxx 2025-12-02 21:07:51 +08:00
parent ac90543548
commit bc719eb96d
6 changed files with 114 additions and 156 deletions

View File

@ -70,12 +70,7 @@ type TuicOption struct {
// DialContext implements C.ProxyAdapter
func (t *Tuic) DialContext(ctx context.Context, metadata *C.Metadata) (C.Conn, error) {
return t.DialContextWithDialer(ctx, dialer.NewDialer(t.DialOptions()...), metadata)
}
// DialContextWithDialer implements C.ProxyAdapter
func (t *Tuic) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (C.Conn, error) {
conn, err := t.client.DialContextWithDialer(ctx, metadata, dialer, t.dialWithDialer)
conn, err := t.client.DialContext(ctx, metadata)
if err != nil {
return nil, err
}
@ -84,11 +79,6 @@ func (t *Tuic) DialContextWithDialer(ctx context.Context, dialer C.Dialer, metad
// ListenPacketContext implements C.ProxyAdapter
func (t *Tuic) ListenPacketContext(ctx context.Context, metadata *C.Metadata) (_ C.PacketConn, err error) {
return t.ListenPacketWithDialer(ctx, dialer.NewDialer(t.DialOptions()...), metadata)
}
// ListenPacketWithDialer implements C.ProxyAdapter
func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, metadata *C.Metadata) (_ C.PacketConn, err error) {
if err = t.ResolveUDP(ctx, metadata); err != nil {
return nil, err
}
@ -98,7 +88,7 @@ func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, meta
uotMetadata := *metadata
uotMetadata.Host = uotDestination.Fqdn
uotMetadata.DstPort = uotDestination.Port
c, err := t.DialContextWithDialer(ctx, dialer, &uotMetadata)
c, err := t.DialContext(ctx, &uotMetadata)
if err != nil {
return nil, err
}
@ -112,21 +102,17 @@ func (t *Tuic) ListenPacketWithDialer(ctx context.Context, dialer C.Dialer, meta
return newPacketConn(uot.NewLazyConn(c, uot.Request{Destination: destination}), t), nil
}
}
pc, err := t.client.ListenPacketWithDialer(ctx, metadata, dialer, t.dialWithDialer)
pc, err := t.client.ListenPacket(ctx, metadata)
if err != nil {
return nil, err
}
return newPacketConn(pc, t), nil
}
// SupportWithDialer implements C.ProxyAdapter
func (t *Tuic) SupportWithDialer() C.NetWork {
return C.ALLNet
}
func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error) {
func (t *Tuic) dial(ctx context.Context) (transport *quic.Transport, addr net.Addr, err error) {
var cDialer C.Dialer = dialer.NewDialer(t.DialOptions()...)
if len(t.option.DialerProxy) > 0 {
dialer, err = proxydialer.NewByName(t.option.DialerProxy, dialer)
cDialer, err = proxydialer.NewByName(t.option.DialerProxy, cDialer)
if err != nil {
return nil, nil, err
}
@ -141,7 +127,7 @@ func (t *Tuic) dialWithDialer(ctx context.Context, dialer C.Dialer) (transport *
}
addr = udpAddr
var pc net.PacketConn
pc, err = dialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
pc, err = cDialer.ListenPacket(ctx, "udp", "", udpAddr.AddrPort())
if err != nil {
return nil, nil, err
}
@ -313,7 +299,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
CWND: option.CWND,
}
t.client = tuic.NewPoolClientV4(clientOption)
t.client = tuic.NewPoolClientV4(clientOption, t.dial)
} else {
maxUdpRelayPacketSize := option.MaxUdpRelayPacketSize
if maxUdpRelayPacketSize > tuic.MaxFragSizeV5 {
@ -332,7 +318,7 @@ func NewTuic(option TuicOption) (*Tuic, error) {
CWND: option.CWND,
}
t.client = tuic.NewPoolClientV5(clientOption)
t.client = tuic.NewPoolClientV5(clientOption, t.dial)
}
return t, nil

View File

@ -18,13 +18,12 @@ var (
TooManyOpenStreams = errors.New("tuic: too many open streams")
)
type DialFunc func(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error)
type DialFunc func(ctx context.Context) (transport *quic.Transport, addr net.Addr, err error)
type Client interface {
DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.Conn, error)
ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.PacketConn, error)
DialContext(ctx context.Context, metadata *C.Metadata) (net.Conn, error)
ListenPacket(ctx context.Context, metadata *C.Metadata) (net.PacketConn, error)
OpenStreams() int64
DialerRef() C.Dialer
LastVisited() time.Time
SetLastVisited(last time.Time)
Close()

View File

@ -6,6 +6,7 @@ import (
"net"
"runtime"
"sync"
"sync/atomic"
"time"
N "github.com/metacubex/mihomo/common/net"
@ -17,30 +18,21 @@ import (
list "github.com/bahlo/generic-list-go"
)
type dialResult struct {
transport *quic.Transport
addr net.Addr
err error
}
type PoolClient struct {
newClientOptionV4 *ClientOptionV4
newClientOptionV5 *ClientOptionV5
dialResultMap map[C.Dialer]dialResult
dialResultMutex *sync.Mutex
tcpClients *list.List[Client]
tcpClientsMutex *sync.Mutex
udpClients *list.List[Client]
udpClientsMutex *sync.Mutex
dialHelper *poolDialHelper
tcpClients list.List[Client]
tcpClientsMutex sync.Mutex
udpClients list.List[Client]
udpClientsMutex sync.Mutex
}
func (t *PoolClient) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.Conn, error) {
newDialFn := func(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error) {
return t.dial(ctx, dialer, dialFn)
}
conn, err := t.getClient(false, dialer).DialContextWithDialer(ctx, metadata, dialer, newDialFn)
func (t *PoolClient) DialContext(ctx context.Context, metadata *C.Metadata) (net.Conn, error) {
conn, err := t.getClient(false).DialContext(ctx, metadata)
if errors.Is(err, TooManyOpenStreams) {
conn, err = t.newClient(false, dialer).DialContextWithDialer(ctx, metadata, dialer, newDialFn)
conn, err = t.newClient(false).DialContext(ctx, metadata)
}
if err != nil {
return nil, err
@ -48,13 +40,10 @@ func (t *PoolClient) DialContextWithDialer(ctx context.Context, metadata *C.Meta
return N.NewRefConn(conn, t), err
}
func (t *PoolClient) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn DialFunc) (net.PacketConn, error) {
newDialFn := func(ctx context.Context, dialer C.Dialer) (transport *quic.Transport, addr net.Addr, err error) {
return t.dial(ctx, dialer, dialFn)
}
pc, err := t.getClient(true, dialer).ListenPacketWithDialer(ctx, metadata, dialer, newDialFn)
func (t *PoolClient) ListenPacket(ctx context.Context, metadata *C.Metadata) (net.PacketConn, error) {
pc, err := t.getClient(true).ListenPacket(ctx, metadata)
if errors.Is(err, TooManyOpenStreams) {
pc, err = t.newClient(true, dialer).ListenPacketWithDialer(ctx, metadata, dialer, newDialFn)
pc, err = t.newClient(true).ListenPacket(ctx, metadata)
}
if err != nil {
return nil, err
@ -62,58 +51,63 @@ func (t *PoolClient) ListenPacketWithDialer(ctx context.Context, metadata *C.Met
return N.NewRefPacketConn(pc, t), nil
}
func (t *PoolClient) dial(ctx context.Context, dialer C.Dialer, dialFn DialFunc) (transport *quic.Transport, addr net.Addr, err error) {
t.dialResultMutex.Lock()
dr, ok := t.dialResultMap[dialer]
t.dialResultMutex.Unlock()
if ok {
return dr.transport, dr.addr, dr.err
// poolDialHelper is a helper for dialFn
// using a standalone struct to let finalizer working
type poolDialHelper struct {
dialFn DialFunc
dialResult atomic.Pointer[dialResult]
}
type dialResult struct {
transport *quic.Transport
addr net.Addr
}
func (t *poolDialHelper) dial(ctx context.Context) (transport *quic.Transport, addr net.Addr, err error) {
if dr := t.dialResult.Load(); dr != nil {
return dr.transport, dr.addr, nil
}
transport, addr, err = dialFn(ctx, dialer)
transport, addr, err = t.dialFn(ctx)
if err != nil {
return nil, nil, err
}
if _, ok := transport.Conn.(*net.UDPConn); ok { // only cache the system's UDPConn
transport.SetSingleUse(false) // don't close transport in each dial
dr.transport, dr.addr, dr.err = transport, addr, err
t.dialResultMutex.Lock()
t.dialResultMap[dialer] = dr
t.dialResultMutex.Unlock()
dr := &dialResult{transport: transport, addr: addr}
t.dialResult.Store(dr)
}
return transport, addr, err
}
func (t *PoolClient) forceClose() {
t.dialResultMutex.Lock()
defer t.dialResultMutex.Unlock()
for key := range t.dialResultMap {
transport := t.dialResultMap[key].transport
func (t *poolDialHelper) forceClose() {
if dr := t.dialResult.Swap(nil); dr != nil {
transport := dr.transport
if transport != nil {
_ = transport.Close()
}
delete(t.dialResultMap, key)
}
}
func (t *PoolClient) newClient(udp bool, dialer C.Dialer) (client Client) {
clients := t.tcpClients
clientsMutex := t.tcpClientsMutex
func (t *PoolClient) newClient(udp bool) (client Client) {
clients := &t.tcpClients
clientsMutex := &t.tcpClientsMutex
if udp {
clients = t.udpClients
clientsMutex = t.udpClientsMutex
clients = &t.udpClients
clientsMutex = &t.udpClientsMutex
}
clientsMutex.Lock()
defer clientsMutex.Unlock()
dialHelper := t.dialHelper
if t.newClientOptionV4 != nil {
client = NewClientV4(t.newClientOptionV4, udp, dialer)
client = NewClientV4(t.newClientOptionV4, udp, dialHelper.dial)
} else {
client = NewClientV5(t.newClientOptionV5, udp, dialer)
client = NewClientV5(t.newClientOptionV5, udp, dialHelper.dial)
}
client.SetLastVisited(time.Now())
@ -122,12 +116,12 @@ func (t *PoolClient) newClient(udp bool, dialer C.Dialer) (client Client) {
return client
}
func (t *PoolClient) getClient(udp bool, dialer C.Dialer) Client {
clients := t.tcpClients
clientsMutex := t.tcpClientsMutex
func (t *PoolClient) getClient(udp bool) Client {
clients := &t.tcpClients
clientsMutex := &t.tcpClientsMutex
if udp {
clients = t.udpClients
clientsMutex = t.udpClientsMutex
clients = &t.udpClients
clientsMutex = &t.udpClientsMutex
}
var bestClient Client
@ -142,46 +136,39 @@ func (t *PoolClient) getClient(udp bool, dialer C.Dialer) Client {
it = next
continue
}
if client.DialerRef() == dialer {
if bestClient == nil {
if bestClient == nil {
bestClient = client
} else {
if client.OpenStreams() < bestClient.OpenStreams() {
bestClient = client
} else {
if client.OpenStreams() < bestClient.OpenStreams() {
bestClient = client
}
}
}
it = it.Next()
}
}()
for it := clients.Front(); it != nil; {
client := it.Value
if client != bestClient && client.OpenStreams() == 0 && time.Now().Sub(client.LastVisited()) > 30*time.Minute {
client.Close()
next := it.Next()
clients.Remove(it)
it = next
continue
for it := clients.Front(); it != nil; {
client := it.Value
if client != bestClient && client.OpenStreams() == 0 && time.Now().Sub(client.LastVisited()) > 30*time.Minute {
client.Close()
next := it.Next()
clients.Remove(it)
it = next
continue
}
it = it.Next()
}
it = it.Next()
}
}()
if bestClient == nil {
return t.newClient(udp, dialer)
return t.newClient(udp)
} else {
bestClient.SetLastVisited(time.Now())
return bestClient
}
}
func NewPoolClientV4(clientOption *ClientOptionV4) *PoolClient {
func NewPoolClientV4(clientOption *ClientOptionV4, dialFn DialFunc) *PoolClient {
p := &PoolClient{
dialResultMap: make(map[C.Dialer]dialResult),
dialResultMutex: &sync.Mutex{},
tcpClients: list.New[Client](),
tcpClientsMutex: &sync.Mutex{},
udpClients: list.New[Client](),
udpClientsMutex: &sync.Mutex{},
dialHelper: &poolDialHelper{dialFn: dialFn},
}
newClientOption := *clientOption
p.newClientOptionV4 = &newClientOption
@ -190,14 +177,9 @@ func NewPoolClientV4(clientOption *ClientOptionV4) *PoolClient {
return p
}
func NewPoolClientV5(clientOption *ClientOptionV5) *PoolClient {
func NewPoolClientV5(clientOption *ClientOptionV5, dialFn DialFunc) *PoolClient {
p := &PoolClient{
dialResultMap: make(map[C.Dialer]dialResult),
dialResultMutex: &sync.Mutex{},
tcpClients: list.New[Client](),
tcpClientsMutex: &sync.Mutex{},
udpClients: list.New[Client](),
udpClientsMutex: &sync.Mutex{},
dialHelper: &poolDialHelper{dialFn: dialFn},
}
newClientOption := *clientOption
p.newClientOptionV5 = &newClientOption
@ -208,5 +190,5 @@ func NewPoolClientV5(clientOption *ClientOptionV5) *PoolClient {
func closeClientPool(client *PoolClient) {
log.Debugln("Close Tuic PoolClient at %p", client)
client.forceClose()
client.dialHelper.forceClose()
}

View File

@ -1,7 +1,6 @@
package tuic
import (
C "github.com/metacubex/mihomo/constant"
"github.com/metacubex/mihomo/transport/tuic/common"
v4 "github.com/metacubex/mihomo/transport/tuic/v4"
v5 "github.com/metacubex/mihomo/transport/tuic/v5"
@ -12,12 +11,12 @@ type ClientOptionV5 = v5.ClientOption
type Client = common.Client
func NewClientV4(clientOption *ClientOptionV4, udp bool, dialerRef C.Dialer) Client {
return v4.NewClient(clientOption, udp, dialerRef)
func NewClientV4(clientOption *ClientOptionV4, udp bool, dialFn DialFunc) Client {
return v4.NewClient(clientOption, udp, dialFn)
}
func NewClientV5(clientOption *ClientOptionV5, udp bool, dialerRef C.Dialer) Client {
return v5.NewClient(clientOption, udp, dialerRef)
func NewClientV5(clientOption *ClientOptionV5, udp bool, dialFn DialFunc) Client {
return v5.NewClient(clientOption, udp, dialFn)
}
type DialFunc = common.DialFunc

View File

@ -40,7 +40,8 @@ type ClientOption struct {
type clientImpl struct {
*ClientOption
udp bool
dialFn common.DialFunc
udp bool
quicConn *quic.Conn
connMutex sync.Mutex
@ -51,7 +52,6 @@ type clientImpl struct {
udpInputMap xsync.Map[uint32, net.Conn]
// only ready for PoolClient
dialerRef C.Dialer
lastVisited atomic2.TypedValue[time.Time]
}
@ -59,10 +59,6 @@ func (t *clientImpl) OpenStreams() int64 {
return t.openStreams.Load()
}
func (t *clientImpl) DialerRef() C.Dialer {
return t.dialerRef
}
func (t *clientImpl) LastVisited() time.Time {
return t.lastVisited.Load()
}
@ -71,13 +67,13 @@ func (t *clientImpl) SetLastVisited(last time.Time) {
t.lastVisited.Store(last)
}
func (t *clientImpl) getQuicConn(ctx context.Context, dialer C.Dialer, dialFn common.DialFunc) (*quic.Conn, error) {
func (t *clientImpl) getQuicConn(ctx context.Context) (*quic.Conn, error) {
t.connMutex.Lock()
defer t.connMutex.Unlock()
if t.quicConn != nil {
return t.quicConn, nil
}
transport, addr, err := dialFn(ctx, dialer)
transport, addr, err := t.dialFn(ctx)
if err != nil {
return nil, err
}
@ -262,7 +258,7 @@ func (t *clientImpl) forceClose(quicConn *quic.Conn, err error) {
if quicConn != nil {
_ = quicConn.CloseWithError(ProtocolError, errStr)
}
udpInputMap := t.udpInputMap
udpInputMap := &t.udpInputMap
udpInputMap.Range(func(key uint32, value net.Conn) bool {
conn := value
_ = conn.Close()
@ -278,8 +274,8 @@ func (t *clientImpl) Close() {
}
}
func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.Conn, error) {
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
func (t *clientImpl) DialContext(ctx context.Context, metadata *C.Metadata) (net.Conn, error) {
quicConn, err := t.getQuicConn(ctx)
if err != nil {
return nil, err
}
@ -353,8 +349,8 @@ func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Meta
return bufConn, nil
}
func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.PacketConn, error) {
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
func (t *clientImpl) ListenPacket(ctx context.Context, metadata *C.Metadata) (net.PacketConn, error) {
quicConn, err := t.getQuicConn(ctx)
if err != nil {
return nil, err
}
@ -397,16 +393,16 @@ type Client struct {
*clientImpl // use an independent pointer to let Finalizer can work no matter somewhere handle an influence in clientImpl inner
}
func (t *Client) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.Conn, error) {
conn, err := t.clientImpl.DialContextWithDialer(ctx, metadata, dialer, dialFn)
func (t *Client) DialContext(ctx context.Context, metadata *C.Metadata) (net.Conn, error) {
conn, err := t.clientImpl.DialContext(ctx, metadata)
if err != nil {
return nil, err
}
return N.NewRefConn(conn, t), err
}
func (t *Client) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.PacketConn, error) {
pc, err := t.clientImpl.ListenPacketWithDialer(ctx, metadata, dialer, dialFn)
func (t *Client) ListenPacket(ctx context.Context, metadata *C.Metadata) (net.PacketConn, error) {
pc, err := t.clientImpl.ListenPacket(ctx, metadata)
if err != nil {
return nil, err
}
@ -417,11 +413,11 @@ func (t *Client) forceClose() {
t.clientImpl.forceClose(nil, common.ClientClosed)
}
func NewClient(clientOption *ClientOption, udp bool, dialerRef C.Dialer) *Client {
func NewClient(clientOption *ClientOption, udp bool, dialFn common.DialFunc) *Client {
ci := &clientImpl{
ClientOption: clientOption,
dialFn: dialFn,
udp: udp,
dialerRef: dialerRef,
}
c := &Client{ci}
runtime.SetFinalizer(c, closeClient)

View File

@ -39,7 +39,8 @@ type ClientOption struct {
type clientImpl struct {
*ClientOption
udp bool
dialFn common.DialFunc
udp bool
quicConn *quic.Conn
connMutex sync.Mutex
@ -50,7 +51,6 @@ type clientImpl struct {
udpInputMap xsync.Map[uint16, net.Conn]
// only ready for PoolClient
dialerRef C.Dialer
lastVisited atomic2.TypedValue[time.Time]
}
@ -58,10 +58,6 @@ func (t *clientImpl) OpenStreams() int64 {
return t.openStreams.Load()
}
func (t *clientImpl) DialerRef() C.Dialer {
return t.dialerRef
}
func (t *clientImpl) LastVisited() time.Time {
return t.lastVisited.Load()
}
@ -70,13 +66,13 @@ func (t *clientImpl) SetLastVisited(last time.Time) {
t.lastVisited.Store(last)
}
func (t *clientImpl) getQuicConn(ctx context.Context, dialer C.Dialer, dialFn common.DialFunc) (*quic.Conn, error) {
func (t *clientImpl) getQuicConn(ctx context.Context) (*quic.Conn, error) {
t.connMutex.Lock()
defer t.connMutex.Unlock()
if t.quicConn != nil {
return t.quicConn, nil
}
transport, addr, err := dialFn(ctx, dialer)
transport, addr, err := t.dialFn(ctx)
if err != nil {
return nil, err
}
@ -270,7 +266,7 @@ func (t *clientImpl) forceClose(quicConn *quic.Conn, err error) {
if quicConn != nil {
_ = quicConn.CloseWithError(ProtocolError, errStr)
}
udpInputMap := t.udpInputMap
udpInputMap := &t.udpInputMap
udpInputMap.Range(func(key uint16, value net.Conn) bool {
conn := value
_ = conn.Close()
@ -286,8 +282,8 @@ func (t *clientImpl) Close() {
}
}
func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.Conn, error) {
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
func (t *clientImpl) DialContext(ctx context.Context, metadata *C.Metadata) (net.Conn, error) {
quicConn, err := t.getQuicConn(ctx)
if err != nil {
return nil, err
}
@ -337,8 +333,8 @@ func (t *clientImpl) DialContextWithDialer(ctx context.Context, metadata *C.Meta
return stream, nil
}
func (t *clientImpl) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.PacketConn, error) {
quicConn, err := t.getQuicConn(ctx, dialer, dialFn)
func (t *clientImpl) ListenPacket(ctx context.Context, metadata *C.Metadata) (net.PacketConn, error) {
quicConn, err := t.getQuicConn(ctx)
if err != nil {
return nil, err
}
@ -381,16 +377,16 @@ type Client struct {
*clientImpl // use an independent pointer to let Finalizer can work no matter somewhere handle an influence in clientImpl inner
}
func (t *Client) DialContextWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.Conn, error) {
conn, err := t.clientImpl.DialContextWithDialer(ctx, metadata, dialer, dialFn)
func (t *Client) DialContext(ctx context.Context, metadata *C.Metadata) (net.Conn, error) {
conn, err := t.clientImpl.DialContext(ctx, metadata)
if err != nil {
return nil, err
}
return N.NewRefConn(conn, t), err
}
func (t *Client) ListenPacketWithDialer(ctx context.Context, metadata *C.Metadata, dialer C.Dialer, dialFn common.DialFunc) (net.PacketConn, error) {
pc, err := t.clientImpl.ListenPacketWithDialer(ctx, metadata, dialer, dialFn)
func (t *Client) ListenPacket(ctx context.Context, metadata *C.Metadata) (net.PacketConn, error) {
pc, err := t.clientImpl.ListenPacket(ctx, metadata)
if err != nil {
return nil, err
}
@ -401,11 +397,11 @@ func (t *Client) forceClose() {
t.clientImpl.forceClose(nil, common.ClientClosed)
}
func NewClient(clientOption *ClientOption, udp bool, dialerRef C.Dialer) *Client {
func NewClient(clientOption *ClientOption, udp bool, dialFn common.DialFunc) *Client {
ci := &clientImpl{
ClientOption: clientOption,
dialFn: dialFn,
udp: udp,
dialerRef: dialerRef,
}
c := &Client{ci}
runtime.SetFinalizer(c, closeClient)