WebSocket项目笔记

1. What is WebSocket?

  (以下内容来源于百度百科)

  • WebSocket是一种在单个TCP连接上进行全双工通信的协议
  • WebSocket使得客户端和服务器之间的数据交换变得更加简单,允许服务端主动向客户端推送数据
  • 在WebSocket API中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接,并进行双向数据传输
  • 背景:
    • 推送技术的演进发展:轮询 ---> Comet ---> WebSocket
  • 握手协议:

  1. WebSocket 是独立的、创建在 TCP 上的协议。
  2. Websocket 通过HTTP/1.1 协议的101状态码进行握手。
  3. 为了创建Websocket连接,需要通过浏览器发出请求,之后服务器进行回应,这个过程通常称为“握手”(handshaking)。

2. Let's try!

   因为项目要求,我的小程序需要与后端服务器进行长连接,以传送数据。所以我需要在我配置好的Springboot框架中添加WebSocket。十分庆幸的是,Springboot已经集成好了WebSocket了。所以过程并不复杂。看了很多博客,内容都大同小异。

   我有点懵,因为我发现大家都在说的是,客户端与服务器建立连接的过程的实现。所以有几个问题:

  1. 既然是服务器主动发送消息,那么服务器到底  “到底什么时候发送消息呢?怎么发送?”
  2. 如何传参数,传的参数如何接收与使用。
  3. 我只需要针对某个客户端发消息,在同时有多个socket连接的时候我怎么标识该客户端?

  大概就有这些。下面我们在配置过程中将问题逐个击破!

  • 开发环境:Springboot 1.5.19  Java1.8
  • 配置pom文件
        <!-- 引入 websocket 依赖类-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
</dependency>
  • 配置Springboot开启WebSocket支持
package com.cuc.happyseat.config.websocket;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter; /**
* 开启WebSocket支持
*/
@Configuration
public class WebSocketConfig { @Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
} }
  • WebSocket服务类编写

    这里对照下面的代码看:

    1. 第20行,@ServerEndpoint("/websocket/{userID}"),括号中的内容就是客户端请求Socket连接时的访问路径,userID是我要求客户端传来的参数,我这里算是为了标识该客户端吧。
    2. 第28行,在该类中添加属性 userID,并添加对应的getUserID()方法。
    3. 第46行,在onOpen()方法即建立连接的时候就接收参数userID,需要标识@PathParam("userID") 。接收参数后直接赋值给属性userID。
    4. 第140-157行,是针对特定客户端发送消息。服务器和客户端在建立连接成功后就生成了一个WebSocket对象,并存在集合中,对象里特有的属性是我们设置的userID。所以通过唯一的userID就能标识服务器与该客户端建立的那个连接啦!这样要求发送消息时,传入userID与消息,服务器在自己的WebSocket连接集合中遍历找到对应客户端的连接,就可以直接发消息过去啦~~
 package com.cuc.happyseat.websocket;

 import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet; import javax.websocket.EncodeException;
import javax.websocket.OnClose;
import javax.websocket.OnError;
import javax.websocket.OnMessage;
import javax.websocket.OnOpen;
import javax.websocket.Session;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint; import org.springframework.stereotype.Component; /*@ServerEndpoint注解是一个类层次的注解,它的功能主要是将目前的类定义成一个websocket服务器端,
* 注解的值将被用于监听用户连接的终端访问URL地址,客户端可以通过这个URL来连接到WebSocket服务器端
*/
@ServerEndpoint("/websocket/{userID}")
@Component
public class WebSocketServer { //每个客户端都会有相应的session,服务端可以发送相关消息
private Session session; //接收userID
private Integer userID; //J.U.C包下线程安全的类,主要用来存放每个客户端对应的webSocket连接
private static CopyOnWriteArraySet<WebSocketServer> copyOnWriteArraySet = new CopyOnWriteArraySet<WebSocketServer>(); public Integer getUserID() {
return userID;
} /**
* @Name:onOpen
* @Description:打开连接。进入页面后会自动发请求到此进行连接
* @Author:mYunYu
* @Create Date:14:46 2018/11/15
* @Parameters:@PathParam("userID") Integer userID
* @Return:
*/
@OnOpen
public void onOpen(Session session, @PathParam("userID") Integer userID) {
this.session = session;
this.userID = userID;
System.out.println(this.session.getId());
//System.out.println("userID:" + userID);
copyOnWriteArraySet.add(this);
System.out.println("websocket有新的连接, 总数:"+ copyOnWriteArraySet.size()); } /**
* @Name:onClose
* @Description:用户关闭页面,即关闭连接
* @Author:mYunYu
* @Create Date:14:46 2018/11/15
* @Parameters:
* @Return:
*/
@OnClose
public void onClose() {
copyOnWriteArraySet.remove(this);
System.out.println("websocket连接断开, 总数:"+ copyOnWriteArraySet.size());
} /**
* @Name:onMessage
* @Description:测试客户端发送消息,测试是否联通
* @Author:mYunYu
* @Create Date:14:46 2018/11/15
* @Parameters:
* @Return:
*/
@OnMessage
public void onMessage(String message) {
System.out.println("websocket收到客户端发来的消息:"+message);
} /**
* @Name:onError
* @Description:出现错误
* @Author:mYunYu
* @Create Date:14:46 2018/11/15
* @Parameters:
* @Return:
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误:" + error.getMessage() + "; sessionId:" + session.getId());
error.printStackTrace();
} public void sendMessage(Object object){
//遍历客户端
for (WebSocketServer webSocket : copyOnWriteArraySet) {
System.out.println("websocket广播消息:" + object.toString());
try {
//服务器主动推送
webSocket.session.getBasicRemote().sendObject(object) ;
} catch (Exception e) {
e.printStackTrace();
}
}
} /**
* @Name:sendMessage
* @Description:用于发送给客户端消息(群发)
* @Author:mYunYu
* @Create Date:14:46 2018/11/15
* @Parameters:
* @Return:
*/
public void sendMessage(String message) {
//遍历客户端
for (WebSocketServer webSocket : copyOnWriteArraySet) {
System.out.println("websocket广播消息:" + message);
try {
//服务器主动推送
webSocket.session.getBasicRemote().sendText(message);
} catch (Exception e) {
e.printStackTrace();
}
}
} /**
* @throws Exception
* @Name:sendMessage
* @Description:用于发送给指定客户端消息
* @Author:mYunYu
* @Create Date:14:47 2018/11/15
* @Parameters:
* @Return:
*/
public void sendMessage(Integer userID, String message) throws Exception {
Session session = null;
WebSocketServer tempWebSocket = null;
for (WebSocketServer webSocket : copyOnWriteArraySet) {
if (webSocket.getUserID() == userID) {
tempWebSocket = webSocket;
session = webSocket.session;
break;
}
}
if (session != null) {
//服务器主动推送
tempWebSocket.session.getBasicRemote().sendText(message); } else {
System.out.println("没有找到你指定ID的会话:{}"+ "; userId:" + userID);
}
} }
  • Controller类的编写。
    1. 我在看博客的时候,发现有的博主写了Controller类,有的没写,我就有点疑惑了。后来,咳咳,发现特地写了一个Controller类只是为了测试。。
    2. 一般在实际项目中,在确保建立连接过程没有问题的情况下,我们就直接在一些写好的接口中写 WebSocketServer.sendMessage(param, message)语句就行了。
    3. 也因此,你写的位置就决定了你什么时候给你的客户端发消息,这样也就实现了主动推送消息的功能咯~
    4. 前提是:在你的Controller类里,以@Resource的方式注入WebSocket,而不是@Autowired方式哦(⊙o⊙)。

@Resource
WebSocketServer webSocket;
@Autowired
UserService userService;
调用示例:
    if(userID>0) {
boolean location = userService.getLocation(userID);
if(location==false) {//验证用户当前不在馆内
boolean i = userService.modifyLocation(userID, true);
if(i==true) {
modelMap.put("successEnter", true); //发消息给客户端
webSocket.sendMessage(userID, "success");
}
}else {
modelMap.put("successEnter", false);
//发消息给客户端
webSocket.sendMessage(userID, "fail");
}
}else {
modelMap.put("successEnter", false);
//发消息给客户端
webSocket.sendMessage(userID, "fail");
}

  • 前端测试

因为我只写后端,前端部分小姐姐说看微信的官方文档就可以啦~ 链接:在此!微信封装好了吧,好像不难。

3. Problems

  • 前期我看很多博客,的确产生很多问题,想不通,主要就是上面几个问题。然后我写了让前端先连WebSocket试了一下,想自己了解一下连接过程(也就是在控制台输出的文件中查),后来就慢慢通了。可以说是一帆风顺了??
  • 不过我确保我没问题不算,得前端说了算对吧。所以,,我背了锅

    我带着小程序和Springboot后端终于战胜了WebSocket!!!胜利( •̀ ω •́ )y的更多相关文章

    1. 微信小程序开发(后端Java)

      微信使用的开发语言和文件很「特殊」. 小程序所使用的程序文件类型大致分为以下几种: ①WXML(WeiXin Mark Language,微信标记语言) ②WXSS(WeiXin Style Shee ...

    2. 【好好编程-技术博客】微信小程序开发中前后端的交互

      微信小程序开发中前后端的交互 微信小程序的开发有点类似与普通网页的开发,但是也不尽然相同.小程序的主要开发语言是JavaScript,开发同普通的网页开发有很大的相似性,对于前端开发者而言,从网页开发 ...

    3. 记录一次用宝塔部署微信小程序Node.js后端接口代码的详细过程

      一直忙着写毕设,上一次写博客还是元旦,大半年过去了.... 后面会不断分享各种新项目的源码与技术.欢迎关注一起学习哈! 记录一次部署微信小程序Node.js后端接口代码的详细过程,使用宝塔来部署. 我 ...

    4. 【node+小程序+web端】简单的websocket通讯

      [node+小程序+web端]简单的websocket通讯 websoket是用来做什么的? 聊天室 消息列表 拼多多 即时通讯,推送, 实时交互 websoket是什么 websocket是一个全新 ...

    5. 微信小程序项目总结-记账小程序(包括后端)

      一.小程序部分 这是理财系统的前端,江苏海洋大学微信小程序比赛,最后获得了一等奖 GitHub:https://github.com/GeorgeLeoo/finance 1. 项目描述 (1). 此 ...

    6. 微信小程序支付+php后端

      最近在做自有项目后端用的是thinkphp5.1框架,闲话不说直接上代码 小程序代码 wxpay: function(e){ let thisid = e.currentTarget.dataset. ...

    7. 关于微信小程序登录,后端如何生成3rd_session?(后端为c#)

      各位大神,请教一个问题,现在是小程序端调用wx.login后,将code传入后端接口,后端发起微信服务器request获取openid和session_key,后端再自定义生成一个登录状态:3rd_s ...

    8. 微信小程序支付C#后端源码

      using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.W ...

    9. 「小程序JAVA实战」小程序的springboot后台拦截器(61)

      转自:https://idig8.com/2018/09/24/xiaochengxujavashizhanxiaochengxudespringboothoutailanjieqi60/ 之前咱们把 ...

    随机推荐

    1. 广州三本找Java实习经历

      前言 只有光头才能变强 这阵子跑去面试Java实习生啦~~~我来简单介绍一下背景吧. 广州三本大三在读,在广州找实习.大学开始接触编程,一个非常平庸的人. 在学习编程时,跟我类似的人应该会有一个疑问: ...

    2. 卷积神经网络之LeNet

      开局一张图,内容全靠编. 上图引用自 [卷积神经网络-进化史]从LeNet到AlexNet. 目前常用的卷积神经网络 深度学习现在是百花齐放,各种网络结构层出不穷,计划梳理下各个常用的卷积神经网络结构 ...

    3. Java 学习笔记 Junit4单元测试使用

      Junit使用 1.导入Junit包 到官网下载个Junit4.12.jar文件,放在lib目录 或者在类的空白处打@Test,之后按下alt+enter,选择添加Junit4依赖 之后就会弹出一个窗 ...

    4. Android 初次进入默认不弹出软键盘

      [遇到问题] 初次进入某个界面时,当页面中有EditText ,会自动聚焦并弹出软键盘. [解决方法] getWindow().setSoftInputMode(WindowManager.Layou ...

    5. T-SQL语法基础

      一.T-SQL语言的分类 DDL-数据定义语言 create-创建 alter-修改 drop-删除(针对对象) DML-数据操作语言 Insert-插入 update-更新 delete-删除(针对 ...

    6. SQL Server关于predicate、density、selectivity、cardinality名词浅析

        在SQL Server中,我们要看懂执行计划和统计信息,我们可能需要深刻理解一些关键词,例如密度(Density).选择性(Selectivity).谓词(predicate).基数(Cardin ...

    7. 搭建环境-Monkeyrunner-自动化测试工具

      这篇博客帮助挺大,我补充部分,帮助同样的小白哈哈,侵删 https://www.cnblogs.com/lynn-li/p/5885001.html 1.前期准备 需要安装:JDK,SDK,pytho ...

    8. PuppeteerSharp: 更友好的 Headless Chrome C# API

      前端就有了对 headless 浏览器的需求,最多的应用场景有两个 UI 自动化测试:摆脱手工浏览点击页面确认功能模式 爬虫:解决页面内容异步加载等问题 也就有了很多杰出的实现,前端经常使用的莫过于 ...

    9. Netty1:初识Netty

      为什么使用Netty Netty是业界最流行的NIO框架之一,它的健壮性.功能.性能.可定制性.可扩展性在同类框架中都是首屈一指的,它已经得到了成百上千的商用项目的证明.对于为什么使用Netty这个话 ...

    10. 实现iframe高度自适应

      iframe高度自适应使用场景是类似于微博,新闻等点击加载更多这种功能实现,要求iframe的高度能够跟随内容的变化而变化. 父html文件,也就是引用ifram的文件 src="blog/ ...