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{})
}