再次思考一下go网络包中的接口设计

Posted 柳清风09

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了再次思考一下go网络包中的接口设计相关的知识,希望对你有一定的参考价值。

我们经常使用的http包,无论是客户端还是服务端,都有很多值得推敲的地方。
首先是服务端设计;上上篇 我们通过监听一个UDS提供一个http服务,代码大概是这样的

   unixListener, err := net.Listen("unix", os.Args[1])
	if err != nil 
		panic(err)
	
	server.Serve(unixListener)

这里创建一个listener,然后交给serve去处理。这就体现了接口的作用,把监听交给服务去处理,这里监听其实只要实现着下面三个接口就行

type Listener interface 
	// Accept waits for and returns the next connection to the listener.
	Accept() (Conn, error)

	// Close closes the listener.
	// Any blocked Accept operations will be unblocked and return errors.
	Close() error

	// Addr returns the listener's network address.
	Addr() Addr

其中最重要的就是Accept。接收请求,交给serve去处理。同理如果是监听vscok也可以,譬如下面的代码。

   l, err := vsock.Listen(1025)

	// Create SOCKS5 proxy on vsock
	if err := server.Serve(l); err != nil 
		panic(err)
	

也是一样,实现accept的方法的vscok同样可以作为接收器交给serve去处理。那server具体干怎么处理了?不用想也可知道,就是从accept里面获取请求,然后启动go 协程处理。譬如github.com/armon/go-socks5中Serve的代码

func (s *Server) Serve(l net.Listener) error 
	for 
		conn, err := l.Accept()
		if err != nil 
			return err
		
		go s.ServeConn(conn)
	
	return nil

如果是http Serve其实也是类似的。

这里顺便说一下,我们经常使用的ListenAndServe这个方法,这个方法名副其实,就是把listen和serve放到一起了。先listen后serve。

func (s *Server) ListenAndServe(network, addr string) error 
	l, err := net.Listen(network, addr)
	if err != nil 
		return err
	
	return s.Serve(l)

我们再来看看客户端的代码,我们都知道http底层是tcp。tcp如何建立连接这部分是通过DialContext实现的

	// DialContext specifies the dial function for creating unencrypted TCP connections.
	// If DialContext is nil (and the deprecated Dial below is also nil),
	// then the transport dials using package net.
	//
	// DialContext runs concurrently with calls to RoundTrip.
	// A RoundTrip call that initiates a dial may end up using
	// a connection dialed previously when the earlier connection
	// becomes idle before the later DialContext completes.
	DialContext func(ctx context.Context, network, addr string) (net.Conn, error)

如果我们需要实现自定义的tcp连接,我们可以自己去实现,还是回到之前UDS的例子

httpc := http.Client
		Transport: &http.Transport
			DialContext: func(_ context.Context, _, _ string) (net.Conn, error) 
				return net.Dial("unix", flag.Args()[0]) //uds 路径
			,
		,
	

我们通过 net.Dial(“unix”) 建立UDS的tcp连接。如果是我们自己可以基于sock5协议实现一个dail方法,去代理我们的请求,我们就可以这样实现。

func (sf *Client) Dial(network, addr string) (net.Conn, error) 
	if network == "tcp" 
		return sf.DialTCP(network, addr)
	
	if network == "udp" 
		return sf.DialUDP(network, nil, addr)
	
	return nil, errors.New("not support network")

然后便可以直接使用

	httpClient := http.Client
		Transport: &http.Transport
			DialContext: func(ctx context.Context, protocl, addr string) (net.Conn, error) 
				return client.Dial(protocl, addr)
			,
		,
	

注意,这里的protocl 已经解析成tcp或者udp协议了。
接口让世界更美好!

以上是关于再次思考一下go网络包中的接口设计的主要内容,如果未能解决你的问题,请参考以下文章

go语言interface设计的一点思考

go语言interface设计的一点思考

为啥我不能用主包中的方法集合调用接口

Go语言中的Interface

GO要点新解

os包方法