本篇博客主要记录下使用websocket作为客户端的功能
一,websocket服务端
1,创建一个spring boot项目
pom.xml的代码如下:
4.0.0
com.jack
springboot-websocket-chat
0.0.1-SNAPSHOT
jar
springboot-websocket-chat
Demo project for Spring Boot
org.springframework.boot
spring-boot-starter-parent
1.5.8.RELEASE
UTF-8
UTF-8
1.8
org.springframework.boot
spring-boot-starter-thymeleaf
org.springframework.boot
spring-boot-starter-websocket
com.alibaba
fastjson
1.2.39
org.java-websocket
Java-WebSocket
1.3.5
org.springframework.boot
spring-boot-starter-test
test
org.springframework.boot
spring-boot-maven-plugin
application.yml里面的配置:
server:
port: 9090
spring:
application:
name: websocket-chat
2,spring mvc的配置类
package com.jack.config;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ViewControllerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
* Created by jack on 2017/10/25.
*/
@Configuration
public class WebMvcConfig extends WebMvcConfigurerAdapter {
@Override
public void addViewControllers(ViewControllerRegistry registry) {
//super.addViewControllers(registry);
registry.addViewController("/ws").setViewName("/ws");
registry.addViewController("/chat").setViewName("/chat");
}
}
3,websocket的配置
package com.jack.config;
/**
* Created by jack on 2017/10/25.
*/
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
/**
* 使用@ServerEndpoint创立websocket endpoint
* 首先要注入 ServerEndpointExporter,
* 这个bean会自动注册使用了 @ServerEndpoint 注
* 解声明的 Web Socket endpoint。
* 要注意,如果使用独立的 Servlet 容器,
* 而不是直接使用 Spring Boot 的内置容器,
* 就不要注入 ServerEndpointExporter,
* 因为它将由容器自己提供和管理
*/
@Configuration
public class WebSocketConfig {
@Bean
public ServerEndpointExporter serverEndpointExporter() {
return new ServerEndpointExporter();
}
}
4,websocket的session获取的配置
package com.jack.config;
import javax.servlet.ServletContext;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionContext;
import javax.websocket.HandshakeResponse;
import javax.websocket.server.HandshakeRequest;
import javax.websocket.server.ServerEndpointConfig;
import java.util.Enumeration;
/**
* Created by jack on 2017/10/25.
*/
/**
* 解决websocket获取不到session的问题
* 参考:http://www.cnblogs.com/jarviswhj/p/4227559.html
* http://www.cnblogs.com/zhaoww/p/5119706.html
*/
public class GetHttpSessionConfigurator extends ServerEndpointConfig.Configurator {
@Override
public void modifyHandshake(ServerEndpointConfig sec, HandshakeRequest request, HandshakeResponse response) {
//super.modifyHandshake(sec, request, response);
HttpSession httpSession = (HttpSession)request.getHttpSession();
//解决httpSession为null的情况
if (httpSession == null){
httpSession = new HttpSession() {
@Override
public long getCreationTime() {
return 0;
}
@Override
public String getId() {
return null;
}
@Override
public long getLastAccessedTime() {
return 0;
}
@Override
public ServletContext getServletContext() {
return null;
}
@Override
public void setMaxInactiveInterval(int i) {
}
@Override
public int getMaxInactiveInterval() {
return 0;
}
@Override
public HttpSessionContext getSessionContext() {
return null;
}
@Override
public Object getAttribute(String s) {
return null;
}
@Override
public Object getValue(String s) {
return null;
}
@Override
public Enumeration getAttributeNames() {
return null;
}
@Override
public String[] getValueNames() {
return new String[0];
}
@Override
public void setAttribute(String s, Object o) {
}
@Override
public void putValue(String s, Object o) {
}
@Override
public void removeAttribute(String s) {
}
@Override
public void removeValue(String s) {
}
@Override
public void invalidate() {
}
@Override
public boolean isNew() {
return false;
}
};
}
sec.getUserProperties().put(HttpSession.class.getName(),httpSession);
}
}
package com.jack.controller;
import com.jack.config.GetHttpSessionConfigurator;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
import javax.websocket.*;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.CopyOnWriteArraySet;
/**
* Created by jack on 2017/10/25.
*/
/**
* websocket的具体实现类
*/
@ServerEndpoint(value = "/websocket",configurator = GetHttpSessionConfigurator.class)
@Component
public class WebSocketServer {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
private static CopyOnWriteArraySet webSocketSet = new CopyOnWriteArraySet();
//与某个客户端的连接会话,需要通过它来给客户端发送数据
private Session session;
/**
* 连接建立成功调用的方法
*/
@OnOpen
public void onOpen(Session session,EndpointConfig config) {
this.session = session;
HttpSession httpSession= (HttpSession) config.getUserProperties().get(HttpSession.class.getName());
System.out.println("name is : "+ httpSession.getAttribute("name"));
//sessionMap.put(session.getId(), session);
System.out.println("the session is : "+session.getId());
System.out.println("session content is : "+session.getId());
System.out.println("httpSession is : "+httpSession.getId());
webSocketSet.add(this); //加入set中
addOnlineCount(); //在线数加1
System.out.println("有新连接加入!当前在线人数为" + getOnlineCount());
try {
//sendMessage(CommonConstant.CURRENT_WANGING_NUMBER.toString());
sendMessage("22222222");
} catch (IOException e) {
System.out.println("IO异常");
}
}
/**
* 连接关闭调用的方法
*/
@OnClose
public void onClose() {
webSocketSet.remove(this); //从set中删除
subOnlineCount(); //在线数减1
System.out.println("有一连接关闭!当前在线人数为" + getOnlineCount());
}
/**
* 收到客户端消息后调用的方法
*
* @param message 客户端发送过来的消息
*/
@OnMessage
public void onMessage(String message, Session session) {
System.out.println("来自客户端的消息:" + message);
System.out.println("onMessage sessionId is : "+session.getId());
//群发消息
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 发生错误时调用
*/
@OnError
public void onError(Session session, Throwable error) {
System.out.println("发生错误");
error.printStackTrace();
}
public void sendMessage(String message) throws IOException {
this.session.getBasicRemote().sendText(message+" : sendMessage id is : "+this.session.getId());
//this.session.getAsyncRemote().sendText(message);
}
/**
* 群发自定义消息
*/
public static void sendInfo(String message) throws IOException {
for (WebSocketServer item : webSocketSet) {
try {
item.sendMessage(message);
} catch (IOException e) {
continue;
}
}
}
public static synchronized int getOnlineCount() {
return onlineCount;
}
public static synchronized void addOnlineCount() {
WebSocketServer.onlineCount++;
}
public static synchronized void subOnlineCount() {
WebSocketServer.onlineCount--;
}
}
6,main/resources/templates/ws.html代码
My WebSocket
Welcome
上面的代码实现了一个websocket作为服务端,html页面作为客户端的一个websocket连接的例子。下面实现websockert作为客户端的功能
二,websocket作为客户端
1,引入jar包:
org.java-websocket
Java-WebSocket
1.3.5
package com.jack.test;
import org.java_websocket.WebSocket;
import org.java_websocket.client.WebSocketClient;
import org.java_websocket.drafts.Draft_6455;
import org.java_websocket.handshake.ServerHandshake;
import java.io.UnsupportedEncodingException;
import java.net.URI;
import java.net.URISyntaxException;
/**
* Created by jack on 2017/10/25.
*/
public class WebsocketClient {
public static WebSocketClient client;
public static void main(String[] args) {
try {
client = new WebSocketClient(new URI("ws://localhost:9090/websocket"),new Draft_6455()) {
@Override
public void onOpen(ServerHandshake serverHandshake) {
System.out.println("打开链接");
}
@Override
public void onMessage(String s) {
System.out.println("收到消息"+s);
}
@Override
public void onClose(int i, String s, boolean b) {
System.out.println("链接已关闭");
}
@Override
public void onError(Exception e) {
e.printStackTrace();
System.out.println("发生错误已关闭");
}
};
} catch (URISyntaxException e) {
e.printStackTrace();
}
client.connect();
System.out.println(client.getDraft());
while(!client.getReadyState().equals(WebSocket.READYSTATE.OPEN)){
System.out.println("还没有打开");
}
System.out.println("打开了");
try {
send("hello world".getBytes("utf-8"));
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
client.send("hello world");
client.close();
}
public static void send(byte[] bytes){
client.send(bytes);
}
}
3,运行测试
1)启动spring boot项目运行websocket的服务端
2)运行
WebsocketClient
类
3)websocket服务输出日志如下:
name is : null
the session is : 1
session content is : 1
httpSession is : null
有新连接加入!当前在线人数为1
来自客户端的消息:hello world
onMessage sessionId is : 1
有一连接关闭!当前在线人数为0
还没有打开
还没有打开
还没有打开
还没有打开
还没有打开
还没有打开
还没有打开
还没有打开
还没有打开
还没有打开
还没有打开
还没有打开
还没有打开
打开链接
打开了
收到消息22222222 : sendMessage id is : 1
收到消息hello world : sendMessage id is : 1
链接已关闭
Process finished with exit code 0
总结:本篇使用另外一种方式实现了websocket的服务端,并在websocket通信中获取到session,要获取session需要先调用:http://localhost:9090/test/testSession这个接口,代码如下:
package com.jack.controller;
import com.alibaba.fastjson.JSONObject;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.Cookie;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
/**
* Created by jack on 2017/10/25.
*/
@RestController
@RequestMapping("/test")
public class TestController {
@RequestMapping(value = "/testSession",method = RequestMethod.GET)
public JSONObject testSession(HttpServletRequest request, HttpServletResponse response){
System.out.println("session test");
HttpSession session = request.getSession();
System.out.println("the session is : "+session.getId());
Cookie[] cookies = request.getCookies();
System.out.println("cookies is : "+JSONObject.toJSON(cookies));
JSONObject json = new JSONObject();
json.put("success", "session test .");
return json;
}
}
用来生成一个session,模拟登入功能以后有一个session的作用。然后使用websocket实现了一个客户端,直接利用客户端连接websocket的服务端。
代码地址:https://github.com/wj903829182/springboot/tree/master/springboot-websocket-chat
还有一种方式实现websocket作为客户端,参考:http://www.cnblogs.com/akanairen/p/5616351.html
上面连接的代码我也集成到https://github.com/wj903829182/springboot/tree/master/springboot-websocket-chat上了,测试的客户端类是:
com.jack.bean.Client.WebsocketClient2
收集的websocket相关的信息:
http://www.cnblogs.com/xiaojf/p/6613822.html
http://www.cnblogs.com/akanairen/p/5616351.html
http://blog.csdn.net/blueblueskyhua/article/details/70807847
http://blog.csdn.net/zzhao114/article/details/60154017
https://segmentfault.com/a/1190000009038991
http://blog.csdn.net/haoyuyang/article/details/53364372
https://segmentfault.com/a/1190000007397316
一种可以实现自定义的websocket的实现,主要给出部分主要的代码,可以获取request的参数,进行一些登入验证,比如session,token,用户名等
package com.qwrt.station.websocket.config;
import com.qwrt.station.websocket.websockethandler.ChatHandshakeInterceptor;
import com.qwrt.station.websocket.websockethandler.ChatMessageHandler;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.socket.config.annotation.EnableWebSocket;
import org.springframework.web.socket.config.annotation.WebSocketConfigurer;
import org.springframework.web.socket.config.annotation.WebSocketHandlerRegistry;
import org.springframework.web.socket.handler.TextWebSocketHandler;
/**
* Created by jack on 2017/10/26.
*/
@Configuration
@EnableWebSocket
public class SpringWebSocketConfig implements WebSocketConfigurer {
/**
* 这个类是配置类,第一个addHandler是对正常连接的配置,
* 第二个是如果浏览器不支持websocket,使用socketjs模拟websocket的连接。
* @param webSocketHandlerRegistry
*/
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry webSocketHandlerRegistry) {
//setAllowedOrigins("*")解决跨越问题
webSocketHandlerRegistry.addHandler(chatMessageHandler(),"/webSocketServer").addInterceptors(new ChatHandshakeInterceptor()).setAllowedOrigins("*");
webSocketHandlerRegistry.addHandler(chatMessageHandler(), "/sockjs/webSocketServer").addInterceptors(new ChatHandshakeInterceptor()).setAllowedOrigins("*").withSockJS();
}
@Bean
public TextWebSocketHandler chatMessageHandler(){
return new ChatMessageHandler();
}
}
package com.qwrt.station.websocket.websockethandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.server.ServerHttpRequest;
import org.springframework.http.server.ServerHttpResponse;
import org.springframework.http.server.ServletServerHttpRequest;
import org.springframework.web.socket.WebSocketHandler;
import org.springframework.web.socket.server.support.HttpSessionHandshakeInterceptor;
import javax.servlet.http.HttpSession;
import java.util.Map;
/**
* Created by jack on 2017/10/26.
*/
public class ChatHandshakeInterceptor extends HttpSessionHandshakeInterceptor {
private static final Logger logger = LoggerFactory.getLogger(ChatHandshakeInterceptor.class);
@Override
public boolean beforeHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Map attributes) throws Exception {
logger.debug("beforeHandshake......Before Handshake");
if (request instanceof ServletServerHttpRequest) {
ServletServerHttpRequest servletRequest = (ServletServerHttpRequest) request;
String topic = servletRequest.getServletRequest().getParameter("topic");
HttpSession session = servletRequest.getServletRequest().getSession(false);
if (session == null){
logger.error("session is null ,cant not connection websocket,to do login");
return false;
}else {
logger.debug("beforeHandshake ---sessionId = "+session.getId()+" ,topic = "+topic);
}
//logger.debug("beforeHandshake ---sessionId = "+session.getId()+" ,topic = "+topic);
attributes.put("topic", topic);
}
return super.beforeHandshake(request, response, wsHandler, attributes);
}
@Override
public void afterHandshake(ServerHttpRequest request, ServerHttpResponse response, WebSocketHandler wsHandler, Exception ex) {
logger.debug("afterHandshake......进入After Handshake方法");
super.afterHandshake(request, response, wsHandler, ex);
}
}
package com.qwrt.station.websocket.websockethandler;
import com.alibaba.fastjson.JSONObject;
import com.qwrt.station.api.vo.authority.SessionInfo;
import com.qwrt.station.websocket.util.WebsocketHandlerMessage;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.socket.CloseStatus;
import org.springframework.web.socket.PongMessage;
import org.springframework.web.socket.TextMessage;
import org.springframework.web.socket.WebSocketSession;
import org.springframework.web.socket.handler.TextWebSocketHandler;
import java.io.IOException;
import java.util.Date;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
/**
* Created by jack on 2017/10/26.
*/
public class ChatMessageHandler extends TextWebSocketHandler {
//静态变量,用来记录当前在线连接数。应该把它设计成线程安全的。
private static int onlineCount = 0;
//public static final ArrayList users;// 这个会出现性能问题,最好用Map来存储,key用userid
//concurrent包的线程安全Set,用来存放每个客户端对应的MyWebSocket对象。
//public static final CopyOnWriteArraySet WEBSOCKET_SESSION = new CopyOnWriteArraySet();
public static final ConcurrentMap WEBSOCKET_SESSION = new ConcurrentHashMap<>();
public static final ConcurrentMap> USER_MSG_SESSION = new ConcurrentHashMap<>();
//public static final ConcurrentMap> USER_MSG_WEBSOCKET_SESSIONIDS = new ConcurrentHashMap<>();
private static Logger logger = LoggerFactory.getLogger(ChatMessageHandler.class);
//初始化时间
private static Long oldTime = new Date().getTime();
private static int minute = 10;
/**
* 连接成功时候,会触发UI上onopen方法
*/
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
logger.debug("connect to the websocket success......sessionId="+session.getId());
logger.debug("session.getAttributes = "+JSONObject.toJSON(session.getAttributes()));
WEBSOCKET_SESSION.put(session.getId(),session);
}
/**
* 在UI在用js调用websocket.send()时候,会调用该方法
*/
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
logger.debug("handleTextMessage........message is : "+message.getPayload());
}
/**
* 处理传输信息错误的时候,进行的处理
* @param session
* @param exception
* @throws Exception
*/
@Override
public void handleTransportError(WebSocketSession session, Throwable exception) throws Exception {
logger.debug("handleTransportError........"+JSONObject.toJSON(session.getAttributes()));
}
/**
*在websocket连接关闭的时候进行的处理
* @param session
* @param closeStatus
* @throws Exception
*/
@Override
public void afterConnectionClosed(WebSocketSession session, CloseStatus closeStatus) throws Exception {
logger.error("websocket connection closed......");
}
@Override
public boolean supportsPartialMessages() {
return false;
}
public static synchronized int getOnlineCount() {
return WEBSOCKET_SESSION.size();
}
/*public static synchronized void addOnlineCount() {
ChatMessageHandler.onlineCount++;
}
public static synchronized void subOnlineCount() {
ChatMessageHandler.onlineCount--;
}*/
/**
* 移除用户信息和对应的session
* @param session
*/
public static void removeUserSessionIdMsg(WebSocketSession session){
}
/**
* 一个用户名可能有多个websocket连接的sessionId,需要进行保存
* @param userName
* @param sessionId
*/
public void saveUserSessionIdMsg(String userName,String sessionId){
..... session num = "+WEBSOCKET_SESSION .size()+" ,username num = : "+USER_MSG_SESSION.size()+" ,username = "+userName+" ,websocketSessionIdCount = "+sessionMap.size());
}
@Override
protected void handlePongMessage(WebSocketSession session, PongMessage message) throws Exception {
super.handlePongMessage(session, message);
logger.debug("handlePongMessage...................");
}
/**
* 更新时间标志,移除在10分钟内已经关闭了的websocketSession
*/
private static void updateTimeAndRemoveWebsocketSessionId(){
}
}