-
Notifications
You must be signed in to change notification settings - Fork 0
20210304关于数据源的析构流程(1)
基于:
“无论怎样都不应该在消费端关闭channel,因为在消费端无法判断生产端是否还会向通道中发送元素值”
的设计思路,ProactiveDestruct()的存在是不合理的
但是也不是完全不合理,对于数据的真正源头,也就是如riverconn中,数据的最初生产者,还是需要对其进行逻辑上的主动析构的操作的
接下来我们来对比一下TestDataCreater(一个数据生产端模拟器)
与
Clent(usr-io808)真实的物理数据源消费者之间的代码结构,从而让逻辑更为清晰,实现系统的说明问题
首先还是感觉基于Clent(usr-io808)设计主动析构逻辑比较好,因为其会包含net.Conn这个最底层的数据源,同时他后期也会被door,天窗,以及给io8080设备定期发送询码这些“功能对象”所包含,因此一旦任何一个“功能对象”中net.Conn被执行了close,都会首先“树式析构”影响到这些“功能对象”,之后各对象的内部才会进而形成“链式析构”
关于net.OpError:
管道关闭后再去进行Conn.Read()或者Conn.Write()都会直接返回一个OpError,而OpError是个结构体:
先看一个英语单词:
职场中,Operations Manager是业务经理的意思。
Operations Manager意思是:操作管bai理;执行经理;营运部经理; 运营经理等。
业务经理对外,与客户进行谈判、联络、提案、收款等;对内,制定策略、协调资源、分派工作、监督进程等。
OpError的前缀Op既是代表Operations,因此可以翻译成“一个可以用来进行业务对接的错误类型”
type OpError struct {
// Op is the operation which caused the error, such as
// "read" or "write".
Op string
// Net is the network type on which this error occurred,
// such as "tcp" or "udp6".
Net string
// For operations involving a remote network connection, like
// Dial, Read, or Write, Source is the corresponding local
// network address.
Source Addr
// Addr is the network address for which this error occurred.
// For local operations, like Listen or SetDeadline, Addr is
// the address of the local endpoint being manipulated.
// For operations involving a remote network connection, like
// Dial, Read, or Write, Addr is the remote address of that
// connection.
Addr Addr
// Err is the error that occurred during the operation.
// The Error method panics if the error is nil.
Err error
}
再去观察Net.Conn几个重要方法的代码:
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
}
// Write implements the Conn Write method.
func (c *conn) Write(b []byte) (int, error) {
if !c.ok() {
return 0, syscall.EINVAL
}
n, err := c.fd.Write(b)
if err != nil {
err = &OpError{Op: "write", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
}
return n, err
}
// Close closes the connection.
func (c *conn) Close() error {
if !c.ok() {
return syscall.EINVAL
}
err := c.fd.Close()
if err != nil {
err = &OpError{Op: "close", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
}
return err
}
由此可知无论net.Conn发生了哪种错误都会返回一个OpError结构类对象,因此只判断返回的error是否为OpError不足以判定错误的产生原因是否为链接已经在别处正常关闭
另一方面,尽管OpError的功能比普通的error内置类型多很多,但是他依然是作为一个error返回给上级的,于是唯一能调用的方法只有error.Error()
从而造成判断依据只能是一个string字符串,这是问题的核心
此文章使用了反射将error接口反射回了OpError结构体的指针,从而更深入的基于内部字段fd判断错误的类型,但是这并不优雅:
https://blog.csdn.net/zhenshanren/article/details/103646626
func readAllShut(conn net.Conn) ([]byte,error){ //这个手动方法可以避免粘包的问题
//bufio.NewWriter
re:=bytes.NewBuffer(nil)
const N=666
for{
var text [N]byte
lens,err:=conn.Read(text[0:])
re.Write(text[:lens])
if lens==0 || err!=nil{
//log.Println(err) //在这个死循环里面,不要有任何的输出
// if errors.As(err,*net.OpError) //
if _,ok:=err.( *net.OpError) ;ok{
return nil,err
}
break
}
//conn
//log.Println(lens,text)
if lens<N{
break
}
}
rb:= re.Bytes()
//log.Println(rb,"len",len(rb))
return rb,nil
/*data,err:=ioutil.ReadAll(conn)
if err!=nil{
log.Printf("读取出现错误%T:%v",err,err)
}
return data;*/
}
现在换成另一个思路,考虑把关闭这一需求逻辑并入心跳包逻辑,是否可行呢?
也就是说,任何包含了net.Conn的对象都会包含一个直接进行net.Conn.Close()的操作
逻辑的顺序是这样的:
- 任意某个对象执行了Close(),但是之后不开启如上所描述的“树式析构链”而是只去进行自身下游的“链式析构链”
- 其他所包含net.Conn对象,内部的心跳包会检测到OpError的产生,但是不去判断其具体类型与内容,而是只按照心跳包超时逻辑进行自身接下来的操作,以及自身下游的“链式析构链”
- 用这种形式进行其他包含着net.Conn字段对象的隐式析构
这样一来,Read()与Write()所返回的error就不再重要了,然而Close返回的error依然重要,隐式析构时同样需要进行Close操作,并且如有必要也需要进行对返回error的辨别操作
因为net.Conn属于一个类似io的东西,其自身内部定会存在需要退出的子携线程与循环,所以有开就必须有闭
https://www.cnblogs.com/rsapaper/p/10850161.html
首先明确的是double close定会造成panic,不能无视他,还有就是net.Conn的内置相关逻辑用到了waitgroup,当wg.Done()的操作过多造成值为-1时程序panic,原文内容:
搞错了golang的设计这允许对net.Conn进行double close操作,但是滴滴作者并不认同golang的设计者如此设计,而我个人反倒比较认同golang的设计者
这并不重要,既然double close是被允许的,那就尽快看一下会返回什么错误吧:
func (c *conn) Close() error {
if !c.ok() {
return syscall.EINVAL
}
err := c.fd.Close()
if err != nil {
err = &OpError{Op: "close", Net: c.fd.net, Source: c.fd.laddr, Addr: c.fd.raddr, Err: err}
}
return err
}
可以得出结论,无论是对一个已关闭的net.Conn进行Read()或Write()或Close()的操作都会返回同样的错误内容
这里探讨一个模式,单独对于心跳包探讨一下,请看下一篇