websocket的原理,前台/后台的实现就不说了,现在说一个应用过程中的问题,首先来看几个问题的引出 。
1. 页面初始化
var Socket = new WebSocket(url, [protocol] );
这样会在服务端开启一个线程来相应websocket请求,高性能,速度快,多例.像struts2的action,每个请求就会开启一个线程来处理.
2. 服务端的 onOpen onClose onMessage方法再特定条件下回调用,看一下onMessage方法,客户端调用socket.send(message)时会调用onMesage,如果要保持长连接,简单的做法是在onMessage方法内用一个while死循环来做.
3.如上1所说,响应是多例的,那么如何广播消息到客户端呢?通常用一个static的map来保存相应类,这样即使在同类不同对象的情况下,依然能访问到众多服务端处理类,可以实现广播了。
综合以上三点, 我现在要实现一个广播功能,也就是客户端和服务端用长连接while死循环的方式,如果有个糟糕的客户,在同一个页面不停的刷新刷新再刷新,会出现什么问题?
不停刷新,while循环不能退出,线程不断增加,严重影响服务端性能。
解决方法:
目前的解决办法是一种补偿机制, 简单的来说就是在服务端想客户端广播消息时,如果发生socket write Exception则在static map里移除发生异常的服务响应对象同时赋值为null,这样在下次广播室,无效的服务器响应异常就不存在了
onMessage
{
while(true) {
for item: map {
try{
item.sendMessage("hello!");
} catch(exception ex) {
map.remove(item);item=null;
}
}
}
}
经过这样的代码,实际上并不能减轻后期服务器性能,原因在于对象的while循环未结束, gc不会销毁对象收集内存.
问题:对象的方法未执行结束,能否销毁对象? 经过测试,答案是不能的
先看一个例子:
public class ThreadTest implements Runnable{
String name;
public ThreadTest(){}
public ThreadTest(String s) {
name = s;
}
public void run() {
int i = 1;
while(true) {
System.out.println(name + "->" +i++);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
//main方法:
package com.ftl;
public class TestMain {
/**
* @param args
* @throws InterruptedException
*/
public static void main(String[] args) throws InterruptedException {
ThreadTest t1 = new ThreadTest("t1");;
ThreadTest t2 = new ThreadTest("t2");
ThreadTest t3 = new ThreadTest("t3");
new Thread(t1).start();
new Thread(t2).start();
new Thread(t3).start();
int i = 1;
while(i < 20) {
System.out.println("main->"+i++);
if(i == 10) {
t1 = null;
t2 = null;
t3 = null;
}
System.out.println(t1);
System.out.println(t2);
System.out.println(t3);
Thread.sleep(1000);
}
}
}
可以看到 在代码里我们把t1 t2 t3 值为 null , 但是线程的while循环依然在执行 即使 t1:null t2:null t3:null ,这就说明对象即使=null, gc也不会立即销毁对象.
遇到这样的情况怎么办? 这样在websocket长连接还是会有性能影响
简单的办法就是给个while结束的条件,增加个标识.
看主要代码:
public class ThreadTest implements Runnable{
String name;
Boolean isRun = true;
public ThreadTest(){}
public ThreadTest(String s) {
name = s;
}
public void run() {
int i = 1;
while(true && isRun) {
System.out.println(name + "->" +i++);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public void setIsRun(boolean isRun) {
this.isRun = isRun;
}
}
//增加isRun来判断是否该结束.
public static void main(String[] args) throws InterruptedException {
ThreadTest t1 = new ThreadTest("t1");;
ThreadTest t2 = new ThreadTest("t2");
ThreadTest t3 = new ThreadTest("t3");
new Thread(t1).start();
new Thread(t2).start();
new Thread(t3).start();
int i = 1;
while(i < 20) {
System.out.println("main->"+i++);
if(i == 10) {
t1.setIsRun(false);
t2.setIsRun(false);
t3.setIsRun(false);
}
System.out.println(t1);
System.out.println(t2);
System.out.println(t3);
Thread.sleep(1000);
}
}
可以看到,这样就成果结束对象的执行,当然gc会在适当的时候销毁对象,这个我们不用管了,想管也管不住 0~ !!~0
回到我们之前的话题,那段代码怎么改?
onMessage
{
while(true) {
for item: map {
try{
if(item.getIsRun() == true) {
item.sendMessage("hello!");
} else {
//这个服务端websocket对象需要gc销毁了
//直接break, 结束循环
break;
}
} catch(exception ex) {
item.setIsRun(false);
map.remove(item)
}
}
}
}
最后这段代码假象一下现在又10个服务器websocket对象在运行while循环,但是只有一个页面,原因是由于客户刷新了页面9次,但是真正有有意义并且保持长连接的socket只有一个,那么在sendMessage时,会发生异常,因为socket write失败,这时我们抓住异常,确定是哪个服务端websocket对象,然后map移除,isRun=false,这样在下次循环时,isRun=FALSE,那么则会break,while结束,onMessage结束,gc负责销毁。 经过实测,没问题。
有不对的地方请指出,谢谢!!