Go语言学习(四)


这次关注的主题比较多:包括空接口、类型转换、并发编程和网络编程!


0、空接口类型

interface{} 类型,相当于java中的Object类型,可以匹配Go的任何数据类型,通常用在map等变量的value中使用,用来存放任意的数据类型的值:

var testmap map[string]interface{} = make(map[string]interface{})


接口类型的妙用: 

type IRouter interface{ ... }

type RouterGroup struct { ... }

var _ IRouter = &RouterGroup{}       // 确保RouterGroup实现了接口IRouter

var _ IRouter = (*RouterGroup)(nil)  // 作用同上


空接口是指没有定义任何方法的接口,因此任何类型都实现了空接口,空接口类型的变量可以存储任意类型的变量。

func main() {
	// 定义一个空接口x
	var x interface{}
	s := "Hello 沙河"
	x = s
	fmt.Printf("type:%T value:%v\n", x, x)
	i := 100
	x = i
	fmt.Printf("type:%T value:%v\n", x, x)
	b := true
	x = b
	fmt.Printf("type:%T value:%v\n", x, x)
}


1、Go并发编程,关注面向并发的内存模型

规则:只有在同一个Goroutine, 才满足顺序一致性内存模型,关注常见的并发模式,比方生产者、消费者模型,发布、订阅模型等等常见并发模式,建议查看这个文章


Go语言推荐更高层次的并发编程哲学:通过管道来传值;虽然像引用计数这类简单的并发问题通过原子操作或互斥锁就能很好地实现,但是通过Channel来控制访问能够让你写出更简洁正确的程序。


Channel的巧妙设计可以让多线程间的并发变得很容易,通过设计合适的业务流程,天然就支持共享内存的并发访问,不需要为此增加各种复杂的锁的逻辑;


Go语言将其并发编程哲学化为一句口号:

Do not communicate by sharing memory; instead, share memory by communicating.

不要通过共享内存来通信,而应通过通信来共享内存。


等待的实现:

var limit = make(chan int, 3)
func main() {
    for _, w := range work {
        go func() {
            limit <- 1
            w()
            <-limit
        }()
    }
    select{}
}


最后一句select{}是一个空的管道选择语句,该语句会导致main线程阻塞,从而避免程序过早退出。


基于sync.Once重新实现单件模式:

var (
    instance *singleton
    once     sync.Once
)
func Instance() *singleton {
    once.Do(func() {
        instance = &singleton{}
    })
    return instance
}


2、判断类型和类型转换

想要判断空接口中的值这个时候就可以使用类型断言,其语法格式:

x.(T)

其中:

* x:表示类型为interface{}的变量

* T:表示断言x可能是的类型


该语法返回两个参数,第一个参数是x转化为T类型后的变量,第二个值是一个布尔值,若为true则表示断言成功,为false则表示断言失败。

举个例子:

func main() {
	var x interface{}
	x = "Hello 沙河"
	v, ok := x.(string)
	if ok {
		fmt.Println(v)
	} else {
		fmt.Println("类型断言失败")
	}
}


go 存在 4 种类型转换分别为:断言、强制、显式、隐式。


- 断言:通过判断变量是否可以转换成某一个类型

一个简单的断言表达式:

var s = x.(T)

如果断言类型成立,则表达式返回值就是 T 类型的 x,如果断言失败就会触发 panic,go 提供了另外一种带返回是否成立的断言语法:

s, ok := x.(T)

类型switch:go 语法种还提供了另外一种类型 switch 的断言方法。

switch i := x.(type) {
case nil:
    printString("x is nil")                // type of i is type of x (interface{})
case int:
    printInt(i)                            // type of i is int
case float64:
    printFloat64(i)                        // type of i is float64
case func(int) float64:
    printFunction(i)                       // type of i is func(int) float64
case bool, string:
    printString("type is bool or string")  // type of i is type of x (interface{})
default:
    printString("don't know the type")     // type of i is type of x (interface{})
}


- 强制类型转换:通过修改变量类型:

该方法不常见,主要用于 unsafe 包和接口类型检测,需要懂得 go 变量的知识。


- 显示类型转换:一个显式转换的表达式 T (x) ,其中 T 是一种类型并且 x 是可转换为类型的表达式 T,例如:uint(666)。


在以下任何一种情况下,变量 x 都可以转换成 T 类型:

x 可以分配成 T 类型。

忽略 struct 标签 x 的类型和 T 具有相同的基础类型。

忽略 struct 标记 x 的类型和 T 是未定义类型的指针类型,并且它们的指针基类型具有相同的基础类型。

x 的类型和 T 都是整数或浮点类型。

x 的类型和 T 都是复数类型。

x 的类型是整数或 [] byte 或 [] rune,并且 T 是字符串类型。

x 的类型是字符串,T 类型是 [] byte 或 [] rune。


int64(222)
[]byte("ssss")
type A int
A(2)


- 隐式类型转换

隐式类型转换日常使用并不会感觉到,但是运行中确实出现了类型转换,以下列出了两种。

组合间的重新断言类型

type Reader interface {
    Read(p []byte) (n int, err error)
}
type ReadCloser interface {
    Reader
    Close() error
}
var rc ReaderClose
r := rc

相同类型间赋值

type Handler func()
func NewHandler() Handler {
    return func() {}
}


3、网络编程

一个简单的服务器和客户端:

//server.go

func main() {
	testTCPServer()
	// Ctrl+C 退出
	fmt.Println("CTL+C quit.....")
	sig := make(chan os.Signal, 1)
	signal.Notify(sig, syscall.SIGINT, syscall.SIGTERM)
	fmt.Printf("quit (%v)\n", <-sig)
}
func process(conn net.Conn) {
	// 处理完关闭连接
	defer conn.Close()
	// 针对当前连接做发送和接受操作
	for {
		reader := bufio.NewReader(conn)
		var buf [128]byte
		n, err := reader.Read(buf[:])
		if err != nil {
			fmt.Printf("read from conn failed, err:%v\n", err)
			break
		}
		recv := string(buf[:n])
		fmt.Printf("收到的数据:%v\n", recv)
		// 将接受到的数据返回给客户端
		_, err = conn.Write([]byte("ok"))
		if err != nil {
			fmt.Printf("write from conn failed, err:%v\n", err)
			break
		}
	}
}
func testTCPServer() {
	// 建立 tcp 服务
	fmt.Printf("testTCPServer listen localhost:9090  \n")
	listen, err := net.Listen("tcp", "127.0.0.1:9090")
	if err != nil {
		fmt.Printf("listen failed, err:%v\n", err)
		return
	}
	defer listen.Close()
	for {
		// 等待客户端建立连接
		conn, err := listen.Accept()
		if err != nil {
			fmt.Printf("accept failed, err:%v\n", err)
			continue
		}
		// 启动一个单独的 goroutine 去处理连接
		go process(conn)
	}
}


//client.go

func main() {
	testTCPClient()
}
func testTCPClient() {
	fmt.Println("connect to server 127.0.0.1:9090")
	//与服务器建立连接
	conn, err := net.Dial("tcp", "127.0.0.1:9090")
	if err != nil {
		fmt.Printf("conn server failed, err:%v\n", err)
		return
	}
	//使用conn 连接进行数据的发送和接收
	input := bufio.NewReader(os.Stdin)
	for {
		fmt.Printf("Input your command:")
		s, _ := input.ReadString('\n')
		s = strings.TrimSpace(s)
		if strings.ToUpper(s) == "Q" {
			return
		}
		if len(s) == 0 {
			continue
		}
		_, err = conn.Write([]byte(s))
		if err != nil {
			fmt.Printf("write to  server failed, err:%v\n", err)
			return
		}
		//从服务器接收消息
		var buf [1024]byte
		n, err := conn.Read(buf[:])
		if err != nil {
			fmt.Printf("\nread from  server failed, err:%v\n", err)
			return
		}
		fmt.Printf("\n收到服务端回复:%v\n", string(buf[:n]))
	}
}


接着看了fastway网关的源码,理解网关实现的逻辑,后面考虑实现一个适合rtp媒体包转发的级联的媒体服务器;部分源码来源网络,感谢!


呱牛笔记



请先登录后发表评论
  • 最新评论
  • 总共0条评论