go【第十篇】网络编程
TCP/UDP协议
由于tcp/udp协议过于底层基本不会使用,这里不再过多赘述,了解基本即可
网络分层架构
为了减少协议设计的复杂性,大多数网络模型均采用分层的方式来组织。每一层都有自己的功能,就像建筑物一样,每一层都靠下一层支持。每一层利用下一层提供的服务来为上一层提供服务,本层服务的实现细节对上层屏蔽。越下面的层,越靠近硬件;越上面的层,越靠近用户。
链路层
以太网规定,连入网络的所有设备,都必须具有“网卡”接口。数据包必须是从一块网卡,传送到另一块网卡。通过网卡能够使不同的计算机之间连接,从而完成数据通信等功能。网卡的地址——MAC 地址,就是数据包的物理发送地址和物理接收地址。
网络层
网络层的作用是引进一套新的地址,使得我们能够区分不同的计算机是否属于同一个子网络。这套地址就叫做“网络地址”,这是我们平时所说的IP地址。这个IP地址好比我们的手机号码,通过手机号码可以得到用户所在的归属地。网络地址帮助我们确定计算机所在的子网络,MAC 地址则将数据包送到该子网络中的目标网卡。因此,从逻辑上可以推断,必定是先处理网络地址,然后再处理 MAC 地址。
传输层
端口是每一个使用网卡的程序的编号。每个数据包都发到主机的特定端口,所以不同的程序就能取到自己所需要的数据
应用层
应用程序收到“传输层”的数据,接下来就要进行解读。由于互联网是开放架构,数据来源五花八门,必须事先规定好格式,否则根本无法解读。“应用层”的作用,就是规定应用程序的数据格式。
什么是Socket
Socket起源于Unix,而Unix基本哲学之一就是“一切皆文件”,都可以用“打开open –> 读写write/read –> 关闭close”模式来操作。Socket就是该模式的一个实现,网络的Socket数据传输是一种特殊的I/O,Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用:Socket(),该函数返回一个整型的Socket描述符,随后的连接建立、数据传输等操作都是通过该Socket实现的。
常用的Socket类型有两种:流式Socket(SOCK_STREAM)和数据报式Socket(SOCK_DGRAM)。流式是一种面向连接的Socket,针对于面向连接的TCP服务应用;数据报式Socket是一种无连接的Socket,对应于无连接的UDP服务应用
socket工作流程
一次通话
package main import ( "fmt" "net" ) func main() { //监听 listener, err := net.Listen("tcp", "127.0.0.1:8000") if err != nil { fmt.Println("err = ", err) return } defer listener.Close() //阻塞等待用户链接 conn, err := listener.Accept() if err != nil { fmt.Println("err = ", err) return } //接收用户的请求 buf := make([]byte, 1024) //1024大小的缓冲区 n, err1 := conn.Read(buf) if err1 != nil { fmt.Println("err1 = ", err1) return } fmt.Println("buf = ", string(buf[:n])) defer conn.Close() //关闭当前用户链接 }
eg1
client:
server: buf = hi go
eg2
package main import ( "fmt" "net" ) func main() { //主动连接服务器 conn, err := net.Dial("tcp", "127.0.0.1:8000") if err != nil { fmt.Println("err = ", err) return } defer conn.Close() //发送数据 conn.Write([]byte("are u ok?")) }
server: buf = are u ok?
高并发socket
package main import ( "fmt" "net" "strings" ) //处理用户请求 func HandleConn(conn net.Conn) { //函数调用完毕,自动关闭conn defer conn.Close() //获取客户端的网络地址信息 addr := conn.RemoteAddr().String() fmt.Println(addr, " conncet sucessful") buf := make([]byte, 2048) for { //读取用户数据 n, err := conn.Read(buf) if err != nil { fmt.Println("err = ", err) return } fmt.Printf("[%s]: %s\n", addr, string(buf[:n])) fmt.Println("len = ", len(string(buf[:n]))) //if "exit" == string(buf[:n-1]) { //nc测试 if "exit" == string(buf[:n-2]) { //自己写的客户端测试, 发送时,多了2个字符, "\r\n" fmt.Println(addr, " exit") return } //把数据转换为大写,再给用户发送 conn.Write([]byte(strings.ToUpper(string(buf[:n])))) } } func main() { //监听 listener, err := net.Listen("tcp", "127.0.0.1:8000") if err != nil { fmt.Println("err = ", err) return } defer listener.Close() //接收多个用户 for { conn, err := listener.Accept() if err != nil { fmt.Println("err = ", err) return } //处理用户请求, 新建一个协程 go HandleConn(conn) } }
HTTP协议
超文本传输协议(HTTP,HyperText Transfer Protocol)是互联网上应用最为广泛的一种网络传输协议,所有的WWW文件都必须遵守这个标准。设计HTTP最初的目的是为了提供一种发布和接收HTML页面的方法。
net/http
它提供了HTTP客户端和服务端的实现,这是其它语言没有做的。其它语言一般只提供了HTTP客户端的实现。
如果要深入了解这个包对于web服务器的实现,可以去看《Go Web编程这本书》。
net/http实现了http消息的封装,web框架的基本功能(cookie/session,模板,数据库支持,路由系统),尽管如此,是一个很基础的很简陋的异步socket框架,封装的不是很高层,使用起来仍然有点复杂,因此,很多框架(gin beego等)基于net/http进行了更高级的封装(如orm的支持、更高级的路由系统等)。
以下就是介绍net/http包的关于web的简单使用,目的仅仅是让大家对net/http有更深入更感性的认识,因为这个包过于简陋做WEB开发还是不行的。
HTTP服务端
使用Go语言中的net/http包来编写一个简单的接收HTTP请求的Server端示例,net/http包是对net包的进一步封装,类似于python中的wsgiref专门用来处理HTTP协议的数据。具体的代码如下:
package main import ( "fmt" "net/http" ) // http server func sayHello(w http.ResponseWriter, r *http.Request) { dataStr:="Welcome Go Server" w.Write([]byte(dataStr)) } func main() { http.HandleFunc("/", sayHello) err := http.ListenAndServe(":9090", nil) if err != nil { fmt.Printf("http server failed, err:%v\n", err) return } }
package main import ( "fmt" "net/http" ) // http server func sayHello(w http.ResponseWriter, r *http.Request) { dataStr := "<h2>Welcome Go Server</h2>" w.Write([]byte(dataStr)) } func main() { http.HandleFunc("/", sayHello) err := http.ListenAndServe(":9090", nil) if err != nil { fmt.Printf("http server failed, err:%v\n", err) return } }
前端支持
经测试,net/http库默认支持html/css/js
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>HTML基础</title> </head> <body> <h2 style="color: red">Welcome Go Server</h2> </body> <script> function f(){ console.log("hello") } f() </script> </html>
package main import ( "fmt" "io/ioutil" "net/http" ) // HTTP server端 func sayHello(w http.ResponseWriter, r *http.Request) { // 从hello.html文件中读取数据写入到w中 data, err := ioutil.ReadFile("./hello.html") if err != nil { fmt.Println("read from file failed, err:", err) return } w.Write(data) } func main() { http.HandleFunc("/", sayHello) // 注册一个处理 / 的函数 // 启动服务 err := http.ListenAndServe("127.0.0.1:9090", nil) if err != nil { panic(err) } }
表单处理
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>HTML基础</title> </head> <body> <h2 style="color: red">Welcome Go Server</h2> </body> <script> function f(){ console.log("hello") } f() </script> </html>
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>获取用户输入</title> </head> <body> <h1>欢迎注册</h1> <hr> <form action="http://127.0.0.1:9090/index" method="POST"> <div> <label for="i1">用户名:</label> <input id="i1" name="username" type="text" placeholder="请输入用户名"> </div> <div> <label>密 码: <input name="pwd" type="password"> </label> </div> <div> <label>性别:</label> <input name="gender" value="male" type="radio">男 <input name="gender" value="female" type="radio">女 </div> <div><label>头像:</label> <input type="file" name="avatar" accept="image/*"> </div> <div> <label for="s1" >地址</label> <select name="addr" id="s1"> <option value="">---</option> <option value="bj">北京</option> <option value="sh">上海</option> <option value="gz" selected>广州</option> <option value="sz">深圳</option> </select> </div> <div> <input type="submit" value="提交"> <button type="submit">提交</button> <input type="reset" value="重置"> <input type="button" value="普通按钮"> </div> </form> </body> </html>
package main
import (
"fmt"
"io/ioutil"
"net/http"
)
// HTTP server端
func sayHello(w http.ResponseWriter, r *http.Request) {
fmt.Println("请求的url, 方法", r.Method, r.URL)
// 从hello.html文件中读取数据写入到w中
data, err := ioutil.ReadFile("./hello.html")
if err != nil {
fmt.Println("read from file failed, err:", err)
return
}
w.Write(data)
}
func search(w http.ResponseWriter, r *http.Request) {
fmt.Println("请求的url, 方法", r.Method, r.URL)
data, err := ioutil.ReadFile("./form.html")
if err != nil {
fmt.Println("read html file failed, err:", err)
return
}
w.Write(data)
}
func index(w http.ResponseWriter, r *http.Request) {
// r:代表跟请求相关的所有内容
// 获取注册的信息
// 获取请求的方法
fmt.Println("请求的url, 方法", r.Method, r.URL)
r.ParseForm() // 解析
// 获取表单中的数据
fmt.Printf("%#v\n", r.Form)
usernameValue := r.Form.Get("username")
pwdValue := r.Form.Get("pwd")
fmt.Println(usernameValue, pwdValue)
// 校验用户输入信息的有效性
w.Write([]byte("index"))
}
func main() {
http.HandleFunc("/", sayHello) // 注册一个处理 / 的函数
http.HandleFunc("/web", search)
http.HandleFunc("/index", index)
http.ListenAndServe("127.0.0.1:9090", nil)
}
127.0.0.1:9090 127.0.0.1:9090/index 127.0.0.1:9090/web
服务器打印信息
Go模板
文档
不使用模板(字符串替换)
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>HTML基础</title> </head> <body> <h2 style="color: red">Welcome Go Server</h2> {ooxx} </body> <script> function f(){ console.log("hello") } f() </script> </html>
package main import ( "fmt" "io/ioutil" "math/rand" "net/http" "strings" ) // template 示例 func info(w http.ResponseWriter, r *http.Request) { data, err := ioutil.ReadFile("./hello.html") if err != nil { fmt.Println("read file failed, err:", err) return } num := rand.Intn(10) dataStr := string(data) // 转换成字符串 if num > 5 { dataStr = strings.Replace(dataStr, "{ooxx}", "<li>服务器开发</li>", 1) } else { dataStr = strings.Replace(dataStr, "{ooxx}", "<li>分布式开发</li>", 1) } w.Write([]byte(dataStr)) } func main() { http.HandleFunc("/info", info) http.ListenAndServe("127.0.0.1:8080", nil) }
使用模板(本质也是字符串替换)
<!DOCTYPE html> <html lang="zh-CN"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>HTML基础</title> </head> <body> <h2 style="color: red">Welcome Go Server</h2> <p>Hello {{.}}</p> </body> <script> function f(){ console.log("hello") } f() </script> </html>
package main
import (
"fmt"
"html/template"
"math/rand"
"net/http"
)
// template 示例
func info(w http.ResponseWriter, r *http.Request) {
tpl, err := template.ParseFiles("./hello.html")
if err != nil {
fmt.Println("read file failed, err:", err)
return
}
var dataStr string
num := rand.Intn(10)
if num > 5 {
dataStr = "服务器开发"
} else {
dataStr = "分布式开发"
}
// 用数据去渲染模板
fmt.Println(dataStr)
tpl.Execute(w, dataStr)
}
func main() {
http.HandleFunc("/info", info)
http.ListenAndServe("127.0.0.1:8080", nil)
}
HTTP客户端
// http_client/main.go package main import ( "fmt" "io/ioutil" "net/http" "reflect" ) func main() { resp, err := http.Get("http://localhost:9090/") if err != nil { fmt.Println("get failed, err:", err) return } defer resp.Body.Close() body, err := ioutil.ReadAll(resp.Body) fmt.Println(body) }