Java使用WebSocket实现即时通信,微信简易小游戏交互机制,经验总结

小编最近在使用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

具体详细的异常暂时找不到了,此异常按照小编个人的经验理解来看,主要就是客户端链接的异常,客户端在做网络波动、断线重连,以及小程序关闭重新打开后的链接一定需要注意此异常,目前,对重连的地方做好精确的把控。

以上就是小编的全部分享,欢迎大家留言讨论

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
是一种在单个TCP连接上进行全双工通信的协议,它可以在客户端和服务器之间建立实时通信,而Java-WebSocket是一个开源的Java库,用于在Java应用程序中实现WebSocket协议。使用Java-WebSocket,您可以轻松地实现WebSocket客户端和服务器端,从而实现即时通信。 以下是一个简单的Java-WebSocket示例,演示如何在Java实现WebSocket即时通信: ```java import org.java_websocket.WebSocket; import org.java_websocket.handshake.ClientHandshake; import org.java_websocket.server.WebSocketServer; import java.net.InetSocketAddress; public class MyWebSocketServer extends WebSocketServer { public MyWebSocketServer(int port) { super(new InetSocketAddress(port)); } @Override public void onOpen(WebSocket webSocket, ClientHandshake clientHandshake) { System.out.println("WebSocket opened: " + webSocket.getRemoteSocketAddress()); } @Override public void onClose(WebSocket webSocket, int i, String s, boolean b) { System.out.println("WebSocket closed: " + webSocket.getRemoteSocketAddress()); } @Override public void onMessage(WebSocket webSocket, String s) { System.out.println("WebSocket message received: " + s); webSocket.send("Server received message: " + s); } @Override public void onError(WebSocket webSocket, Exception e) { System.out.println("WebSocket error: " + e.getMessage()); } public static void main(String[] args) { MyWebSocketServer server = new MyWebSocketServer(8080); server.start(); System.out.println("WebSocket server started on port: " + server.getPort()); } } ``` 在此示例中,我们创建了一个WebSocket服务器,并实现了四个回调方法:onOpen,onClose,onMessage和onError。当WebSocket连接打开时,onOpen方法将被调用,当WebSocket连接关闭时,onClose方法将被调用,当WebSocket接收到消息时,onMessage方法将被调用,当WebSocket发生错误时,onError方法将被调用。在onMessage方法中,我们简单地将接收到的消息发送回客户端。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

吾疾唯君医

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值