虽然Go语言没有类的概念,也没有所谓继承,所以谈不上属于真正的面向对象的编程语言,但是拥有非常灵活的接口概念,而且独特之处在于它是满足隐式实现的,不需要类似implements这样的关键字,编译器自动在需要的时候检查两个类型之间的实现关系。
接口是一种双方的约定,其接口类型是对其他类型行为的抽象和概括,接口的实现者不需要关心接口会被怎样使用,调用者也不需要关心接口的实现细节,接口的定义和结构体类似,所以也是一种自定义类型,是一种抽象结构,不会暴露所含数据的格式、类型和结构。
接口的实现与错误处理
接口声明格式如下:
type 名称 interface{
方法名称1(参数...) (返回值...)
方法名称2(参数...) (返回值...)
...
}
具体来看一个示例,熟悉接口的声明与调用,然后针对接口的规范,我们故意设计一些出错的情况来了解它:
package main
import (
"fmt"
)
// 声明一个Tester接口
type Tester interface {
PrintData(info interface{}) error
}
// 定义一个结构体实现这个接口
type person struct {
}
// 实现接口里面的方法
// error返回类型,表示可能发生的错误
func (p *person) PrintData(info interface{}) error {
fmt.Println(info)
return nil
}
func main() {
// 实例化person,类型是*person
p := new(person)
// 声明一个接口变量
var tester Tester
//将*person类型的p赋值给接口变量,虽然两个变量类型不一致。但是tester是一个接口,且p已经完全实现了接口的所有方法,因此赋值是成功的
tester = p
// 接口变量实现其定义的方法
tester.PrintData("你好,寅恪光潜")
}
//你好,寅恪光潜
错误一,方法名称需一致
如果我们将PrintData方法名称修改为PrintData1,也就是说不去实现接口中的PrintData方法,虽然PrintData1写法都是合乎规范的,但由于修改的名称方法,没有实现,所以会报错:
./prog.go:29:11: cannot use p (variable of type *person) as type Tester in assignment:
*person does not implement Tester (missing PrintData method)
报错提示没有实现Tester接口里面的PrintData方法。也就是说tester = p这块的赋值就不会成功了。
因为编译器扫描到这块赋值的时候,发现试着将*person类型赋值给tester时,需要检查*person类型是否完全实现了Tester接口。显然,编译器没有找到这个接口需要的PrintData方法,所以就会报错了。
错误二,参数类型需一致
如果我们将PrintData(info interface{})参数的类型修改为PrintData(info string),我们看下报错:
./prog.go:29:11: cannot use p (variable of type *person) as type Tester in assignment:
*person does not implement Tester (wrong type for PrintData method)
have PrintData(info string) error
want PrintData(info interface{}) error
这个就是方法参数类型不一致导致,所以类型也需要一样的才可以
错误三,方法需全部实现
由于接口是属于双方的一种约定,那么其定义的方法,就需要全部的实现,这样才可以被正确的编译执行。现在我们在Tester接口中增加一个方法:
type Tester interface {
PrintData(info interface{}) error
//新增一个方法
PrintData2() string
}
运行之后,将出现报错信息:
./prog.go:31:11: cannot use p (variable of type *person) as type Tester in assignment:
*person does not implement Tester (missing PrintData2 method)
就会提示这个Tester接口还有一个PrintData2()方法没有被实现。
socket套接字
socket是一种套接字,对于新手来说,套接字可能不好理解,你可以将它看做一种特殊的文件,可以连接打开与读写,因为起源于Linux,而Linux是将任何东西或设备都当文件来对待,这样去看待就很容易理解了。
服务端:server.go
package main
import (
"fmt"
"log"
"net"
"bufio"
)
func handleConnection(conn net.Conn) {
// 一直监听客户端的连接
// 连接成功,如果客户端被强制中断,将报错:
// 2022/11/05 11:37:05 客户端错误信息: read tcp 127.0.0.1:6010->127.0.0.1:3651: wsarecv: An existing connection was forcibly closed by the remote host.
data, err := bufio.NewReader(conn).ReadString('\n')
if err != nil {
log.Fatal("客户端错误信息:", err)
}
fmt.Printf("%v", data)
fmt.Fprintf(conn, "已连接到服务端\n")
conn.Close()
}
func main() {
ln, err := net.Listen("tcp", ":6010")
if err != nil {
panic(err)
}
for {
conn, err := ln.Accept()
if err != nil {
log.Fatal("客户端连接错误:", err)
}
go handleConnection(conn)
}
}
客户端:client.go
package main
import (
"fmt"
"net"
"bufio"
"log"
)
func main() {
// 客户端连接服务端,服务端的端口是6010
// 如果没有连接上或服务端没有开启,将报错:
// 2022/11/05 12:01:09 服务器拒绝服务:dial tcp :6010: connectex: No connection could be made because the target machine actively refused it.exit status 1
conn, err := net.Dial("tcp", ":6010")
if err != nil {
log.Fatal("服务器拒绝服务:",err)
}
// 如果客户端连接成功,服务端反馈显示:客户端已连接
fmt.Fprintf(conn, "客户端已连接\n")
data, err := bufio.NewReader(conn).ReadString('\n')
if err != nil {
panic(err)
}
fmt.Printf("%v", data)
}
然后分别各自启动,就可以看到它们的连接状态了。
以下更多关于套接字的文章,多属于攻防方面,有兴趣的可以看下: