小编最近在使用WebSocket实现小游戏交互,虽然简单的游戏很简单,但要实现游戏中双方的交互效果能够实时展现,达到完美的情况花费了不少时间精力,中间会遇到客户端问题,断网问题,以及客户端链接问题,在此,小编将这些日子所积累的经验发布在此博客,欢迎大家留言讨论。
Java 部分:
引入依赖:
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>5.3.29</version>
</dependency>
创建WebSocket程序启动文件 WebSocketConfig
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;
@Component
public class WebSocketConfig{
@Bean
public ServerEndpointExporter serverEndpointExporter() {
System.out.println("启动websocket支持");
return new ServerEndpointExporter();
}
}
2.创建WebSocket核心服务文件:CommonWebSocketService
import com.alibaba.fastjson.JSONObject;
import com.haoyue.rabbitmq.MsgProducer;
import com.haoyue.util.data.DataService;
import com.haoyue.util.string.StringEncryptUtil;
import com.haoyue.util.string.StringTimeUtil;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
@EnableScheduling
@ServerEndpoint("/api/webSocket")
public class CommonWebSocketService {
private Session session;
@Autowired
private DataService dataService;
@Autowired
public MsgProducer msgProducer;
/**
* 使用线程安全的Map存储 用于存储WebSocket链接id与Session的映射关系
* LinkedList 是双向链表 有序数据集合 List是无序数据集合
* ConcurrentHashMap 是线程安全的MAp 确保多线程使用的时候 这个变量不会出问题
*/
public static final ConcurrentHashMap<String, CommonWebSocketService> sessions_data = new ConcurrentHashMap<>();
public static final LinkedList<ConcurrentHashMap<String, Object>> sessions_connect = new LinkedList<>();
/**
*此定时器用于每间隔30秒给所有客户端发送心跳续连包,因为WebSocket长时间未交互会发生自动断开链接
*/
@Scheduled(initialDelay = 5000, fixedRate = 30000)
public void websocketz_heartbeat() throws InterruptedException {
try {
System.gc();
JSONObject heartbeatpack=new JSONObject();
heartbeatpack.put("type","heartbeat");
heartbeatpack.put("content","");
heartbeatpack.put("code","200");
send_to_Message_all(heartbeatpack.toJSONString());
}catch (Exception e){
String error_title=e.getMessage();
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
String exceptionStackTrace = sw.toString();
log.info("\r\n心跳续连包 发送异常:"+error_title+"----------\n"+exceptionStackTrace);
}
}
/**
* 此定时器用于判断前端发来的心跳,长时间未收到心跳,则移除此客户端链接
*/
@Scheduled(initialDelay = 5000, fixedRate = 40000)
public void applets_heartbeat(){
try {
for (int i = 0; i < sessions_connect.size(); i++) {
if(sessions_connect.get(i)==null){sessions_connect.remove(i);continue;}
int isstate=Integer.parseInt(sessions_connect.get(i).get("isstate")+"");
//移除离线客户端
if(isstate>1){
String linkeid=sessions_connect.get(i).get("linkeid")+"";
sessions_data.remove(linkeid); sessions_connect.remove(i);
System.out.println(linkeid+":websocket已离线");
}
}
for (int i = 0; i < sessions_connect.size(); i++){
int isstate=Integer.parseInt(sessions_connect.get(i).get("isstate")+"");
isstate=isstate+1; sessions_connect.get(i).put("isstate",isstate);
}
}
catch (Exception e){
String error_title=e.getMessage();
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
String exceptionStackTrace = sw.toString();
log.info("\r\nwebsocket链接在线判断处理异常:"+error_title+"----------\n"+exceptionStackTrace);
}
}
//建立前端与后端的链接
@OnOpen
public void onOpen(Session session,@PathParam("param")String param) throws InterruptedException {
try {
this.session = session;
String canshu = session.getQueryString();
if (!StringEncryptUtil.Is_Null(canshu) && canshu.split("=").length == 2) {
String linkid = canshu.split("=")[1];//客户端id,小编这里采用以微信用户的id作为客户端链接的唯一标识,实现每个小程序用户都会有独立的WebSocket链接
//原有的客户端链接已失效 移除原有的客户端链接
for (int i = 0; i < sessions_connect.size(); i++) {
if(sessions_connect.get(i)==null){sessions_connect.remove(i);continue;}
if(sessions_connect.get(i).get("linkid").equals(linkid)){sessions_connect.remove(i);}
}
sessions_data.put(linkid,this);
ConcurrentHashMap<String, Object> connect = new ConcurrentHashMap<>();
connect.put("linkid",linkid); connect.put("isstate",0); sessions_connect.add(connect);
Thread.sleep(4000);//延迟四秒
//发送消息给小程序 链接成功
JSONObject senddata = new JSONObject();
senddata.put("websocket_id",linkid);
senddata.put("type","java_websocketsuccess");
senddata.put("content","链接成功");
senddata.put("code","200");
send_to_Message(senddata.toJSONString());
}
log.info("\r\n【websocket消息】有新的连接, 独立链接总数:{}", sessions_connect.size());
}catch (Exception e){
System.out.println(e);
}
}
//关闭前端与后端的链接
@OnClose
public void onClose(Session session,@PathParam("param")String param) {
try{
String canshu=session.getQueryString();
if(!StringEncryptUtil.Is_Null(canshu)&&canshu.split("=").length==2){
String linkid=canshu.split("=")[1]; sessions_data.remove(linkid);
for (int i = 0; i < sessions_connect.size(); i++) {
if(sessions_connect.get(i)==null){sessions_connect.remove(i);continue;}
if(sessions_connect.get(i).get("linkid").equals(linkid)){
sessions_connect.remove(i);
}
}
}
log.info("\r\n【websocket消息】连接断开, 独立链接总数:{}", sessions_connect.size());
}catch (Exception e){
String error_title=e.getMessage();
StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw);
e.printStackTrace(pw);
String exceptionStackTrace = sw.toString();
log.info("\r\nwebsocket链接关闭异常 :"+error_title+"----------\n"+exceptionStackTrace);
}
}
//接收前端向WebSocket服务发来的消息 由于后端WebSocket带有注解的方法均属于静态方法,小编也在此进行小程序的游戏数据交互和前端的心跳接收
@OnMessage
public void onMessage(String message) throws InterruptedException {
try{
JSONObject dataall=JSONObject.parseObject(message);
log.info("【websocket消息】收到客户端发来的消息:"+dataall.get("type"));
if(dataall.get("type").equals("java_topgameingdata")){
//发送小程序游戏用户操作数据给游戏的对方
dataall.put("type","applets_gameinprogress");
dataall.put("websocket_id",dataall.get("rival_id"));
dataall.remove("rival_id");
send_to_Message(dataall.toJSONString());
}
else if(dataall.get("type").equals("applets_heartbeat")){
//接收小程序的心跳,用于更新WebSocket客户端的在线状态
String linkid=dataall.get("linkid")+"";
for (int i = 0; i < sessions_connect.size(); i++) {
if(sessions_connect.get(i).get("linkid").equals(linkid)){ sessions_connect.get(i).put("isstate",0);}
}
}
}catch (Exception e){
System.out.println("消息异常:"+e);
}
}
/**
* 配置错误信息处理
* @param session
* @param t
*/
@OnError
public void onError(Session session, Throwable t) {
log.info("\r\n【websocket出现错误】:"+t.getMessage()+"------------------------"+t);
// 打印异常的堆栈跟踪
t.printStackTrace();
}
//发送websocket消息
public Map<String,Object> send_to_Message(String message) throws InterruptedException {
JSONObject data=JSONObject.parseObject(message);
String id="";
//这里对WebSocket发送的消息做了持久化处理,当用户发生断线重连时,可以写一个接口,将重要的未发送的消息进行重新发送,但几个消息除外
//applets_gameinprogress 游戏运行数据类型 由于游戏运行中的交互属于静态交互,第一,不好使用@Autowired进行调方法,第二,为了优化游戏的运行性能,所以不用进行持久化
//java_websocketsuccess和heartbeat均属于不重要的消息,所以不必要进行持久化
if(!data.get("type").equals("applets_gameinprogress")&&!data.get("type").equals("java_websocketsuccess")&&!data.get("type").equals("heartbeat")){
if(StringEncryptUtil.Is_Null(data.get("base_websocket_id")+"")){
//新增websocket消息放入数据库 持久化
Map<String,Object> insert=new HashMap<>();
insert.put("websocket_id",data.get("websocket_id")); insert.put("type",data.get("type"));
insert.put("content",message); insert.put("issend",0);
insert.put("time_date", StringTimeUtil.now());
id=dataService.dataInsertUtil.Insert("base_websocket",insert);
data.put("base_websocket_id",id);
}
else{
id=data.get("base_websocket_id")+"";
data.put("base_websocket_id",id);
}
}
boolean issended=false;
try{
JSONObject sendstr=new JSONObject(data);
if(StringEncryptUtil.Is_Null(data.get("websocket_id")+"")){return null;}
CommonWebSocketService webSocket=sessions_data.get(data.get("websocket_id"));
if(webSocket!=null){
try{ send(webSocket,sendstr.toJSONString());issended=true; }
catch (Exception r){
//当消息发送异常时候 移除客户端链接
sessions_data.remove(data.get("websocket_id"));
for (int j = 0; j < sessions_connect.size(); j++) {
if(sessions_connect.get(j).get("linkid").equals(data.get("websocket_id"))){sessions_connect.remove(j);}
}
String error_title=r.getMessage(); StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw); r.printStackTrace(pw); String exceptionStackTrace = sw.toString();
log.info("\r\nwebsocket独立消息发送异常:"+error_title+"----------\n"+exceptionStackTrace);
}
}
if(!issended){ log.info("\r\n【websocket】独立消息类型:"+data.get("type")+"----"+data.get("websocket_id")+":未连接");}
}
catch (Exception e){
e.printStackTrace();
log.info("\r\nwebsocket异常信息是:"+e);
}
if(issended){ log.info("\r\n【websocket独立消息】"+data.get("type")+"发送完成----"+data.get("websocket_id"));}
Map<String,Object> result=new HashMap<>();
result.put("issend",issended);
result.put("base_websocket_id",id);
return result;
}
//此方法用于给所有客户端发送全体消息,比如游戏大厅的游戏房间刷新
public void send_to_Message_all(String message) throws InterruptedException {
JSONObject data=JSONObject.parseObject(message); data.put("websocket_id","all"); boolean issended=false;
try{
System.out.println("发送全部链接消息 独立链接总数"+sessions_connect.size());
String id="";
if(StringEncryptUtil.Is_Null(data.get("base_websocket_id")+"")&&!data.get("type").equals("heartbeat")){
Map<String,Object> insert=new HashMap<>();
insert.put("websocket_id",data.get("websocket_id")); insert.put("type",data.get("type"));
insert.put("content",message); insert.put("issend",0);
insert.put("time_date", StringTimeUtil.now());
id=dataService.dataInsertUtil.Insert("base_websocket",insert);
data.put("base_websocket_id",id);
}
for (Map.Entry<String, CommonWebSocketService> e:sessions_data.entrySet()) {
if(e.getValue()==null){ sessions_data.remove(e.getKey()); continue; }
CommonWebSocketService webSocket=e.getValue();
System.out.println(e.getKey()+" websocket链接发送数据类型:"+data.get("type"));
try { send(webSocket,message);issended=true; }
catch (Exception r){
sessions_data.remove(e.getKey());
for (int j = 0; j < sessions_connect.size(); j++) {
if(sessions_connect.get(j).get("linkid").equals(e.getKey())){sessions_connect.remove(j);}
}
String error_title=r.getMessage(); StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw); r.printStackTrace(pw); String exceptionStackTrace = sw.toString();
log.info("\r\nwebsocket独立消息全部 发送异常:"+error_title+"----------\n"+exceptionStackTrace);
}
if(!data.get("type").equals("heartbeat")){
//发送异步综合RabbitMQ消息 将持久化的websocket消息改为已发送
Map<String,Object> senddata=new HashMap<>();
senddata.put("type","java_websocketissend");
senddata.put("data",id);
JSONObject rabbitmqdata = new JSONObject(senddata);
msgProducer.send_zongheDirectExchange("zonghe_shop_activity", rabbitmqdata.toJSONString());
}
}
if(!issended){ log.info("\r\n【websocket】全部独立消息类型:"+data.get("type")+"----"+data.get("websocket_id")+":未连接");}
}
catch (Exception e){
e.printStackTrace();
log.info("\r\nwebsocket发送全部消息,发送数据为:"+data.get("type")+"----websocket链接:"+data.get("websocket_id"));
log.info("\r\nwebsocket发送全部消息异常是:"+e);
}
if(issended){
log.info("\r\n【websocket发送全部独立消息】"+data.get("type")+"发送完成----"+(StringEncryptUtil.Is_Null(data.get("websocket_id")+"")==true?"":data.get("websocket_id")));
}
}
//这里注意,必须要加synchronized锁,因为真实环境中极有可能出现高并发的情况,如果不加锁会经常出现异常
public synchronized void send(CommonWebSocketService webSocket,String message){
JSONObject data=JSONObject.parseObject(message);
try {
webSocket.session.getAsyncRemote().sendText(message);
}
catch (Exception e){
String error_title=e.getMessage(); StringWriter sw = new StringWriter();
PrintWriter pw = new PrintWriter(sw); e.printStackTrace(pw); String exceptionStackTrace = sw.toString();
log.info("\r\nwebsocket消息发送异常:"+error_title+"----------\n"+exceptionStackTrace);
}
}
}
以上就是Java的全部部分,下面是微信小程序部分,小编是采用的uniapp开发的小程序。
首先,需要用户在进入小程序的时候就立即建立WebSocket链接,所以在APP.vue中的onLunch写入
小程序部分:
App.vue:
onLaunch: async function() {
await this.opwebsocket();//建立链接
await this.setthat();//启动前端向后端发送心跳
console.log('App Launch');
},
globalData:{
sockets:null,//保存客户端链接
networkvalue:5,
},
methods:{
//链接websocket并打开onMessage
async opwebsocket(){
//小编做了当用户登录成功以后,会执行一次此方法,读取缓存中的用户id,建立独立的客户端链接
let base_user_id=uni.getStorageSync("base_user_id");
if(base_user_id==undefined||base_user_id==""||base_user_id==null||base_user_id=="undefined"||base_user_id=="null"){base_user_id="";}
//http请求使用ws,https请求使用wss
let websocketurl="ws://本机ip:端口号/api/webSocket?linkId="+base_user_id;
this.globalData.sockets = await uni.connectSocket({
url: websocketurl,
success: (rs) => { console.log('WebSocket 连接'); },
});
await message.websocketopen();//打开客户端收消息
},
async starttimer(){ await this.message.timer(); },
//websocket链接成功,收到连接成功的消息,请求接口发送所有未发送的消息
async connectSuccess(){
let that=this;console.log("链接成功");
},
//websocket收到消息,请求接口此消息已读
async connectrecive(id){
let that=this;
},
}
小程序的第二个websocket核心处理文件,message.js
export var isheart=0;//用于记录后端服务向前端发的心跳
export var isthat=false;//用于判断that是否已经指向了小程序页面
export var appletsheartbeat=null;//小程序端向后端服务发送心跳定时器
export var websocketlistener=null;//监听后端服务发送心跳定时器
export var networkresult=null;//记录网络连接变化的结果
export var listener=null;//listener用于记录网络变化的函数
export var that;//用于指向各个页面的this
//网络变化监听
export function networklisten(){
if(networkresult==null){
listener = function (res) {
networkresult=res;
if(!networkresult.isConnected){getApp().globalData.networkvalue=0;uni.showToast({ title: "网络断开", icon: "error" }); }
if(networkresult.networkType=="none"||networkresult.networkType=="unknown"){getApp().globalData.networkvalue=0;}
if(networkresult.isConnected||(networkresult.networkType=="4g"||networkresult.networkType=="5g")){
if(getApp().globalData.networkvalue==0){
stopnetworklisten(listener);
that.resetwebsocket();
setTimeout(function(){uni.showToast({ title: "网络已连接", icon: "success" });},3000);
}
}
}
startnetworklisten(listener);
}
}
//开启网络监听
export function startnetworklisten(listene){
uni.onNetworkStatusChange(listene);
}
//移除上一次的网络监听
export function stopnetworklisten(listene){
uni.offNetworkStatusChange(listene);
setTimeout(function(){startnetworklisten(listene);},3000);
}
export function websocketopen() {
let socket=getApp().globalData.sockets;
socket.onMessage(event => {
let dataall=JSON.parse(event.data);let base_user_id=uni.getStorageSync("base_user_id");
console.log("websocket消息类型:"+dataall.type);
if(dataall.type=="heartbeat"){isheart=0;}
if(dataall.type=="java_websocketsuccess"){ getApp().connectSuccess(); }//告诉后端连接成功
//告诉后端消息已读,同意过滤掉心跳、连接成功消息和全体消息
if(dataall.type!="heartbeat"&&dataall.type!="java_websocketsuccess"&&dataall.base_websocket_id!="all"){ getApp().connectrecive(dataall.base_websocket_id); }
//用户当前所在页面
let currentPagePath = getCurrentPages().pop().$page.fullPath;
//后续的各种消息类型就按照需求做即可,这里就不做过多展示了
});
//socket关闭事件
socket.onClose(event => {
socket.close();clearInterval(appletsheartbeat);
if(!getApp().globalData.sockets==null){
getApp().globalData.sockets.close();clearInterval(appletsheartbeat);
getApp().globalData.sockets=null;
}
setTimeout(function(){console.log("websocket断线重连中:"+JSON.stringify(event));websocketconnectreset();},500);
});
}
//向websocket服务端发送消息
export function websocketsend(data) {
let socket=getApp().globalData.sockets;
if(socket!=null){ socket.send({ data: data}); }
}
//重新链接websocket
export async function websocketconnectreset(){
getApp().opwebsocket(that);isheart=0;setTimeout(function(){ that.websocket(); },500);
}
//websocket30秒心跳监听
export async function websocketlisten() {
if(websocketlistener==null){
websocketlistener=setInterval(function(){
isheart=isheart+1;
console.log("心跳值:"+isheart);
if(isheart>2){
console.log("websocket未收到心跳,重连中");clearInterval(appletsheartbeat);
if(getApp().globalData.sockets!=null){
getApp().globalData.sockets.close();clearInterval(appletsheartbeat);
if(getApp().globalData.sockets!=null){getApp().globalData.sockets=null;}
setTimeout(function(){websocketconnectreset();isheart=0;},500);
}
}
},30000);
}
}
//赋值指向this
export function websocketthat(_that) { that=_that;isthat=true;}
export function timer(){
let data={};data.type="applets_heartbeat"; data.linkid=uni.getStorageSync("base_user_id");
appletsheartbeat=setInterval(function(){websocketsend(JSON.stringify(data));},40000);//每隔40秒向后台发送心跳
}
以上就是小程序链接后端WebSocket服务的核心两个文件
小编还在小程序的各个页面做了一些方法,每个页面都需要做的
async onLoad() {
await this.websocket();
}
methods:{
//网络波动后的重新链接
resetwebsocket(){
let that=this;
if(getApp().globalData.networkvalue==0){ getApp().globalData.networkvalue=5;
setTimeout(function(){ console.log("网络波动后启动重连!"); that.message.websocketconnectreset(); },1000);
}
},
//赋值指向this
async websocket(){ await this.message.websocketthat(this); },
}
经验总结:
小编websocket经常出现的两种异常:第一种:
websocket独立消息发送异常:The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method
java.lang.IllegalStateException: The remote endpoint was in state [TEXT_FULL_WRITING] which is an invalid state for called method
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.checkState(WsRemoteEndpointImplBase.java:1274)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase$StateMachine.textStart(WsRemoteEndpointImplBase.java:1236)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.sendStringByCompletion(WsRemoteEndpointImplBase.java:213)
at org.apache.tomcat.websocket.WsRemoteEndpointImplBase.sendStringByFuture(WsRemoteEndpointImplBase.java:201)
at org.apache.tomcat.websocket.WsRemoteEndpointAsync.sendText(WsRemoteEndpointAsync.java:53)
at com.haoyue.common.service.CommonWebSocketService.send(CommonWebSocketService.java:326)
at com.haoyue.common.service.CommonWebSocketService.send_to_Message(CommonWebSocketService.java:236)
at com.haoyue.special.screen.service.BigScreenService.bascdata(BigScreenService.java:153)
at com.haoyue.special.screen.controller.BigScreenController.ranking(BigScreenController.java:29)
at sun.reflect.GeneratedMethodAccessor99.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:205)
at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:150)
at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:117)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1070)
at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
at org.springframework.web.servlet.FrameworkServlet.doPost(FrameworkServlet.java:909)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:681)
at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:117)
at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:541)
at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:360)
at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:399)
at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:890)
at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1789)
at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
at java.lang.Thread.run(Thread.java:748)
此异常在网络上有很多处理方法,小编这里就不做过多解释了。
null------------------------java.io.EOFException
具体详细的异常暂时找不到了,此异常按照小编个人的经验理解来看,主要就是客户端链接的异常,客户端在做网络波动、断线重连,以及小程序关闭重新打开后的链接一定需要注意此异常,目前,对重连的地方做好精确的把控。
以上就是小编的全部分享,欢迎大家留言讨论