第七节WebSocket

HTML5 WebSocket

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。
WebSocket 使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据。在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输。
在 WebSocket API 中,浏览器和服务器只需要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
现在,很多网站为了实现推送技术,所用的技术都是 Ajax 轮询。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出HTTP请求,然后由服务器返回最新的数据给客户端的浏览器。这种传统的模式带来很明显的缺点,即浏览器需要不断的向服务器发出请求,然而HTTP请求可能包含较长的头部,其中真正有效的数据可能只是很小的一部分,显然这样会浪费很多的带宽等资源。
HTML5 定义的 WebSocket 协议,能更好的节省服务器资源和带宽,并且能够更实时地进行通讯
![](https://img-blog.csdnimg.cn/img_convert/6903ad9782356ed3267da03ed699afd1.png#align=left&display=inline&height=444&margin=[object Object]&originHeight=444&originWidth=834&size=0&status=done&style=none&width=834)
浏览器通过 JavaScript 向服务器发出建立 WebSocket 连接的请求,连接建立以后,客户端和服务器端就可以通过 TCP 连接直接交换数据。
当你获取 Web Socket 连接后,你可以通过 send() 方法来向服务器发送数据,并通过 onmessage 事件来接收服务器返回的数据。
以下 API 用于创建 WebSocket 对象。

介绍

首先要使用WebSocket那么肯定需要服务器端有一个连接的地方,那么用golang来做就是将普通的一个接口,通过声明升级,来将这个接口升级为ws协议而不是http协议。

在golang中使用WebSocket有很多可以使用的包,这里使用github.com/gorilla/websocket这个包,我们可以去这个地址看一下,有详细的项目介绍,以及使用方法。

安装

在项目中执行go get github.com/gorilla/websocket 将这个依赖包拉到我们的项目中

实现

// Manager 所有 websocket 信息
type Manager struct {
	IdClientMap                              map[int32]*Client // 用户连接数组
	Lock                                     sync.RWMutex // 锁
	MonitoringRealData, Register, UnRegister chan *Client // 监控实时数据,注册连接,删除连接
	Message                                  chan *MessageData // 定义一个消息通道
	GroupMessage                             chan *GroupMessageData // 消息组通道
	BroadCastMessage                         chan *BroadCastMessageData // 广播消息
	incr                                     int32 // 自增数
}

首先定义一个结构体,来存储记录我们的websocket中的内容,每个用户请求接口连接WebSocket都是一个单独的进程,那么如果想要用户之间可以进行交互那么就需要将连接都存储起来,这里就
用一个IdClientMap来存储每个用户的连接,每次用户连接进来 incr都是自增+1 这个就代表当前用户的一个连接ID,用连接ID存储当前连接到数组中,就可以使用IdClientMap[1].client就可以做交互了。
定义了之后需要将这个结构体初始化,那么定义一个变量来初始化结构体中的各个属性。

var (
	// WebsocketManager 初始化 wsManager 管理器
	WebsocketManager = Manager{
		IdClientMap:        make(map[int32]*Client),
		Register:           make(chan *Client, 128),
		UnRegister:         make(chan *Client, 128),
		MonitoringRealData: make(chan *Client, 128),
		Message:            make(chan *MessageData, 128),
		GroupMessage:       make(chan *GroupMessageData, 128),
		BroadCastMessage:   make(chan *BroadCastMessageData, 128),
		incr:               1,
	}
	SystemId int32 = 1
)

// Client 单个 websocket 信息
type Client struct {
	Id        int32
	GroupList []int32
	Socket    *websocket.Conn
	Message   chan []byte
}

// messageData 单个发送数据信息
type MessageData struct {
	Id      int32
	Group   int
	Message []byte
}

// groupMessageData 组广播数据信息
type GroupMessageData struct {
	Group   int32
	Message []byte
}

// 广播发送数据信息
type BroadCastMessageData struct {
	Message []byte
}

// 读取消息
type ReadMessageRequest struct {
	Type     int    `json:"type"`
	Message  string `json:"message"`
	AcceptId int32  `json:"accept_id"`
}

// 消息返回
type MessageResponse struct {
	Status  int    `json:"status"`
	Type    int    `json:"type"`
	Message string `json:"message"`
	FromId  int32  `json:"from_id;"`
}

这里将各个属性都make初始化,再定义好所需的对应结构体,读取消息,存储用户连接发送消息,广播消息,群组消息。这里定义完之后,创建一个接口来访问。

// webSocket连接
func (manager *Manager) WsClient(ctx *gin.Context) {
	upGrader := websocket.Upgrader{
		// cross origin domain
		CheckOrigin: func(r *http.Request) bool {
			return true
		},
		// 处理 Sec-WebSocket-Protocol Header
		Subprotocols: []string{ctx.GetHeader("Sec-WebSocket-Protocol")},
	}
	conn, err := upGrader.Upgrade(ctx.Writer, ctx.Request, nil)
	if err != nil {
		log.Printf("websocket connect error: %s", ctx.Param("channel"))
		return
	}
	client := &Client{
		Id:      atomic.AddInt32(&manager.incr, 1),
		Socket:  conn,
		Message: make(chan []byte, 1024),
	}
	go client.Read(manager)
	go client.Write()
	manager.RegisterClient(client)
}

这里创建了一个Manage结构体下的成员方法WsClient ,在方法中调用拉取的websocket包的方法websocket.Upgrader 定义升级http到ws的参数,再调用upGrader.Upgrade(ctx.Writer, ctx.Request, nil)将当前接口升级为WebSocket接口。再初始化Client结构体,设置当前用户的连接,以及唯一的ID,再初始化一个消息的通道,
下面使用了go client.Read(manager) 这里是开启了golang的一个goroutine协程,这个是读取WebSocket消息,另外还有一个go client.Write() 这里是将消息传输到客户端,然后调用manager.RegisterClient(client) 注册一个连接到组里


// 读取websocket发送来的数据
func (c *Client) Read(manager *Manager) {
	defer func() {
		log.Printf("close:[%s],date:%s", c.Id, time.Now())
		WebsocketManager.UnRegister <- c
	}()
	for {
		messageType, message, err := c.Socket.ReadMessage()
		if err != nil || messageType == websocket.CloseMessage {
			break
		}
		var ResponseData []byte
		ReadMessage := ReadMessageRequest{}
		if err := json.Unmarshal(message, &ReadMessage); err != nil {
			ResponseData, _ = json.Marshal(MessageResponse{Status: 201, Message: "消息发送失败", Type: 1, FromId: SystemId})
			c.Message <- ResponseData
		} else {
			acceptClient, ok := manager.IdClientMap[ReadMessage.AcceptId]
			fmt.Printf("ok:%s,acceptId:%s", ok, ReadMessage.AcceptId)
			if !ok {
				ResponseData, _ = json.Marshal(MessageResponse{Status: 201, Message: "消息发送失败,对方已离线", Type: 1, FromId: SystemId})
				c.Message <- ResponseData
			} else {
				ResponseData, _ = json.Marshal(MessageResponse{Status: 200, Message: ReadMessage.Message, Type: 1, FromId: c.Id})
				acceptClient.Message <- ResponseData
			}
		}

		fmt.Printf("[clientId:]%v,[message:]%s\n", c.Id, message)
	}

}

// 写信息,从 channel 变量 Send 中读取数据写入 websocket 连接
func (c *Client) Write() {
	defer func() {
		log.Printf("clientWrite [%s] disconnect", c.Id)
		if err := c.Socket.Close(); err != nil {
			log.Printf("clientWrite [%s] disconnect err: %s", c.Id, err)
		}
	}()
	for {
		select {
		case message, ok := <-c.Message:
			if !ok {
				_ = c.Socket.WriteMessage(websocket.CloseMessage, []byte{})
				return
			}

			err := c.Socket.WriteMessage(websocket.TextMessage, message)
			if err != nil {
				log.Printf("client [%s] writemessage err: %s", c.Id, err)
			}
		}
	}
}

// 注册用户进程
func (manager *Manager) RegisterClient(c *Client) {
	manager.Register <- c
}

// 注销用户进程
func (manager *Manager) UnRegisterClient(c *Client) {
	manager.UnRegister <- c
}

// 监听用户进程注册,用户进程注销
func (manager *Manager) Start() {
	for {
		select {
		case client := <-manager.Register:
			log.Printf("client [%s] connect", client.Id)
			/*		log.Printf("register client [%s] to group [%s]", client.Id, client.Group)
			 */
			manager.Lock.RLock()
			if _, ok := manager.IdClientMap[client.Id]; ok {
				manager.IdClientMap[client.Id].Socket.Close()
			}
			manager.IdClientMap[client.Id] = client
			ResponseData, _ := json.Marshal(MessageResponse{Status: 200, Message: fmt.Sprintf("连接成功,您的会话ID为[%v]", client.Id), Type: 1, FromId: SystemId})
			client.Message <- ResponseData
			manager.Lock.RUnlock()

		// 注销
		case client := <-manager.UnRegister:
			manager.Lock.Lock()
			delete(manager.IdClientMap, client.Id)
			manager.Lock.Unlock()
			close(client.Message)
		}
	}
}

这里一共三个方法,Read,Write,Start 分别是读,写,启动。
通过c.Socket.ReadMessage() 分别返回3个参数,消息内容,消息类型,错误,这里我们定义全部使用json来传输消息,那么消息类型就是文本类型,判断一下err是否有错误,如果没有错误那么将收到的消息解析json,就可以拿到对应传输过来的参数,处理过后通过c.Message <- ResponseData 将消息传输到通道中。在Write中开启了一个循环,通过select case来接受一个channel通道的数据,然后通过c.Socket.WriteMessage(websocket.TextMessage, message) 来将数据传输给客户端。

调用

![image.png](https://img-blog.csdnimg.cn/img_convert/ea9887aa6a88177b187c87c9462a37ea.png#align=left&display=inline&height=68&margin=[object Object]&name=image.png&originHeight=136&originWidth=1514&size=36747&status=done&style=none&width=757)
首先将WebSocket启动。然后将我们实现的接口WsClient注册一个路由/user/connect 将服务启动,那么就可以通过WebSocket连接到服务器

可以通过http://www.websocket-test.com/ 这个地址是一个WebSocket的测试网站。我们可以将地址粘贴进去连接进行一个测试。
![image.png](https://img-blog.csdnimg.cn/img_convert/deeed2b1166d865495af4c8d769693f1.png#align=left&display=inline&height=740&margin=[object Object]&name=image.png&originHeight=1480&originWidth=3066&size=535381&status=done&style=none&width=1533)
![image.png](https://img-blog.csdnimg.cn/img_convert/ec22bff24d5ef690697d0fceb0fdcfe0.png#align=left&display=inline&height=786&margin=[object Object]&name=image.png&originHeight=1572&originWidth=2076&size=428807&status=done&style=none&width=1038)
这里填入地址ws://127.0.0.1:8088/user/connect就是我们实现的WebSocket接口,这里的协议是必须ws://,否则连接就会不成功,下面就可以发送消息到服务器。

![image.png](https://img-blog.csdnimg.cn/img_convert/6d9aa7f0be0dfc518ae5f73bff663706.png#align=left&display=inline&height=692&margin=[object Object]&name=image.png&originHeight=1384&originWidth=2246&size=357072&status=done&style=none&width=1123)
这里发送消息{"type":1,"message":"xiaoxi ","accept_id":2} 定义类型是文本,消息内容,接受人ID,这里发送过去之后返回消息发送失败,对方已离线,那是因为id为2的已经不在线 断开连接了,那么重新打开一个网页 在来连接
![image.png](https://img-blog.csdnimg.cn/img_convert/b554ba3e17e6df0665365431924a728b.png#align=left&display=inline&height=680&margin=[object Object]&name=image.png&originHeight=1360&originWidth=1944&size=338723&status=done&style=none&width=972)
这里新开浏览器连接的ID为4,那么我们在这边发送消息给连接ID为3的用户

![image.png](https://img-blog.csdnimg.cn/img_convert/5281da180a2b2e24849ddc281790d328.png#align=left&display=inline&height=303&margin=[object Object]&name=image.png&originHeight=606&originWidth=1142&size=64337&status=done&style=none&width=571)
这边发送之后,打开3号的窗口
![image.png](https://img-blog.csdnimg.cn/img_convert/25b4a57f6cc4ddf3a3e9a251008be573.png#align=left&display=inline&height=204&margin=[object Object]&name=image.png&originHeight=408&originWidth=1032&size=50339&status=done&style=none&width=516)

可以看到这里多了一条消息,状态200 成功,消息内容,以及发送人的ID,这样一个简单的聊天WebSocket就完成了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值