golang 源码中对 interface的灵活运用

interface的灵活运用

 

1、struct TCPAddr 和 struct sockaddr 之间的灵活转换


2、关于系统调用errNo 到 err 的转换


3、实现 对 go 源码库 的 结构体的 接口封装

工作期间阅读了一下 go 中 mysql driver github.com/go-sql-driver/mysql@v1.5.0 的源代码,发现几个 interface 灵活应用的的小例子,特此记录一下

1、struct TCPAddr 和 struct sockaddr 之间的灵活转换

我们先来看一下几个定义(都是定义在/usr/local/go/src/net/tcpsock.go:5),主要是用来创建和设置socket,并封装成
struct fd 结构体

type TCPAddr struct {
	IP   IP
	Port int
	Zone string // IPv6 scoped addressing zone
}

type IP []byte

type sockaddr interface {
	Addr

	// family returns the platform-dependent address family
	// identifier.
	family() int

	// isWildcard reports whether the address is a wildcard
	// address.
	isWildcard() bool

	// sockaddr returns the address converted into a syscall
	// sockaddr type that implements syscall.Sockaddr
	// interface. It returns a nil interface when the address is
	// nil.
	sockaddr(family int) (syscall.Sockaddr, error)

	// toLocal maps the zero address to a local system address (127.0.0.1 or ::1)
	toLocal(net string) sockaddr
}

type Addr interface {
	Network() string // name of the network (for example, "tcp", "udp")
	String() string  // string form of address (for example, "192.0.2.1:25", "[2001:db8::1]:80")
}

其中 interface sockaddr,是 interface Addr 的 子接口 TCPAddr 实现了interface
sockaddr,同时 也就实现 interface Addr

在这里插入图片描述

有个创建TCP 连接的方法

func (sd *sysDialer) doDialTCP(ctx context.Context, laddr, raddr *TCPAddr) (*TCPConn, error) {
	fd, err := internetSocket(ctx, sd.network, laddr, raddr, syscall.SOCK_STREAM, 0, "dial", sd.Dialer.Control)
}

func internetSocket(ctx context.Context, net string, laddr, raddr sockaddr, sotype, proto int, mode string, ctrlFn func(string, string, syscall.RawConn) error) (fd *netFD, err error) {
	if (runtime.GOOS == "aix" || runtime.GOOS == "windows" || runtime.GOOS == "openbsd" || runtime.GOOS == "nacl") && mode == "dial" && raddr.isWildcard() {
		raddr = raddr.toLocal(net)
	}
	family, ipv6only := favoriteAddrFamily(net, laddr, raddr, mode)
	return socket(ctx, net, family, sotype, proto, ipv6only, laddr, raddr, ctrlFn)
}

func socket(ctx context.Context, net string, family, sotype, proto int, ipv6only bool, laddr, raddr sockaddr, ctrlFn func(string, string, syscall.RawConn) error) (fd *netFD, err error) {
	s, err := sysSocket(family, sotype, proto)
	if err != nil {
		return nil, err
	}
	if err = setDefaultSockopts(s, family, sotype, ipv6only); err != nil {
		poll.CloseFunc(s)
		return nil, err
	}
	if fd, err = newFD(s, family, sotype, net); err != nil {
		poll.CloseFunc(s)
		return nil, err
	}
	if laddr != nil && raddr == nil {
		switch sotype {
		case syscall.SOCK_STREAM, syscall.SOCK_SEQPACKET:
			if err := fd.listenStream(laddr, listenerBacklog(), ctrlFn); err != nil {
				fd.Close()
				return nil, err
			}
			return fd, nil
		case syscall.SOCK_DGRAM:
			if err := fd.listenDatagram(laddr, ctrlFn); err != nil {
				fd.Close()
				return nil, err
			}
			return fd, nil
		}
	}
	if err := fd.dial(ctx, laddr, raddr, ctrlFn); err != nil {
		fd.Close()
		return nil, err
	}
	return fd, nil
}

在进入 doDialTCP 方法前有个判断和类型转换

在这里插入图片描述

一开始 la 的值为空接口,interface Addr,经过 la, _ := la.(*TCPAddr) 后,la 变为 struct TCPAddr(只有在struct TCPAddr 实现了interface Addr 才可以做类型强行转化 ) ,进入到dialTCP方法后 调用 doDialTCP 方法

在这里插入图片描述

在 doDialTCP 方法中调用 internetSocket 方法,将 la 从 struct TCPAddr 匿名类型转换成 interface sockaddr ,因为 struct TCPAddr 除了实现了 父接口 interface Addr ,还实现了 子接口 interface sockaddr ,所以可以匿名转换

在这里插入图片描述

在进入socket 方法后,判断 laddr 是否为空,这是是不为空,因为根据传值laddr 是 为nil 的 struct TCPAddr 结构体,转换成 nil 的空接口interface sockaddr 后,由于动态类型 为 TCPAddr 不为 nil ,所以 if laddr != nil 为true ,关于判断 接口是否为空,需要判断静态类型和动态类型

2、关于系统调用errNo 到 err 的转换

func read(fd int, p []byte) (n int, err error) {
	var _p0 unsafe.Pointer
	if len(p) > 0 {
		_p0 = unsafe.Pointer(&p[0])
	} else {
		_p0 = unsafe.Pointer(&_zero)
	}
	r0, _, e1 := syscall(funcPC(libc_read_trampoline), uintptr(fd), uintptr(_p0), uintptr(len(p)))
	n = int(r0)
	if e1 != 0 {
		err = errnoErr(e1)
	}
	return
}

func syscall(fn, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)

type Errno uintptr

func (e Errno) Error() string {
	if 0 <= int(e) && int(e) < len(errors) {
		s := errors[e]
		if s != "" {
			return s
		}
	}
	return "errno " + itoa(int(e))
}

这里 执行 syscall 后 返回 Errno,属于 uintptr ,由于Errno实现了 Error() 方法,所以 Errno 类型 可以直接 转为 error 接口类型,后续就是对err实现一系列对封装,比如

func NewSyscallError(syscall string, err error) error {
	if err == nil {
		return nil
	}
	return &SyscallError{syscall, err}
}

type SyscallError struct {
	Syscall string
	Err     error
}

func (e *SyscallError) Error() string { return e.Syscall + ": " + e.Err.Error() }

type OpError struct {
	Op string
	
	Net string

	Source Addr

	Addr Addr

	Err error
}

func (c *conn) Read(b []byte) (int, error) {
	if !c.ok() {
		return 0, syscall.EINVAL
	}
	n, err := c.fd.Read(b)
	if err != nil && err != io.EOF {
		err = &OpError{Op: "read", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
	}
	return n, err
}

3、实现 对 go 源码库 的 结构体的 接口封装

在 github.com/go-sql-driver/mysql@v1.5.0/errors.go:43 中定义了接口,并使用改接口封装原依赖库结构体

type Logger interface {
	Print(v ...interface{})
}

var errLog = Logger(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))

其中 log.New 方法在 go/src/log/log.go:62 中

func New(out io.Writer, prefix string, flag int) *Logger {
	return &Logger{out: out, prefix: prefix, flag: flag}
}

type Logger struct {
	mu     sync.Mutex // ensures atomic writes; protects the following fields
	prefix string     // prefix to write at beginning of each line
	flag   int        // properties
	out    io.Writer  // destination for output
	buf    []byte     // for accumulating text to write
}

在 github.com/go-sql-driver/mysql@v1.5.0/errors.go:43中 直接使用
Logger(&Logger()),之所以可以这样,是因为,*Logger 实现了这个接口 Print(v …interface{}) 方法,因此我们可以自定义接口封装源码库提供的原生结构体

func (l *Logger) Print(v ...interface{}) { l.Output(2, fmt.Sprint(v...)) }

我们同时也可以自定义自己的log

var errLog = Mylog(log.New(os.Stderr, "[mysql] ", log.Ldate|log.Ltime|log.Lshortfile))


type Mylog interface {
	Print(v ...interface{})
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值