【编写中】html5+go+websocket不到150行代码,实现一个在线实时聊天的功能
创始人
2024-05-29 16:45:22
0

阮一峰websocket
相关参考

websocket

什么是websocket

在了解什么是websocket之前,我们下说一说http,因为HTTP我们太熟了。我们知道,HTTP是一种基于应用层的网络协议,往往都是一个请求,一个相应。websocket呢,也是一种基于应用层的网络协议,但是它不仅可以实现请求-相应这种模式,还可以实现主动推送,即你不请求,我也可以给你发消息通知。它实现了浏览器与服务器之间的双工通信。浏览器和服务器只需要完成一次握手,两者就可以创建一个持续的链接。

为什么出现websocket

  • http满足了我们大部分的需求,比如浏览网页 图片,视频,音频等等,我想要什么内容,我就给服务器发什么请求。
  • 但是随着互联网的发展,我们有了网络聊天的功能,在最早时候,要看对方有没有给你发送消息,得需要ajax轮询来实现,还有一些其他需要实时推送消息的场景,比如股票价格,体育解说,弹幕,直播这种,都需要服务器来主动推送消息到客户端。http不再能满足我们的需求。所以,在2008年,websocket诞生了。2011年成为了国际标准。

websocket特点

  • websocket最大的特点就是实现了浏览器和服务端之间的双工通信,更好的支持实时通信。

  • websocket头部信息很少,一般只有2bytes左右,节省了网络IO。

  • 因为websocket大部分的使用场景也是在浏览器中使用,HTTP、WebSocket 等应用层协议,都是基于 TCP 协议来传输数据的,因此其连接和断开,都要遵循 TCP 协议中的三次握手和四次挥手 ,所以websocket复用了http的握手机制,所以很好的兼容了http,默认端口也是80和443,复用 HTTP 的 Upgrade 机制,完成升级协议的协商过程,能通过各种http代理服务器。

  • 可以发送二进制数据

  • 没有同源策略,客户端可以和任意服务器通信。

  • 协议标识符是ws或者wss(加密),比如:ws://example.com:80/some/path

  • WebSocket API 是 HTML5 标准的一部分, 但这并不代表 WebSocket 一定要用在 HTML 中,或者只能在基于浏览器的应用程序中使用。 实际上,许多语言、框架和服务器都提供了 WebSocket 支持,例如 Nginx Apache C C++ Node Python等

websocket的工作原理
  • 我们先来启动一个简单的websocket服务,代码非常简单,大家理解完代码之后,再来下一步分析
    服务端代码
package mainimport ("fmt""github.com/gorilla/websocket""io/ioutil""log""net/http""os""time"
)
//显示页面
func rootHandler(w http.ResponseWriter, r *http.Request) {file, err := ioutil.ReadFile("othor-project/websocket-demo/index.html")if err != nil {fmt.Println(os.Getwd())fmt.Println(err)}_, err = fmt.Fprintf(w, "%s", file)if err != nil {fmt.Println(err)}
}//ws连接
func wsHandler(w http.ResponseWriter, r *http.Request) {//声明协议是websocketvar upgrader = websocket.Upgrader{} //定义一个websocketws, err := upgrader.Upgrade(w, r, nil)//初始化fmt.Println("i got a websocket")if err != nil {log.Print("upgrade:", err)return}ws.PongHandler()//主动发送消息给浏览器for i:=0;i<3;i++ {time.Sleep(time.Second*2)str:=fmt.Sprintf("hello i am %d",i)fmt.Println(str)err := ws.WriteMessage(websocket.TextMessage, []byte(str))if err != nil {fmt.Println("send message err")fmt.Println(err)}}//监听消息for  {_, i, err := ws.ReadMessage()if err != nil {fmt.Println("read msg error")fmt.Println(err)}fmt.Println(string(i))}
}func main() {fmt.Println("server start")//展示页面http.HandleFunc("/", rootHandler)//ws连接http.HandleFunc("/ws", wsHandler)//先开启端口监听err := http.ListenAndServe(":30000", nil)if err != nil {fmt.Println(err)}
}

我们简单了解一下h5怎么操作websocket
在这里插入图片描述
我们使用var ws = new WebSocket("ws://hostname/path", ["protocol1", "protocol2"])来建立一个连接。第一个参数是服务端websocket地址,如果是https+websocket,那么前缀写成wss。第二个参数并不是必须的,它约定了双方通讯使用的自定义子协议,会被放到这个Header中: Sec-WebSocket-Protocol。子协议在某些场合是很必要的,例如服务端要与多个客户端版本兼容,那么若干个版本之后,服务端设定支持子协议 v1.5, v2.0, 而客户端发送的却是 v1.0,那么他们就可以在握手阶段失败,不会继续通信下去导致奇奇怪怪的错误。

WebSocket构造函数只有两个变量,不能提供通过设置自定义Header的方式来携带其它信息,那么我们如何对ws的连接认证呢?可以通过以下方式实现认证:

  • 通过ws地址填写形如 ws://username:password@hostname/path, 即构造出了 Authorization Header
  • 通过ws地址填写形如 ws://:password@hostname/path ,即构造出了 Bearer Token Header
  • 通过在Cookie中加入值,也能够携带额外的信息
  • 在websocket连接建立后,再通过自定义的认证协议,走websocket进行认证。

客户端代码

启动后的效果,这是服务端往客户端推送的消息
在这里插入图片描述
这是客户端往服务端推送的消息
在这里插入图片描述

  • 建立连接
    具体连接过程如下图(ws表示协议头,101表示协议切换,upgrade表示要升级协议,upgrade:websocket 表示要升级到websocket协议,Sec-WebSocket-Key表示),websocket-key和websocket-accept之间的加密方式是base64(hsa1(sec-websocket-key + 258EAFA5-E914-47DA-95CA-C5AB0DC85B11)),如果这个Sec-WebSocket-Accept计算错误浏览器会提示Sec-WebSocket-Accept dismatch,如果返回成功,Websocket就会回调onopen事件。注意,每个ws的的key都是唯一的,所以如果你刷新了页面,ws的key也会刷新,服务端就会认为是新连接。
    在这里插入图片描述

  • 交换数据
    具体的数据格式是怎么样的呢?WebSocket 的每条消息可能会被切分成多个数据帧(最小单位)。发送端会将消息切割成多个帧发送给接收端,接收端接收消息帧,并将关联的帧重新组装成完整的消息。

  • 保持连接
    websocket使用心跳机制(ping和pong)来保持正常通信。定时发送一个数据包,让对方知道自己在线且正常工作。如果对方无法相应,则可以弃用旧连接,开启新连接。

群聊服务

  • 基于上面的知识,我们写一个简单的群聊系统
    服务端代码
package mainimport ("encoding/json""fmt""github.com/gorilla/websocket""io/ioutil""log""net/http""os"
)//定义一个聊天的结构体,姓名和信息
type Msg struct {Uid string `json:"uid"`Msg string `json:"msg"`
}//定义一个全局的channel用来接收消息
var msgChan = make(chan *Msg, 10)
var upgrader = websocket.Upgrader{} // use default options
//全局变量用来存储用户连接 用于发送消息
var clients = make(map[*websocket.Conn]int)//用来存放用户连接,读取完毕开启一个协程处理客户端发来的请求,用于读取消息
var clientsMsg = make(map[*websocket.Conn]int)//将channel中的消息推送给客户端
func sendMsg() {for {//从channel获取值msg := <-msgChanif msg != nil {fmt.Printf("我接收到了信息,我开始发送信息了\r\n")fmt.Println(msg)msgStr, _ := json.Marshal(msg)//给每一个websocket发送消息for client, v := range clients {fmt.Printf("我在给第%d个用户发信息\n\r", v)err := client.WriteMessage(websocket.TextMessage, msgStr)defer client.Close()if err != nil {fmt.Printf("给第%d个用户发信息失败了\r\n", v)fmt.Println(err)}}}}
}//接收客户端发来的msg,并写入chan
func getMsg() {for {//遍历所有的链接for client := range clientsMsg {defer client.Close()if client == nil {delete(clientsMsg, client)break} else {go listenMsg(client)delete(clientsMsg, client)}}}
}//给连接设置一个监听消息
func listenMsg(client *websocket.Conn) {var jsonMsg Msgfor {err := client.ReadJSON(&jsonMsg)if err != nil {fmt.Println("get msg error")fmt.Println(err)}fmt.Println(jsonMsg)if err != nil {fmt.Println("json error")}msgChan <- &jsonMsg}
}//显示网页
func rootHandler(w http.ResponseWriter, r *http.Request) {file, err := ioutil.ReadFile("othor-project/websocket/index.html")if err != nil {fmt.Println(os.Getwd())fmt.Println(err)}_, err = fmt.Fprintf(w, "%s", file)if err != nil {fmt.Println(err)}
}//建立ws连接
func wsHandler(w http.ResponseWriter, r *http.Request) {//声明协议是websocketws, err := upgrader.Upgrade(w, r, nil)if err != nil {log.Print("upgrade:", err)return}ws.PongHandler()clength := len(clients) + 1clients[ws] = clength //用于发送消息 把所有的socket连接都放到一个map,因为我收到一个消息,要发送到每一个连接的的socket//这里为了方便 我们就用第几个来代表用户id,项目中需要谨慎定义clientsMsg[ws] = clength //用于读取消息fmt.Printf("新用户id:%d", clength)clientLen := fmt.Sprintf("%d", clength)err = ws.WriteMessage(websocket.TextMessage, []byte(clientLen))if err != nil {fmt.Println("send id error")}
}/*** Notes:利用websocket快速实现一个聊天室*/
func main() {fmt.Println("server start")//页面http.HandleFunc("/", rootHandler)http.HandleFunc("/ws", wsHandler)// 监听所有ws发送的消息go getMsg()//发送消息给所有的wsgo sendMsg()//监听端口err := http.ListenAndServe(":30000", nil)if err != nil {fmt.Println(err)}
}

客户端代码






聊天系统:

uid:

信息:

  • 我模拟了3个用户,我们来看一下效果吧
    在这里插入图片描述
    看一下服务端的信息打印
    在这里插入图片描述

相关内容

热门资讯

喜欢穿一身黑的男生性格(喜欢穿... 今天百科达人给各位分享喜欢穿一身黑的男生性格的知识,其中也会对喜欢穿一身黑衣服的男人人好相处吗进行解...
发春是什么意思(思春和发春是什... 本篇文章极速百科给大家谈谈发春是什么意思,以及思春和发春是什么意思对应的知识点,希望对各位有所帮助,...
网络用语zl是什么意思(zl是... 今天给各位分享网络用语zl是什么意思的知识,其中也会对zl是啥意思是什么网络用语进行解释,如果能碰巧...
为什么酷狗音乐自己唱的歌不能下... 本篇文章极速百科小编给大家谈谈为什么酷狗音乐自己唱的歌不能下载到本地?,以及为什么酷狗下载的歌曲不是...
华为下载未安装的文件去哪找(华... 今天百科达人给各位分享华为下载未安装的文件去哪找的知识,其中也会对华为下载未安装的文件去哪找到进行解...
怎么往应用助手里添加应用(应用... 今天百科达人给各位分享怎么往应用助手里添加应用的知识,其中也会对应用助手怎么添加微信进行解释,如果能...
家里可以做假山养金鱼吗(假山能... 今天百科达人给各位分享家里可以做假山养金鱼吗的知识,其中也会对假山能放鱼缸里吗进行解释,如果能碰巧解...
四分五裂是什么生肖什么动物(四... 本篇文章极速百科小编给大家谈谈四分五裂是什么生肖什么动物,以及四分五裂打一生肖是什么对应的知识点,希...
一帆风顺二龙腾飞三阳开泰祝福语... 本篇文章极速百科给大家谈谈一帆风顺二龙腾飞三阳开泰祝福语,以及一帆风顺二龙腾飞三阳开泰祝福语结婚对应...
美团联名卡审核成功待激活(美团... 今天百科达人给各位分享美团联名卡审核成功待激活的知识,其中也会对美团联名卡审核未通过进行解释,如果能...