java中websocket使用实例解读-亚博电竞手机版
介绍
现在很多网站为了实现即时通讯,所用的技术都是轮询(polling)。轮询是在特定的的时间间隔(如每1秒),由浏览器对服务器发出http request,然后由服务器返回最新的数据给客服端的浏览器。
这种传统的http request 的模式带来很明显的缺点 – 浏览器需要不断的向服务器发出请求,然而http request 的header是非常长的,里面包含的数据可能只是一个很小的值,这样会占用很多的带宽。
而最比较新的技术去做轮询的效果是comet – 用了ajax。但这种技术虽然可达到全双工通信,但依然需要发出请求。
在 websocket api,浏览器和服务器只需要要做一个握手的动作,然后,浏览器和服务器之间就形成了一条快速通道。两者之间就直接可以数据互相传送。
运行环境:
客户端
实现了websocket的浏览器
chrome | supported in version 4 |
firefox | supported in version 4 |
internet explorer | supported in version 10 |
opera | supported in version 10 |
safari | supported in version 5 |
服务端
依赖
tomcat 7.0.47以上 j2ee7
org.apache.tomcat tomcat-websocket-api 7.0.47 provided javax javaee-api 7.0 provided
注意:早前业界没有统一的标准,各服务器都有各自的实现,现在j2ee7的jsr356已经定义了统一的标准,请尽量使用支持最新通用标准的服务器。
详见:
http://www.oracle.com/technetwork/articles/java/jsr356-1937161.html
http://jinnianshilongnian.iteye.com/blog/1909962
我是用的tomcat 7.0.57 java7
必须是tomcat 7.0.47以上
详见:http://www.iteye.com/news/28414
ps:最早我们是用的tomcat 7自带的实现,后来要升级tomcat 8,结果原来的实现方式在tomcat 8不支持了,就只好切换到支持websocket 1.0版本的tomcat了。
主流的java web服务器都有支持jsr365标准的版本了,请自行google。
用nginx做反向代理的需要注意啦,socket请求需要做特殊配置的,切记!
tomcat的处理方式建议修改为nio的方式,同时修改连接数到合适的参数,请自行google!
服务端不需要在web.xml中做额外的配置,tomcat启动后就可以直接连接了。
实现
import com.dooioo.websocket.utils.sessionutils; import org.apache.commons.logging.log; import org.apache.commons.logging.logfactory; import javax.websocket.*; import javax.websocket.server.pathparam; import javax.websocket.server.serverendpoint; /** * 功能说明:websocket处理类, 使用j2ee7的标准 * 切忌直接在该连接处理类中加入业务处理代码 * 作者:liuxing(2014-11-14 04:20) */ //relationid和usercode是我的业务标识参数,websocket.ws是连接的路径,可以自行定义 @serverendpoint("/websocket.ws/{relationid}/{usercode}") public class websocketendpoint { private static log log = logfactory.getlog(websocketendpoint.class); /** * 打开连接时触发 * @param relationid * @param usercode * @param session */ @onopen public void onopen(@pathparam("relationid") string relationid, @pathparam("usercode") int usercode, session session){ log.info("websocket start connecting: " sessionutils.getkey(relationid, usercode)); sessionutils.put(relationid, usercode, session); } /** * 收到客户端消息时触发 * @param relationid * @param usercode * @param message * @return */ @onmessage public string onmessage(@pathparam("relationid") string relationid, @pathparam("usercode") int usercode, string message) { return "got your message (" message ").thanks !"; } /** * 异常时触发 * @param relationid * @param usercode * @param session */ @onerror public void onerror(@pathparam("relationid") string relationid, @pathparam("usercode") int usercode, throwable throwable, session session) { log.info("websocket connection exception: " sessionutils.getkey(relationid, usercode)); log.info(throwable.getmessage(), throwable); sessionutils.remove(relationid, usercode); } /** * 关闭连接时触发 * @param relationid * @param usercode * @param session */ @onclose public void onclose(@pathparam("relationid") string relationid, @pathparam("usercode") int usercode, session session) { log.info("websocket close connection: " sessionutils.getkey(relationid, usercode)); sessionutils.remove(relationid, usercode); } }
工具类用来存储唯一key和连接
这个是我业务的需要,我的业务是服务器有对应动作触发时,推送数据到客户端,没有接收客户端数据的操作。
import javax.websocket.session; import java.util.map; import java.util.concurrent.concurrenthashmap; /** * 功能说明:用来存储业务定义的sessionid和连接的对应关系 * 利用业务逻辑中组装的sessionid获取有效连接后进行后续操作 * 作者:liuxing(2014-12-26 02:32) */ public class sessionutils { public static mapclients = new concurrenthashmap<>(); public static void put(string relationid, int usercode, session session){ clients.put(getkey(relationid, usercode), session); } public static session get(string relationid, int usercode){ return clients.get(getkey(relationid, usercode)); } public static void remove(string relationid, int usercode){ clients.remove(getkey(relationid, usercode)); } /** * 判断是否有连接 * @param relationid * @param usercode * @return */ public static boolean hasconnection(string relationid, int usercode) { return clients.containskey(getkey(relationid, usercode)); } /** * 组装唯一识别的key * @param relationid * @param usercode * @return */ public static string getkey(string relationid, int usercode) { return relationid "_" usercode; } }
推送数据到客户端
在其他业务方法中调用
/** * 将数据传回客户端 * 异步的方式 * @param relationid * @param usercode * @param message */ public void broadcast(string relationid, int usercode, string message) { if (telsocketsessionutils.hasconnection(relationid, usercode)) { telsocketsessionutils.get(relationid, usercode).getasyncremote().sendtext(message); } else { throw new nullpointerexception(telsocketsessionutils.getkey(relationid, usercode) " connection does not exist"); } }
我是使用异步的方法推送数据,还有同步的方法
详见:http://docs.oracle.com/javaee/7/api/javax/websocket/session.html
客户端代码
var websocket = null; var trytime = 0; $(function () { initsocket(); window.onbeforeunload = function () { //离开页面时的其他操作 }; }); /** * 初始化websocket,建立连接 */ function initsocket() { if (!window.websocket) { alert("您的浏览器不支持websocket!"); return false; } websocket = new websocket("ws://127.0.0.1:8080/websocket.ws/" relationid "/" usercode); // 收到服务端消息 websocket.onmessage = function (msg) { console.log(msg); }; // 异常 websocket.onerror = function (event) { console.log(event); }; // 建立连接 websocket.onopen = function (event) { console.log(event); }; // 断线重连 websocket.onclose = function () { // 重试10次,每次之间间隔10秒 if (trytime < 10) { settimeout(function () { websocket = null; trytime ; initsocket(); }, 500); } else { trytime = 0; } }; }
其他调试工具
java实现一个websocket的客户端
依赖:
org.java-websocket java-websocket 1.3.0
代码:
import java.io.ioexception; import javax.websocket.clientendpoint; import javax.websocket.onerror; import javax.websocket.onmessage; import javax.websocket.onopen; import javax.websocket.session; @clientendpoint public class myclient { @onopen public void onopen(session session) { system.out.println("connected to endpoint: " session.getbasicremote()); try { session.getbasicremote().sendtext("hello"); } catch (ioexception ex) { } } @onmessage public void onmessage(string message) { system.out.println(message); } @onerror public void onerror(throwable t) { t.printstacktrace(); } }
import java.io.bufferedreader; import java.io.ioexception; import java.io.inputstreamreader; import java.net.uri; import javax.websocket.containerprovider; import javax.websocket.deploymentexception; import javax.websocket.session; import javax.websocket.websocketcontainer; public class myclientapp { public session session; protected void start() { websocketcontainer container = containerprovider.getwebsocketcontainer(); string uri = "ws://127.0.0.1:8080/websocket.ws/relationid/12345"; system.out.println("connecting to " uri); try { session = container.connecttoserver(myclient.class, uri.create(uri)); } catch (deploymentexception e) { e.printstacktrace(); } catch (ioexception e) { e.printstacktrace(); } } public static void main(string args[]){ myclientapp client = new myclientapp(); client.start(); bufferedreader br = new bufferedreader(new inputstreamreader(system.in)); string input = ""; try { do{ input = br.readline(); if(!input.equals("exit")) client.session.getbasicremote().sendtext(input); }while(!input.equals("exit")); } catch (ioexception e) { // todo auto-generated catch block e.printstacktrace(); } } }
chrome安装一个websocket客户端调试
最后
为了统一的操作体验,对于一些不支持websocket的浏览器,请使用socketjs技术做客户端开发。