写在前面
在前面的文章当中,我们学习了go当中的通道(channel)与协程(go-routine)及其应用。接下来,我们将学习go关于网络编程方面的内容。
Golang网络编程初识
在正式的跟大家讲解网络编程之前,需要提前跟大家说明一下:网络编程是一个很庞大的领域,本文当中只是通过一些简单的demo带大家初步了解golang在网络编程方面的应用。
为了大家能够更好地理解本文,大家需要先了解互联网协议的相关知识点。这包括OSI七层模型,了解各层的作用、协议等。如果大家学习过其他的高级语言,例如Java、Python等,了解socket编程的相关知识点,那这再好不过。
Golang socket编程
socket是什么?
socket是一种操作系统提供的进程间通信机制,中文翻译为“套接字”。它用于描述IP地址和端口,是一个通信链的句柄。
socket最初被翻译为把socket译为“介质(字)”。不久,ARPANET的socket就被翻译为“套接字”,其理由是:
由于每个主机系统都有各自命名进程的方法,而且常常是不兼容的,因此,要在全网范围内硬把进程名字统一起来是不现实的。所以,每个计算机网络中都要引入一种起介质作用的、全网一致的标准名字空间。这种标准名字,在ARPA网中称作套接字,而在很多其他计算机网中称作信口。更确切地说,进程之间的连接是通过套接字或信口构成的。
在操作系统当中,通常会为应用程序提供一组API,称之为套接字接口。应用程序可以通过套接字接口,向网络发出请求或者应答网络请求,进行数据交换。
一个套接字对(socket pairs)由本地的IP地址和端口、远程的IP地址和端口以及建立连接所使用的协议(protocol)组成。
对于socket的理解
socket位于应用层与TCP/IP协议族通信的中间,相当于设计模式当中的门面。对于程序员来说,不需要关系通信协议的具体实现,只需要调用socket提供的相关API,就可以进行网络数据交换。
Golang的TCP client-server Demo
服务端程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69
| package main
import ( "fmt" "go_code/web/tcp/constants" "net" "time" )
func main() {
listen, err := net.Listen(constants.Protocol, constants.Addr)
if err != nil { println("监听端口出错", err) return
} else { fmt.Println("服务器启动完成 " + time.Now().Format("2006-01-02 15:04:05")) }
for {
accept, err := listen.Accept() if err != nil { println("出现错误:\n", err.Error()) return
}
go doServerStuff(accept)
}
}
func doServerStuff(conn net.Conn) {
defer func() { println("关闭连接") conn.Close() }()
for { buf := make([]byte, 256)
read, err := conn.Read(buf)
if err != nil { if "EOF" == err.Error() { println("数据读取结束") return } println("出现错误:", err.Error()) return
}
println("接收到数据:") println(string(buf[:read])) _, err = conn.Write([]byte(" hello client " + conn.RemoteAddr().String()))
if err != nil { println("出现错误\n", err.Error()) }
}
}
|
在服务端程序当中,服务启动后,使用for无限循环接收客户端的请求并启动协程处理来自客户端的请求。
客户端程序:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| package main
import ( "fmt" "go_code/web/tcp/constants" "net" "time" )
func main() {
conn, err := net.Dial(constants.Protocol, constants.Addr)
defer conn.Close()
if err != nil { println("建立连接失败", err.Error()) return
} else { fmt.Println("建立连接成功") } conn.Write([]byte("hello server " + time.Now().Format("2006-01-02 15:04:05")))
buf := make([]byte, 1024) read, err := conn.Read(buf)
println("收到服务器发送来的消息")
if err != nil { println("读取数据失败 ", err.Error()) return }
fmt.Println(string(buf[:read]))
}
|
客户端启动后,与服务器建立连接,并向服务器发送数据。
服务器程序输出:
1 2 3 4 5
| 服务器启动完成 2022-01-16 18:37:53 接收到数据: hello server 2022-01-16 18:38:01 数据读取结束 关闭连接
|
客户端程序输出:
1 2 3 4 5
| 建立连接成功 收到服务器发送来的消息 hello client 127.0.0.1:3471
进程完成,并显示退出代码 0
|
以上就是Go 关于tcp编程的一个简单的demo,其中主要应用到了net包和协程的相关知识点。
http 服务器demo
接下来,我们通过一个小demo来学习Go当中简单的http服务编程的相关知识点。
constants.go
1 2 3 4 5 6
| package constants
const ( ADDR = "localhost:9527" CONTEXT_PATH = "/" )
|
http_server.go
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66
| package main
import ( "go_code/web/http/constants" "net/http" "strings" )
var handlerMap = map[string]http.HandlerFunc{}
func main() {
handlerMap["/sayHello"] = SayHelloHandler handlerMap["/goodbye"] = SayGoodbyeHandler
http.HandleFunc(constants.CONTEXT_PATH, BaseHandler)
err := http.ListenAndServe(constants.ADDR, nil)
if err != nil { println("启动http服务器错误\n", err.Error()) }
}
func BaseHandler(w http.ResponseWriter, r *http.Request) {
uri := strings.Split(r.RequestURI, "?")[0]
println("请求URI ", uri)
handlerFunc, ok := handlerMap[uri] if ok { handlerFunc(w, r) } else {
w.WriteHeader(http.StatusNotFound) _, _ = w.Write([]byte("请求资源不存在"))
} }
func SayHelloHandler(w http.ResponseWriter, r *http.Request) {
name := r.URL.Query().Get("name")
if name != "" { _, _ = w.Write([]byte("hello ! " + name)) } else { w.WriteHeader(http.StatusBadRequest) _, _ = w.Write([]byte("缺少参数name")) } }
func SayGoodbyeHandler(w http.ResponseWriter, r *http.Request) {
name := r.FormValue("name") if name != "" { _, _ = w.Write([]byte("goodbye ! " + name)) } else { w.WriteHeader(http.StatusBadRequest) _, _ = w.Write([]byte("缺少参数name")) } }
|
在这个例子当中,http.ListenAndServe
函数主要用来监听并启动http服务,如果启动出现异常,则会返回相应的error。http.HandleFunc
函数用于注册对应请求路径的处理函数,在这个例子当中,我们对/
路径注册了对应的处理函数BaseHandler
,它实现于http包中的Handler
接口。在BaseHandler接口当中我们再对请求进一步的转发到具体的处理器函数。
而具体的处理函数也很简单,分别是一个sayHello和sayGoodbye的模拟业务处理的函数。当程序启动后,使用curl
进行测试
程序输出:
1 2 3 4 5 6 7 8
| curl http://localhost:9527/sayHello?name=韩立 hello ! 韩立 curl http://localhost:9527/sayHello 缺少参数name curl http://localhost:9527/sayHell 请求资源不存在 curl http://localhost:9527/goodbye?name=陈师姐 goodbye ! 陈师姐
|
写在最后
在这篇文章当中,我们主要通过两个例子初步地认识了go语言网络编程的相关知识点。本文当中涉及到的例子可以点击此处下载。如果我的学习笔记能够给你带来帮助,还请多多点赞鼓励。文章如有错漏之处还请各位小伙伴帮忙斧正。