Skip to content

20210304关于数据源的析构流程(1)

zqy edited this page Mar 4, 2021 · 1 revision

基于:
“无论怎样都不应该在消费端关闭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()的操作
逻辑的顺序是这样的:

  1. 任意某个对象执行了Close(),但是之后不开启如上所描述的“树式析构链”而是只去进行自身下游的“链式析构链”
  2. 其他所包含net.Conn对象,内部的心跳包会检测到OpError的产生,但是不去判断其具体类型与内容,而是只按照心跳包超时逻辑进行自身接下来的操作,以及自身下游的“链式析构链”
  3. 用这种形式进行其他包含着net.Conn字段对象的隐式析构

这样一来,Read()与Write()所返回的error就不再重要了,然而Close返回的error依然重要,隐式析构时同样需要进行Close操作,并且如有必要也需要进行对返回error的辨别操作
因为net.Conn属于一个类似io的东西,其自身内部定会存在需要退出的子携线程与循环,所以有开就必须有闭

问题的本质是net.Conn的double close,可以再去研读滴滴的那篇文章了:

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()的操作都会返回同样的错误内容
这里探讨一个模式,单独对于心跳包探讨一下,请看下一篇

Clone this wiki locally