前面一篇大致讲解了webSocket的定义以及配置,那么这一篇通过简单案例对webSocket更好的理解与使用。使用的是spring-servlet-webSocket的整合形式。因为这里主要是以webSocket为重点,主要展示的是webSocket如何使客户端与服务器端的交互变得简单。
1. 导入需要的maven包(这里version忽略了)
spring jar包:
org.springframework
spring-context-support
org.springframework
spring-messaging
org.springframework
spring-webmvc
org.springframework
spring-websocket
java.servlet jar包:
javax.servlet
javax.servlet-api
provided
javax.servlet.jsp
jsp-api
provided
json jar包:
com.google.code.gson
gson
com.fasterxml.jackson.core
jackson-core
com.fasterxml.jackson.core
jackson-databind
log jar 包:
ch.qos.logback
logback-classic
org.logback-extensions
logback-ext-spring
org.slf4j
jcl-over-slf4j
2.配置spring-servlet.xml文件
3.配置web.xml
spring-notes-helloworld
spring-servlet
org.springframework.web.servlet.DispatcherServlet
contextConfigLocation
classpath:spring/spring-servlet.xml
1
spring-servlet
/
encodingFilter
org.springframework.web.filter.CharacterEncodingFilter
encoding
UTF-8
forceEncoding
true
encodingFilter
/*
REQUEST
FORWARD
index.jsp
模拟qq聊天无非两个主要因素 人与聊天信息 ,所以这里写两个类,用户类与聊天信息类
User:
public class User {
private Long id;
private String name;
private String password;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Message:
public class Message {
// 发送者
private Long from;
// 发送者名称
private String fromName;
// 接收者
private Long to;
// 发送的文本
private String text;
// 发送日期
private Date date;
public Long getFrom() {
return from;
}
public void setFrom(Long from) {
this.from = from;
}
public Long getTo() {
return to;
}
public void setTo(Long to) {
this.to = to;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public String getFromName() {
return fromName;
}
public void setFromName(String fromName) {
this.fromName = fromName;
}
public Date getDate() {
return date;
}
public void setDate(Date date) {
this.date = date;
}
}
WebSocket握手请求的拦截器:
public class HandShake implements HandshakeInterceptor {
/**
* 在握手之前执行该方法, 继续握手返回true, 中断握手返回false. 通过attributes参数设置
*/
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Map attributes) throws Exception {
System.out.println("Websocket:用户[ID:"
+ ((ServletServerHttpRequest) request).getServletRequest().getSession(false).getAttribute("uid")
+ "]已经建立连接");
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
HttpSession session = servletRequest.getServletRequest().getSession(false);
// 标记用户
Long uid = (Long) session.getAttribute("uid");
if (uid != null) {
attributes.put("uid", uid);
} else {
return false;
}
}
return true;
}
/**
* 在握手之后执行该方法. 无论是否握手成功都指明了响应状态码和相应头.
*/
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler,
Exception exception) {
}
}
webSocket处理类
@Component
public class MyWebSocketHandler implements WebSocketHandler {
private static final Logger logger = LoggerFactory.getLogger(MyWebSocketHandler.class);
private static Map> userSocketSessionMap = new ConcurrentHashMap<>();
/**
* 建立连接后
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
Long uid = (Long) session.getAttributes().get("uid");
logger.info("Session {} connected.", uid);
// 如果是新 Session,记录进 Map
boolean isNewUser = true;
for (Object o : userSocketSessionMap.entrySet()) {
Entry entry = (Entry) o;
Long key = (Long) entry.getKey();
if (key.equals(uid)) {
userSocketSessionMap.get(uid).add(session);
isNewUser = false;
break;
}
}
if (isNewUser) {
Set sessions = new HashSet<>();
sessions.add(session);
userSocketSessionMap.put(uid, sessions);
}
logger.info("当前在线用户数: {}", userSocketSessionMap.values().size());
}
/**
* 消息处理,在客户端通过Websocket API发送的消息会经过这里,然后进行相应的处理
*/
@Override
public void handleMessage(WebSocketSession session, WebSocketMessage> message) throws Exception {
if (message.getPayloadLength() == 0) {
return;
}
Message msg = new Gson().fromJson(message.getPayload().toString(), Message.class);
msg.setDate(new Date());
sendMessageToUser(msg.getTo(),
new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));
}
/**
* 消息传输错误处理
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
if (session.isOpen()) {
session.close();
}
// 移除Socket会话
for (Set item : userSocketSessionMap.values()) {
if (item.contains(session)) {
// 删除连接 session
item.remove(session);
// 如果 userId 对应的 session 数为 0 ,删除该 userId 对应的记录
if (0 == item.size()) {
userSocketSessionMap.values().remove(item);
}
break;
}
}
}
/**
* 关闭连接后
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
logger.info("Session {} disconnected. Because of {}", session.getId(), closeStatus);
for (Set item : userSocketSessionMap.values()) {
if (item.contains(session)) {
// 删除连接 session
item.remove(session);
// 如果 userId 对应的 session 数为 0 ,删除该 userId 对应的记录
if (0 == item.size()) {
userSocketSessionMap.values().remove(item);
}
break;
}
}
logger.info("当前在线用户数: {}", userSocketSessionMap.values().size());
}
@Override
public boolean supportsPartialMessages() {
return false;
}
/**
* 给所有在线用户发送消息
*
* @param message
* @throws IOException
*/
public void broadcast(final TextMessage message) throws IOException {
// 多线程群发
for(Set item : userSocketSessionMap.values()) {
for (final WebSocketSession session : item) {
if (session.isOpen()) {
ScheduledExecutorService executorService = new ScheduledThreadPoolExecutor(1,
new BasicThreadFactory.Builder().namingPattern("socket-schedule-pool-%d").daemon(true).build());
for (int i = 0; i < 3; i++) {
executorService.execute(new Runnable() {
@Override
public void run() {
try {
if (session.isOpen()) {
logger.debug("broadcast output:" + message.toString());
session.sendMessage(message);
}
} catch (IOException e) {
e.printStackTrace();
}
}
});
}
}
}
}
}
/**
* 给某个用户发送消息
*
* @param userName
* @param message
* @throws IOException
*/
private void sendMessageToUser(Long uid, TextMessage message) throws IOException {
for (Long id : userSocketSessionMap.keySet()) {
if (id.equals(uid)) {
for (WebSocketSession session : userSocketSessionMap.get(uid)) {
try {
logger.info("SendAll: {}", message);
session.sendMessage(message);
} catch (Exception e) {
logger.error(e.toString());
}
}
}
}
}
}
webSocket入口:
@Configuration
@EnableWebSocket
public class WebSocketConfig extends WebMvcConfigurerAdapter implements WebSocketConfigurer {
@Bean
public WebSocketHandler myHandler() {
return new MyWebSocketHandler();
}
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
//允许连接的域,只能以http或https开头
String[] allowsOrigins = {"http://www.xxx.com"};
registry.addHandler(myHandler(), "/ws").addInterceptors(new HandShake());
registry.addHandler(myHandler(), "/ws/sockjs").addInterceptors(new HandShake()).
setAllowedOrigins(allowsOrigins).addInterceptors(new HandShake()).withSockJS();
}
}
MsgController控制层
@Controller
@RequestMapping("/msg")
public class MsgController {
@Resource
MyWebSocketHandler handler;
Map users = new HashMap<>();
// 模拟一些数据
@ModelAttribute
public void setReqAndRes() {
User u1 = new User();
u1.setId(1L);
u1.setName("张三");
users.put(u1.getId(), u1);
User u2 = new User();
u2.setId(2L);
u2.setName("李四");
users.put(u2.getId(), u2);
}
// 用户登录
@RequestMapping(value = "login", method = RequestMethod.POST)
public ModelAndView doLogin(User user, HttpServletRequest request) {
request.getSession().setAttribute("uid", user.getId());
request.getSession().setAttribute("name", users.get(user.getId()).getName());
return new ModelAndView("redirect:talk");
}
// 跳转到交谈聊天页面
@RequestMapping(value = "talk", method = RequestMethod.GET)
public ModelAndView talk() {
return new ModelAndView("talk");
}
// 跳转到发布广播页面
@RequestMapping(value = "broadcast", method = RequestMethod.GET)
public ModelAndView broadcast() {
return new ModelAndView("broadcast");
}
// 发布系统广播(群发)
@ResponseBody
@RequestMapping(value = "broadcast", method = RequestMethod.POST)
public void broadcast(String text) throws IOException {
Message msg = new Message();
msg.setDate(new Date());
msg.setFrom(-1L);
msg.setFromName("系统广播");
msg.setTo(0L);
msg.setText(text);
handler.broadcast(new TextMessage(new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg)));
}
// 发布系统广播(群发)
@ResponseBody
@RequestMapping(value = "test", method = RequestMethod.GET)
public void test(@RequestParam("text") String text) throws IOException {
Message msg = new Message();
msg.setDate(new Date());
msg.setFrom(-1L);
msg.setFromName("系统广播");
msg.setTo(0L);
msg.setText(text);
String output = new GsonBuilder().setDateFormat("yyyy-MM-dd HH:mm:ss").create().toJson(msg);
System.out.println("output:" + output);
handler.broadcast(new TextMessage(output));
}
}
前端JS
本文参考:https://www.cnblogs.com/jingmoxukong/p/7755643.html