网站做视频,手机网站建设,wordpress没有权限建立目录权限,建设部门户网站条例免费下载golang网络编程day3
golang TCP聊天室golang UDP聊天室URL组成golang URL编程golang http编程http请求方法golanghttp框架 golang TCP聊天室
看懂例子把它理解好#xff0c;知道实现的原理是什么#xff0c;还可以自己实现出来。 忘了就看day2的讲解#xff0c;里面的函数…golang网络编程day3
golang TCP聊天室golang UDP聊天室URL组成golang URL编程golang http编程http请求方法golanghttp框架 golang TCP聊天室
看懂例子把它理解好知道实现的原理是什么还可以自己实现出来。 忘了就看day2的讲解里面的函数都懂这个并不难。 首先要知道tcp聊天室的实现理论客户端采用简单的输入输出方式进行消息的发送和接收服务端通过广播的方式将信息发送给所有的客户端。
服务端例子
package mainimport (fmtnet
)var (clients make(map[net.Addr]net.Conn)addCh make(chan net.Conn)delCh make(chan net.Addr)messageCh make(chan []byte)listenAddr localhost:8080
)func main() {fmt.Println(Server started on, listenAddr)listener, err : net.Listen(tcp, listenAddr)if err ! nil {fmt.Println(err)return}defer listener.Close()go broadcaster()for {conn, err : listener.Accept()if err ! nil {fmt.Println(err)continue}addCh - conngo handleConn(conn)}
}func broadcaster() {for {select {case conn : -addCh:clients[conn.RemoteAddr()] connfmt.Println(New client:, conn.RemoteAddr())case addr : -delCh:delete(clients, addr)fmt.Println(Client disconnected:, addr)case msg : -messageCh:for _, conn : range clients {_, err : conn.Write(msg)if err ! nil {fmt.Println(err)}}}}
}func handleConn(conn net.Conn) {defer func() {delCh - conn.RemoteAddr()conn.Close()}()for {msg : make([]byte, 4096)n, err : conn.Read(msg)if err ! nil {return}messageCh - msg[:n]}
}
常量解读 clients这里客户登记使用了键值对的形式创建了一个map来登记连接key是客户端的地址value是与该地址建立的连接。 addCh :net.Conn类型的通道这个是用于实现客户端连接建立。 delCh:net.Addr类型的通道用于实现客户端连接的删除。 messageCH 字节切片类型的通道放要传输的数据。 listenAddr:监听的地址和端口号。
可能有这样的一个疑问这里为什么要用通道用通道有个好处在并发的环境下不会产生冲突保证了并发安全。因为是模拟聊天室那就要考虑一时间有好多个用户接入的情况我们在操作map的时候就必须保证并发安全这里保证map的并发安全就使用了通道来进行实现。
main主函数逻辑 服务器端先调用了net.Listener,先创建一个监听器然后启动广播协程这个协程主要是用于监听每一个通道的等下再说这个函数的细节。然后服务器有个无限循环因为启动监听之后就要开始监听客户端发来的请求了这里一般都是无限循环然后调用listener.Accept()来接收客户端的请求一旦请求接收成功就建立了Conn可以进行数据的收发。既然有连接建立那就要进行连接的登记也就是for里面有个处理addCh - conn这就是连接的登记把连接送入addCh通道里面。然后启动一个处理函数协程handleConn对连接进行处理。
func broadcaster()函数逻辑 首先要明白select的性质select是go并发编程里面的内容与通道相关每个case都是一个通道操作当有case的通道操作能够执行那就会执行通道语句当有多个通道case有效会随机选择一个进行执行这里是for无限循环那就会处理所有的通道操作。当没有通道操作的时候这个select语句就会阻塞。 所以说这个函数就是来处理所有的通道操作的。来看里面每一个通道的处理
case conn : -addCh:clients[conn.RemoteAddr()] conn这个是进行登记处理net.Conn类型里面的这个函数是登记远端的IP地址对于服务器来说那就是用户的IP地址。conn就是这个连接注意这里我前面说过了是采用map来登记。
case addr : -delCh:delete(clients, addr)这个就是连接断开然后进行登记的删除这个delete也是调用了map里面的一个api删除指定的key也就是addr。
case msg : -messageCh:for _, conn : range clients {_, err : conn.Write(msg)if err ! nil {fmt.Println(err)}}这个通道处理是进行消息广播的这里for range clients就是拿到每一个连接然后把msg发送到这个连接去。可以看到拿到连接后调用了conn.Write(msg)把消息写入每个用户的连接中达到数据广播的效果。
func handleConn(conn net.Conn)的逻辑这个函数是用来进行拿到连接后的处理的。因为同一时间要进行操作用户很多所以要启动协程来进行处理。
func handleConn(conn net.Conn) {defer func() {delCh - conn.RemoteAddr()conn.Close()}()for {msg : make([]byte, 4096)n, err : conn.Read(msg)if err ! nil {return}messageCh - msg[:n]}
}defer这里是进行了模拟用户操作完后的连接关闭所以这里就要给delCH通道发个信息进行登记的删除然后关闭这个conn。 for循环是进行了无限循环从连接里面读取数据因为msg是广播的。所以每个用户都要随时接收连接里面的数据。 这里接收完数据之后也会做一个messageCh - msg[:n]也是再次进行消息的发送这个消息会广播出去。
总结把握好tcp聊天室的理论原理就是用户输入数据后服务器负责将消息广播给每个用户。这样就能很好的理解每个操作是为了什么。 实现原理main函数负责启动开启服务器监听然后启动通道处理启动用户请求处理。通道处理复杂所有通道的功能启动用户请求处理是处理每个用户。 golang UDP聊天室
package mainimport (fmtnetosstrings
)const (serverAddr localhost:8080
)func main() {// 解析UDP地址addr, err : net.ResolveUDPAddr(udp, serverAddr)if err ! nil {fmt.Println(err)return}// 建立UDP连接conn, err : net.ListenUDP(udp, addr)if err ! nil {fmt.Println(err)return}defer conn.Close()fmt.Println(Server started on, serverAddr)// 用于保存客户端的地址clients : make(map[string]*net.UDPAddr)for {// 接收请求buf : make([]byte, 1024)n, clientAddr, err : conn.ReadFromUDP(buf)if err ! nil {fmt.Println(err)continue}// 解析客户端请求msg : strings.TrimSpace(string(buf[:n]))if msg {continue}fmt.Printf(Received %d bytes from %s: %s\n, n, clientAddr, msg)// 如果是新客户端添加到客户端列表中if _, ok : clients[clientAddr.String()]; !ok {fmt.Println(New client:, clientAddr)clients[clientAddr.String()] clientAddr}// 广播消息给其他客户端for _, addr : range clients {if addr.String() clientAddr.String() {continue // 不发送给发送者}_, err : conn.WriteToUDP([]byte(msg), addr)if err ! nil {fmt.Println(err)}fmt.Printf(Sent %d bytes to %s: %s\n, len(msg), addr, msg)}}
}
解读 1.UDP和tcp有点区别这个解析地址的步骤要单独写出来。解析本地地址端口号8080调用ResolveUDPAddr()之后会得到解析后的UDP地址UDPAddr然后进行调用ListenUDP()监听客户端的UDP请求返回操作数据的连接。 2.这里还是用map来进行保存客户端的地址。 我学这里的时候我会去对比tcp建立聊天室的实现我发现udp的监听这里不是无限循环监听这里其实是我理解错了因为udp是无连接的所以这里我是这么来方便理解的相当于这里调了监听函数就是服务器UDP开了个口子然后客户端根本不用建立连接只管往这个UDPConn里面发数据就完事了。 3.所以说真正的无限循环是在直接对数据进行处理这里。 下面这个无限循环for直接调用这个conn的读数据从里面读udp客户端的请求,n, clientAddr, err : conn.ReadFromUDP(buf)调用这个函数把数据读到buf中并返回远端的IP地址。 把数据清理后赋给msg并输出。 4.下面访问键值对看这个地址原来在不在不在就是新连接进行登记。 法消息的时候遍历这个map这样调用func (c *UDPConn) WriteToUDP(b []byte, addr *UDPAddr) (int, error)可以把数据广播给登记的IP地址。这就完成了广播功能。
客户端
package mainimport (bufiofmtnetostime
)const (serverAddr localhost:8080
)func main() {// 解析服务器地址raddr, err : net.ResolveUDPAddr(udp, serverAddr)if err ! nil {fmt.Println(err)return}// 建立与服务器的连接conn, err : net.DialUDP(udp, nil, raddr)if err ! nil {fmt.Println(err)return}defer conn.Close()// 从标准输入读取数据reader : bufio.NewReader(os.Stdin)fmt.Print(Enter message: )message, _ : reader.ReadString(\n)// 向服务器发送数据_, err conn.Write([]byte(message))if err ! nil {fmt.Println(err)return}// 设置读取超时err conn.SetReadDeadline(time.Now().Add(15 * time.Second))if err ! nil {fmt.Println(err)return}// 从服务器接收数据buf : make([]byte, 1024)n, _, err : conn.ReadFromUDP(buf)if err ! nil {fmt.Println(err)return}fmt.Printf(Received: %s\n, string(buf[:n]))
}
分析 客户端也是先解析服务器ip地址 然后调用net.DialUDP(“udp”, nil, raddr)这样就拿到了conn相当于建立了连接。这里注意这并不是真正意义上的连接只是我这样形容。 拿到这个conn用户就可以往服务器发数据了我要发的数据全装在这个reader里面然后调用reader的Readstring方法把内容给message。 然后调用conn的写把mes传进去。这样就完成了用户发送数据。 下面还设置了一个读取超时。15秒服务器没读就会发生错误。
从服务器接收数据用到了ReadFromUDP读到的数据都存在了buf里面。 基本逻辑就是这样。 URL组成
专业说法什么是URL统一资源定位符是一种用于定位互联网上资源的地址它是互联网上标准资源的地址可以用来访问网页、图片、视频或其他类型的数据。 通俗理解你在网上点击的每个链接都是URL因为你点链接的过程实际上就是请求资源的过程。这个URL就是定义好的访问资源的通用方法。
一个URL通常包含以下几个部分 1.协议(scheme)定义了访问资源的方法比如http/https/ftp等 2.域名或IP地址(host)制定了托管资源的服务器位置 3.端口号(可选)(port)用于访问服务器上的特定服务比如HTTP协议端口号是80HTTPS是443。 4.路径(path)指定服务器上资源的具体位置 5.查询字符串(可选)(query):以键值对的形式提供额外参数通常用于提交表单数据或指定资源的某种特定视图。 6.片段标识符(fragment)用于指向网页内的特定部分。
举个例子这个例子会分析就相当于学会啥是URL了。
https://www.example.com:443/path/to/myfile.html?key1value1key2value2#Section2
分析 协议https 域名www.example.com 端口号443 路径/path/to/myfile.html 查询字符串key1value1key2value2 注意这里是键值对形式 片段标识符Section2
//:是协议分隔符用于分割协议 ?是查询字符串的开始后面跟着一系列键值对每对键值对用隔开。 #是片段标识符的开始它通常用于指向网页中的特定部分。浏览器会滚动到页面中ID为该标识符的元素处。注意片段标识符不会被发送到服务器它只客户端(i浏览器上使用。
Golang URL编程
go的标准库net/url包提供了URL编程的实现该包提供了一些功能使得我们可以解析、构建和操作URL字符串注意是字符串我们平时点的就是字符串。
解析URL
主要用到了net/url包中的Parse函数该函数可以将URL字符串解析为URL结构体该结构体包含了URL的各个组成部分.
package mainimport (fmtnet/url
)func main() {// 解析URLu, err : url.Parse(https://www.example.com/search?qgolang#top)if err ! nil {fmt.Println(err)return}// 输出URL的各个部分fmt.Println(Scheme:, u.Scheme)fmt.Println(Host:, u.Host)fmt.Println(Path:, u.Path)fmt.Println(Query:, u.Query())fmt.Println(Fragment:, u.Fragment)
}
输出的内容
Scheme: https
Host: www.example.com
Path: /search
Query: map[q:[golang]]
Fragment: top
u就是结构体下面这些都是它里面的属性和方法
总结url.Parse(url string) 返回值是URL结构体和一个可能发送的错误。 然后就可以操作这个结构体。
构建URL
就是用这个URL结构体转回字符串相当于上面逆过来。
package mainimport (fmtnet/url
)func main() {// 构建URLu : url.URL{Scheme: https,Host: www.example.com,Path: /search,}q : u.Query()q.Set(q, golang)u.RawQuery q.Encode()// 输出URL字符串fmt.Println(u.String())
}
解读 u.Query() 用于返回一个url.Values类型代表URL的查询字符串这个查询字符串就是上面介绍部分的那个。url.Values是什么类型map[string][]string,key是字符串value是字符串切片这意味着每个键可以对于多个值这是因为在URL的查询字符串中同一个键可以有多个值例如 ?keyvalue1keyvalue2
q.Set(“q”, “golang”) 这个事Values类型的一个方法用于向查询字符串中添加一个键值对qgolang
u.RawQuery q.Encode() 使用Encode方法将修改后的查询参数编码为字符串并将其赋值给u的RawQuery字段。这样URL的查询部分就被设置为qgolang
代码运行输出结果
https://www.example.com/search?qgolang
解析查询参数
要解析查询参数可以使用net/url包中的Values类型。将查询字符串作为传输传递给Values函数然后通过Get方法来获取特定参数的值。
package mainimport (fmtnet/url
)func main() {// 解析查询参数values, err : url.ParseQuery(qgolangsortrecentlimit10)if err ! nil {fmt.Println(err)return}// 获取特定参数的值q : values.Get(q)sort : values.Get(sort)limit : values.Get(limit)fmt.Println(q:, q)fmt.Println(sort:, sort)fmt.Println(limit:, limit)
}
解读 values, err : url.ParseQuery(“qgolangsortrecentlimit10”) 用于解读一个URL编码格式的查询字符串。这个字符串包含三个键值对解析的结果是values类型的存储在遍历values中。
values.Get(“q”) 用于获取特定参数的值里面的参数是key返回值是value
代码运行输出
q: golang
sort: recent
limit: 10
编码和解码
要对URL字符串进行编码和解码就要用net/url包下面的QueryEscape和QueryUnescape函数分别实现对URL字符串进行编码和解码。
package mainimport (fmtnet/url
)func main() {// 编码字符串encoded : url.QueryEscape(https://www.example.com/search?qgolangsortrecent)fmt.Println(Encoded:, encoded)// 解码字符串decoded, err : url.QueryUnescape(encoded)if err ! nil {fmt.Println(err)return}fmt.Println(Decoded:, decoded)
}
解读 encoded : url.QueryEscape(“https://www.example.com/search?qgolangsortrecent”) 用于编码字符串对给定的字符串进行百分比编码这个对于编码URL的查询字符串部分很有用因为对处理URL特殊字符( ?)时会把这些特殊字符转换为百分比形式。 这里的输出结果
Encoded: https%3A//www.example.com/search%3Fq%3Dgolang%26sort%3Drecent
decoded, err : url.QueryUnescape(encoded) 解码就是进行还原
输出 Decoded: https://www.example.com/search?qgolangsortrecent
一个疑问为什么要换成百分比 将URL中的特定字符串替换为百分比这种被定义为URL编码。 这种编码的好处 1.保留字符URL中某些字符有特殊含义百分比编码可以在不改变原有意义的情况下安全地包含这些字符。 2.非ASCII字符对于某些在ASCII中有特殊含义的字符必须使用百分比编码来标识 3.安全性百分比编码有助于消除URL中可能引起安全问题的字符。 4.一致性和标准性通过百分比编码可以确保URL的一致性和标准化使得不同的网络设备和软件能够正确解析URL。 golang http编程
这种编程是通过net/http包实现的。 这个标注库提供了完善的HTTP客户端和服务器实现。所以可以在go语言实现编写HTTP相关的应用。 直接来看demo这些都是这个包的应用
HTTP客户端 一个简单的使用net/http包发送 HTTP GET请求的例子
package mainimport (fmtio/ioutilnet/http
)func main() {resp, err : http.Get(http://www.example.com/)if err ! nil {fmt.Println(err)return}defer resp.Body.Close()body, err : ioutil.ReadAll(resp.Body)if err ! nil {fmt.Println(err)return}fmt.Println(string(body))
}
解读 resp, err : http.Get(“http://www.example.com/”) 调用http下的这个函数可以向这个指定的网站发送一个GET请求这个函数非常常用于从指定的URL获取数据。 参数就是一个url就是要请求的url字符串类型的。 resp是*http.Response类型的指针它标识服务器的响应。err是请求过程中可能发生的错误。
当这个函数调用成功它会向这个URL发送一个GET请求如果成功resp将是服务器的响应。这个响应前面学过理论基础就是服务器那边发来的信息。包含状态码、响应头以及响应体等信息。如果请求失败err会描述发生的错误。
*http.Response类型解读这是一个结构体下面注意介绍里面的字段。 Status 类型string 描述响应的状态行例如 “200 OK”。这个字段包含了状态码和状态描述。
StatusCode 类型int 描述数字形式的 HTTP 状态码例如 200、404 等。
Header 类型http.Header实质上是 map[string][]string 描述响应头。这是一个映射表包含了所有的响应头字段和值。每个头字段可以有一个或多个值。
Body 类型io.ReadCloser 描述响应的主体。这是一个 io.Reader 接口用于读取响应的主体内容。它还实现了 io.Closer 接口这意味着在读取完毕后你需要调用 Body.Close() 来关闭它释放资源。
ContentLength 类型int64 描述响应主体的长度。如果长度未知该值为 -1。
TransferEncoding 类型[]string 描述传输编码列表按照应用的顺序。
Close 类型bool 描述指示是否应在读取完响应主体后关闭连接。
Request 类型*http.Request 描述生成这个响应的 HTTP 请求。这对于跟踪请求-响应链很有用。
TLS 类型*tls.ConnectionState 描述如果通过 HTTPS 访问则包含有关 TLS 连接的信息。如果不是 HTTPS则为 nil。
通过上述描述这个resp完全就是服务器的响应的全部内容我们可以按照需要选择字段进行操作。这里我们一般都去关注body这个字段响应的主体就是我们要的主要内容。 关于这个body的数据类型做一点说明
type ReadCloser interface {ReaderCloser
}
它是一个接口类型它又嵌套了两个接口一个接口用于从数据流中读数据这个接口包含了个read方法可以实现读取数据到指定字节切片中另一个用于关闭数据流它又一个close()方法调用后关闭数据流。
这里有个注意就是处理完响应主题后应该关闭resp.Body,这样做的原因和前面学的那些关闭几乎都一个原因避免资源泄露。
body, err : ioutil.ReadAll(resp.Body) 用于读取响应体的数据传的参数只要求实现了io.Reader接口就可以传这样刚好符合。然后返回一个字节切片这样就做到了把响应体里面的内容读出来。
综上使用net/http包轻松的做到了向指定的URL发送http get请求并且得到响应后输出响应的内容。
一个更进阶的用法使用http.Client可以实现更复杂的HTTP客户端逻辑例如设置请求头、发送POST请求等。
看例子还是客户端
package mainimport (bytesfmtio/ioutilnet/http
)func main() {url : http://www.example.com/logindata : []byte({username: admin, password: password})req, err : http.NewRequest(POST, url, bytes.NewBuffer(data))if err ! nil {fmt.Println(err)return}req.Header.Set(Content-Type, application/json)client : http.Client{}resp, err : client.Do(req)if err ! nil {fmt.Println(err)return}defer resp.Body.Close()body, err : ioutil.ReadAll(resp.Body)if err ! nil {fmt.Println(err)return}fmt.Println(string(body))
}
解读 url : “http://www.example.com/login” data : []byte({username: admin, password: password}) url指向登录接口下面这个json格式标识的用户名和密码。
req, err : http.NewRequest(“POST”, url, bytes.NewBuffer(data)) 这个函数用于创建一个新的HTTP请求。这个函数的作用是构造一个指定HTTP方法、URL和可选正文的HTTP请求。
结构
func NewRequest(method, url string, body io.Reader) (*http.Request, error)
参数方法指定urlbody是请求的正文如果请求不需要正文可以为nil。正文是在HTTP请求和响应中传输的主要数据部分就是头部的下面那部分即实际要收发的数据内容。这个正文通常也是有格式的文本(纯文本jsonxml表单数据二进制数据文件 在上面这个代码中我请求的正文就是一个json格式的字节切片这里由于是要io.Reader所以这里用了一个bytes.NewBuffer(data)创建了一个io.Reader对象从而可以作为参数传入。
返回值*http.Request标识构造的HTTP请求可以说是请求的要素全部齐全了包含了方法URL头部正文等信息。
总结 这个函数起到了定制请求的效果满足了对灵活性的需求通过这个函数我可以构造几乎任何类型的HTTP请求。
req.Header.Set(“Content-Type”, “application/json”) 设置请求的请求正文的内容格式
client : http.Client{} resp, err : client.Do(req) 这里是创建一个实例来发送求情使用client结构体的Do()方法来发送这个创建的请求并接收响应。这个resp就是接收的响应。
最后要关闭响应体。defer resp.Body.Close()
拿到的这个响应体可以用 body, err : ioutil.ReadAll(resp.Body) fmt.Println(string(body)) 打印出来因为resp.Body是io.Reader类型的。
http服务器
下面是一个简单的服务器锂离子
package mainimport (fmtnet/http
)func main() {http.HandleFunc(/, func(w http.ResponseWriter, r *http.Request) {fmt.Fprintf(w, Hello, World!)})http.ListenAndServe(:8080, nil)
}
解读 *http.HandleFunc(“/”, func(w http.ResponseWriter, r http.Request) { fmt.Fprintf(w, “Hello, World!”) })
注册处理函数这个函数用于注册一个处理函数该函数用于处理对特定路径的HTTP请求。 这个代码中这个函数被用来处理所有发往根路径/的请求。
func HandleFunc(pattern string, handler func(ResponseWriter, *Request))
pattern字符串类型代表指定要处理的URL路径handler一个函数当接收到匹配这个路径的请求时这个函数被调用。
*func(w http.ResponseWriter, r http.Request) 匿名函数w http.ResponseWriter用于构建和发送HTTP响应你可以通过w写入响应正文、设置响应的状态码、添加响应头等。 *r http.Request 代表接收到的HTTP请求。它包含了请求的各自信息比如请求方法URL请求头请求正文等。
综上也就是服务器接收到了请求就会输出响应w和hello world
http.ListenAndServe(“:8080”, nil) 用于启动HTTP服务器第一个参数代表监听本地的8080端口第二个参数是处理器nil表示使用默认多路复用器http.DefaultServeMux。
调用之后会起到HTTP服务器并监听端口等待并处理HTTP请求。
http.ServeFile可以轻松地将静态文件提高给客户端。
package mainimport (net/http
)func main() {http.Handle(/, http.FileServer(http.Dir(static)))http.ListenAndServe(:8080, nil)
}
解读 会把当前目录下的static文件夹作为根目录提供静态文件服务例如客户端请求http://localhost:8080/index.html时服务器返回的是static/index.html文件。
总结
以上是http客户端和服务器的基本用法这个包还有很多其他的功能比如实现了WebSocket,HTTP长连接Http代理等等。根据需要查询文档。 http请求方法
看day1
http框架
1.GinGin是一个高性能、易用的HTTP框架它提供了路由、中间件、静态文件服务、模板渲染等常用功能并且支持自定义中间件和路由分组等高级特性。Gin的设计理念是尽量简单、快速地完成HTTP请求处理并且提供高度可定制的API接口。
2.ECHO
3.BeegoBeego是一个完整的Web框架它提供了MVC架构、ORM、Websocket、RESTful API等功能并且支持国际化、日志、缓存等高级特性。Beego的设计理念是快速开发、易用可扩展并且提供丰富的文档和社区支持。
4.Revel 还有很多框架这里做了解1是必须要学的Beego看有没有多于的时间学。