摘要: 原创出处 blog.csdn.net/qq_32101993/article/details/83994524/ 「星光不问赶路人º」欢迎转载,保留摘要,谢谢!
websocket
简单的说,websocket是真正实现了全双工通信的服务器向客户端推的互联网技术。
全双工与单工、半双工的区别?
- 全双工:简单地说,就是可以同时进行信号的双向传输(A->B且B->A),是瞬时同步的。
- 单工、半双工:一个时间段内只有一个动作发生。
推送和拉取的区别?
- 推:由服务器主动发消息给客户端,就像广播。优势在于,信息的主动性和及时性。
- 拉:由客户端主动请求所需要的数据。
实现消息通信的几种方式?
- 传统的http协议实现方式:。
- 传统的socket技术。
- websocket协议实现方式。
接下来我们主要讲第三种,使用websocket协议,来实现服务端定时向客户端推送消息。
- 开发环境:jdk1.8、tomcat7
- 后台:springmvc、websocket、quartz
- 前台:html5中新增的API
- 开发工具:IDEA、maven
实现步骤
一、环境搭建
(1)导入相关约束:
在pom文件中加入需要的约束,spring相关的约束,请各位自己导入,这里我就不贴出来了。
<dependency> <groupId>org.quartz-scheduler</groupId> <artifactId>quartz</artifactId> <version>2.3.0</version> </dependency>
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-context-support</artifactId> <version>5.1.1.RELEASE</version> </dependency>
<dependency> <groupId>javax.websocket</groupId> <artifactId>javax.websocket-api</artifactId> <version>1.1</version> <scope>provided</scope> </dependency>
|
(2)配置xml文件
web.xml中就配置前端控制器,大家自行配置。然后,加载springmvc的配置文件。
springmvc.xml文件中
<context:component-scan base-package="com.socket.web" />
<bean class="org.springframework.web.servlet.view.InternalResourceViewResolver"> <property name="prefix" value="/WEB-INF/views/"/> <property name="suffix" value=".jsp"/> <property name="contentType" value="text/html; charset=utf-8"/> </bean> <mvc:annotation-driven/> <mvc:annotation-driven> <mvc:message-converters register-defaults="true"> <bean class="com.alibaba.fastjson.support.spring.FastJsonHttpMessageConverter"> <property name="supportedMediaTypes"> <list> <value>text/html;charset=UTF-8</value> <value>application/json</value> </list> </property> <property name="features"> <list> <value>WriteMapNullValue</value> <value>QuoteFieldNames</value> </list> </property> </bean> </mvc:message-converters> </mvc:annotation-driven>
|
到此,环境就基本搭建完成了。
二、完成后台的功能
这里我就直接贴出代码了,上面有相关的注释。
首先,完成websocket的实现类。
package com.socket.web.socket;
import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.stereotype.Component;
import javax.websocket.*; import javax.websocket.server.PathParam; import javax.websocket.server.ServerEndpoint; import java.io.IOException; import java.util.Map; import java.util.Set; import java.util.concurrent.ConcurrentHashMap;
@ServerEndpoint(value = "/socket/{ip}") @Component public class WebSocketServer {
private static final Logger LOGGER = LoggerFactory.getLogger(WebSocketServer.class);
private static int onLineCount = 0;
private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<String, WebSocketServer>();
private Session session;
private String ip;
@OnOpen public void onOpen(@PathParam("ip")String ip,Session session){ this.session = session; this.ip = ip; webSocketMap.put(ip,this); addOnLineCount(); LOGGER.info("有新的连接加入,ip:{}!当前在线人数:{}",ip,getOnLineCount()); }
@OnClose public void onClose(@PathParam("ip")String ip){ webSocketMap.remove(ip); subOnLineCount(); LOGGER.info("WebSocket关闭,ip:{},当前在线人数:{}",ip,getOnLineCount()); }
@OnMessage public void onMessage(String message,Session session){ LOGGER.info("收到客户端的消息:{}",message); }
@OnError public void onError(Session session,Throwable error){ LOGGER.error("WebSocket发生错误"); error.printStackTrace(); }
public void sendMessage(String message){ try{ this.session.getBasicRemote().sendText(message); }catch (IOException e){ e.printStackTrace(); LOGGER.info("发送数据错误:,ip:{},message:{}",ip,message); } }
public static void sendMessageAll(final String message){ Set<Map.Entry<String, WebSocketServer>> entries = webSocketMap.entrySet(); for (Map.Entry<String, WebSocketServer> entry : entries) { final WebSocketServer webSocketServer = entry.getValue(); new Thread(new Runnable() { public void run() { webSocketServer.sendMessage(message); } }).start(); } }
public static synchronized int getOnLineCount(){ return WebSocketServer.onLineCount; }
public static synchronized void addOnLineCount(){ WebSocketServer.onLineCount++; }
public static synchronized void subOnLineCount(){ WebSocketServer.onLineCount--; }
public Session getSession(){ return session; } public void setSession(Session session){ this.session = session; }
public static ConcurrentHashMap<String, WebSocketServer> getWebSocketMap() { return webSocketMap; }
public static void setWebSocketMap(ConcurrentHashMap<String, WebSocketServer> webSocketMap) { WebSocketServer.webSocketMap = webSocketMap; } }
|
然后写我们的定时器(quartz),这里我就不详解定时器了。大家可以自行去了解。
这里我使用的是xml注解的方式,创建一个job类,此类不需要继承任何类和实现任何接口。
package com.socket.web.quartz;
import com.socket.web.socket.WebSocketServer;
import java.io.IOException; import java.util.Map; import java.util.concurrent.ConcurrentHashMap;
public class TestJob {
public void task(){ ConcurrentHashMap<String, WebSocketServer> map = WebSocketServer.getWebSocketMap(); if (map.size() != 0){ for (Map.Entry<String, WebSocketServer> entry : map.entrySet()) { WebSocketServer webSocketServer = entry.getValue(); try { webSocketServer.getSession().getBasicRemote().sendText("每隔两秒,向客户端推送一次数据"); }catch (IOException e){ e.printStackTrace(); } } }else { System.out.println("WebSocket未连接"); } } }
|
定时器的实现类就完成了,我们还需要在springmvc.xml中进行配置
springmvc.xml配置:
<bean id="testJob" class="com.socket.web.quartz.TestJob"></bean>
<bean id="jobDetail" class="org.springframework.scheduling.quartz.MethodInvokingJobDetailFactoryBean"> <property name="targetObject" ref="testJob"/> <property name="targetMethod" value="task"></property> <property name="concurrent" value="false" /> </bean>
<bean id="trigger" class="org.springframework.scheduling.quartz.SimpleTriggerFactoryBean"> <property name="jobDetail" ref="jobDetail"/> <property name="startDelay" value="3000"/> <property name="repeatInterval" value="2000"/> </bean>
<bean id="scheduler" class="org.springframework.scheduling.quartz.SchedulerFactoryBean"> <property name="triggers"> <list> <ref bean="trigger"/> </list> </property> </bean>
|
接下来是controller层的代码,就一个登录的功能。
package com.socket.web.controller;
import com.socket.domain.User; import com.sun.org.apache.bcel.internal.generic.RETURN; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpSession; import java.util.UUID;
@RequestMapping("socket") @Controller public class ChatController {
@RequestMapping(value = "/login",method = RequestMethod.GET) public String goLogin(){ return "login"; }
@RequestMapping(value = "/home",method = RequestMethod.GET) public String goMain(HttpServletRequest request){ HttpSession session = request.getSession(); if (null == session.getAttribute("USER_SESSION")){ return "login"; } return "home"; }
@RequestMapping(value = "/login",method = RequestMethod.POST) public String login(User user, HttpServletRequest request){ HttpSession session = request.getSession(); session.setAttribute("USER_SESSION",user); return "redirect:home"; }
}
|
以上就是登录的代码了,基本上就是伪代码,只要输入用户名就可以了,后面的逻辑,大家可以根据自己的业务来实现。
最后就是前台页面的设计了,登录,login.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <c:set var="path" value="${pageContext.request.contextPath}"/> <html> <head> <title>登录</title> </head> <body> <form action="${path}/socket/login" method="post"> 登录名:<input type="text" name="username"/> <input type="submit" value="登录"/> </form> </body> </html>
|
消息接收页面,home.jsp
<%@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>聊天</title> <script type="text/javascript"> var webSocket = null; if ('WebSocket' in window) { webSocket = new WebSocket("ws://localhost:9001/socket/127.0.0.1"); } else if ('MozWebSocket' in window) { webSocket = new MozWebSocket("ws://localhost:9001/socket/127.0.0.1"); } else { alert('Not support webSocket'); }
webSocket.onopen = function (event) { alert("websocket已经连接"); } webSocket.onmessage = function (event) { console.info(event); alert(event.data); } webSocket.onerror = function (event) { console.info("发生错误"); alert("websocket发生错误" + event); }
webSocket.onclose = function () { console.info("关闭连接"); }
window.onbeforeunload = function (event) { webSocket.close(); } </script> </head> <body>
</body> </html>
|
基本上,数据推送的功能就完成了,下面附上效果图。
启动tomcat。后台定时器两秒刷新一次,判断是否有websocket连接。

登录页面:

数据推送页面:

服务器定时向客户端推送数据的功能就完成了,有不明白的可以给博主留言,如果有什么错误,也希望各位朋友指出,谢谢大家。