先展示一下简单的go程序,然后分析GO的API和Linux API的关系。像简单的socket概念等就不在这里介绍了,不懂的去百度一下。
server.go
package main
import "net"
import "fmt"
import "bufio"
import "strings" // only needed below for sample processing
func main() {
fmt.Println("Launching server...")
// 监听端口8081
ln, _ := net.Listen("tcp", ":8081")
// 接入
conn, _ := ln.Accept()
for {
message, _ := bufio.NewReader(conn).ReadString('\n')
fmt.Print("Message Received:", string(message))
//处理一下收到的数据
newmessage := strings.ToUpper(message)
//写回到客户端
conn.Write([]byte(newmessage + "\n"))
}
}
client.c
package main
import "net"
import "fmt"
import "bufio"
import "os"
func main() {
// 连接到服务器
conn, _ := net.Dial("tcp", "127.0.0.1:8081")
for {
// 从标准的输入流中读取数据
reader := bufio.NewReader(os.Stdin)
fmt.Print("Text to send: ")
text, _ := reader.ReadString('\n')
// 发送给socket
fmt.Fprintf(conn, text + "\n")
// 监听服务端写回来的数据
message, _ := bufio.NewReader(conn).ReadString('\n')
if message == ""{
fmt.Print("message from client is nil")
break
}
fmt.Print("Message from server: "+message)
}
}
使用go语言实现了一个简单的客户服务器之间的交流。左边是客户端,右边是服务端。
Linux Socket API和go API之间的联系
go语言写网络程序非常的简单,和C语言相比省略了许多的步骤,C语言从创建套接字-->填充相关的信息,再使用函数bind()绑定套接字--->再listen监听端口--->最后是使用accept接收连接。go语言省略了前面两个步骤,直接使用Listen()绑定了端口并且监听端口,再使用Accept()接收连接,那么go的Listen、Accept函数和C语言里面的bind、listen、accept有什么联系呢。
Listen
不说那么多废话,show the picture!
图1 go Listen函数和Linux Socket API的关系
图片凑活的看,可以看到其实go语言的Listen函数底层是直接调用了Linux网络编程API的socket()创建一个套接字,并且将数据绑定到这个套接字上,最后在这个端口上监听,可谓是一举完成了三件事情,真的是非常的方便。
在阅读代码和查看资料的时候,发现go是将socket使用epoll来进行异步通知的。说到这里,其实逻辑很清晰,go语言通过一步到位的方式使用Listen将创建套接字、绑定、监听都完成了,最后还顺便将socket加入到epoll的监听队列中,从而完成了异步网络编程。
Accept
图2 go Accept函数和Linux Socket API的关系
相比较我们上面讨论的Listen来说,Accept就简单很多了。在连接建立后,还是会将这个得到socket加入到咱们的epoll队列中,从而有事件到来的时候,异步的通知我们处理此socket的线程。
总结
其实总的来说,不管是JAVA、PHP、还是我们讨论的GO语言,调用来调用去,封装来封装去,到最后还是调用操作系统提供的系统调用来完成我们的工作,而系统调用又需要调用网络驱动程序来完成对网卡或者其他硬件的读和写。套用一句很有名气的话,"计算机世界的所有问题,都可以通过向上一层进行抽象封装来解决"。如果有,那就两层解决。回想我们编程,也正是如此。我们一开始使用面向过程的编程思想,写函数,然后出现了高阶函数,又出现了面向对象的思想,这何尝不是一种我们应用程序员进行的封装呢?