websocket是什么?
websocket是一个以TCP/IP为基础的应用层协议。它能够实现全双工通信,这样就会使得客户端与服务端的通信变的简单,服务端可以主动推送消息给客户端。它的最大特点就是,服务器可以主动向客户端推送信息,客户端也可以主动向服务器发送信息,是真正的双向平等对话,属于服务器推送技术的一种。
在 WebSocket API 中,浏览器和服务器只需要完成一次握手,两者之间就直接可以创建持久性的连接, 并进行双向数据传输。(维基百科)
为什么要有websocket通信
因为http协议存在一个问题或特点:协议只能由客户端发起。
就比如说我想知道现在的天气,就要使用app或手机组件主动向服务端发起请求才能获得当前的天气,这也就说明如果使用http来实现的话用户就只能自己先去申请,然后再获取当前的数据。这样就说明如果服务端有数据的变化,那么客户端要更新就比较麻烦了,我们只能使用轮询操作,也就是每隔一段时间去查询一下数据。轮询的效率低,非常浪费资源(因为必须不停连接,或者 HTTP 连接始终打开)。所以就有了websocket。
socket和websocket的区别
Socket 其实并不是一个协议,而是为了方便使用 TCP 或 UDP 而抽象出来的一层,是位于应用层和传输控制层之间的一组接口。 Socket本身并不是一个协议,它工作在OSI模型会话层,是一个套接字,TCP/IP网络的API,是为了方便大家直接使用。
更底层协议而存在的一个抽象层。Socket其实就是一个门面模式,它把复杂的TCP/IP协议族隐藏在Socket接口后面,对用户来说,一组简单的接口就是全部,让Socket去组织数据,以符合指定的协议。
而WebSocket则是一个典型的应用层协议。
websocket的实现借鉴了socket的实现。
websocket的特点
普遍认为,WebSocket的优点有如下几点:
- 较少的控制开销:在连接创建后,服务器和客户端之间交换数据时,用于协议控制的数据包头部相对较小;数据格式比较轻量,性能开销小,通信高效。
- 更强的实时性:由于协议是全双工的,所以服务器可以随时主动给客户端下发数据。相对于 HTTP 请求需要等待客户端发起请求服务端才能响应,延迟明显更少;
- 保持连接状态:与 HTTP 不同的是,WebSocket 需要先创建连接,这就使得其成为一种有状态的协议,之后通信时可以省略部分状态信息;
- 更好的二进制支持:WebSocket 定义了二进制帧,相对 HTTP,可以更轻松地处理二进制内容;
- 可以支持扩展:WebSocket 定义了扩展,用户可以扩展协议、实现部分自定义的子协议。
- websocket的端口和tttp相同,不使用tls双方都是80,使用tls双方都是443
- 没有同源限制,客户端可以与任意服务器通信。
- 协议标识符是
ws(如果加密,则为wss),服务器网址就是 URL。
基于上面的特点websocket被广泛地应用在即时通讯/IM、实时音视频、在线教育和游戏等领域。
java使用websocket
引入依赖:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-websocket</artifactId>
<version>2.5.1</version>
</dependency>
websocket配置类:
@Configuration
public class WebSocketConfig {
//用于发现websocket服务的
@Bean
public ServerEndpointExporter serverEndpointExporter(){
return new ServerEndpointExporter();
}
}
websocketService的定义, 例子:
@Component
//被ServerEndpoint标注后说明就是和websocket相关的服务类了。实际访问过程中根据参数来确定访问的位置
@ServerEndpoint("/imserver/{token}")
public class WebSocketService {
//记录日志
private final Logger logger = LoggerFactory.getLogger(this.getClass());
//原子类,用于记录连接的人数。原子类的相关操作都是原子性的,可以保护线程的安全
private static final AtomicInteger ONLINE_COUNT = new AtomicInteger(0);
//使用concurrentHashMap来记录用户以及和用户对应的websocketservice,在spring中,默认是单例模式,而这里的实现形式要求一个用户一个websocketservice,所以我们要使用别的方法实现。
public static final ConcurrentHashMap<String, WebSocketService> WEBSOCKET_MAP = new ConcurrentHashMap<>();
//用于客户端与服务端的会话
private Session session;
//标记session
private String sessionId;
//标记用户
private Long userId;
//OnOpen是websocket提供的方法,表示连接成功时调用OnOpen注解的方法
@OnOpen
public void openConnection(Session session, @PathParam("token") String token){
try{//验证用户的token是否过期
this.userId = TokenUtil.verifyToken(token);
}catch (Exception ignored){}
//获取传进来的session和sessionid
this.sessionId = session.getId();
this.session = session;
if(WEBSOCKET_MAP.containsKey(sessionId)){
//如果sessionID已存在,删除原有的。再加新的
WEBSOCKET_MAP.remove(sessionId);
WEBSOCKET_MAP.put(sessionId, this);
}else{
//原来没有,直接加入,并且增加人数
WEBSOCKET_MAP.put(sessionId, this);
ONLINE_COUNT.getAndIncrement();
}
//显示信息
logger.info("用户连接成功:" + sessionId + ",当前在线人数为:" + ONLINE_COUNT.get());
try{
//在项目中返回状态码,本项目中使用到了
this.sendMessage("0");
}catch (Exception e){
logger.error("连接异常");
}
}
//发送消息的方法
public void sendMessage(String message) throws IOException {
//调用session现成的方法进行使用
this.session.getBasicRemote().sendText(message);
}
关闭的情况
@OnClose
public void closeConnection(){
if(WEBSOCKET_MAP.containsKey(sessionId)){
//如果用户会话id存在,删除,人数减一
WEBSOCKET_MAP.remove(sessionId);
ONLINE_COUNT.getAndDecrement();
}
logger.info("用户退出:" + sessionId + "当前在线人数为:" + ONLINE_COUNT.get());
}
有消息需要进行通信的时候使用OnMessage进行标注,有异常时使用OnError进行处理。
/**
* 收到客户端消息后调用的方法,根据业务要求进行处理,这里就简单地将收到的消息直接群发推送出去
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
log.info("收到来自窗口"+sid+"的信息:"+message);
if(StringUtils.isNotBlank(message)){
for(WebSocketServer server:websocketMap.values()) {
try {
server.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
continue;
}
}
}
}
/**
* 发生错误时的回调函数
* @param session
* @param error
*/
@OnError
public void onError(Session session, Throwable error) {
log.error("发生错误");
error.printStackTrace();
}
解决单列bean注入的问题
//获取运行环境的上下文
private static ApplicationContext APPLICATION_CONTEXT;
//通过上下文来获取bean
public static void setApplicationContext(ApplicationContext applicationContext){
WebSocketService.APPLICATION_CONTEXT = applicationContext;
}