tomcat支持websocket吗?-4008云顶国际网站
http协议是“请求-响应”模式,浏览器必须先发请求给服务器,服务器才会响应该请求。即服务器不会主动发送数据给浏览器。
实时性要求高的应用,如在线游戏、股票实时报价和在线协同编辑等,浏览器需实时显示服务器的最新数据,因此出现ajax和comet技术:
- ajax本质还是轮询
- comet基于http长连接做了一些hack
但它们实时性不高,频繁请求也会给服务器巨大压力,也浪费网络流量和带宽。于是html5推出websocket标准,使得浏览器和服务器之间任一方都可主动发消息给对方,这样服务器有新数据时可主动推给浏览器。
websocket原理
网络上的两个程序通过一个双向链路进行通信,这个双向链路的一端称为一个socket。一个socket对应一个ip地址和端口号,应用程序通常通过socket向网络发出或应答网络请求。
socket不是协议,是对tcp/ip协议层抽象出来的api。
websocket跟http协议一样,也是应用层协议。为兼容http协议,它通过http协议进行一次握手,握手后数据就直接从tcp层的socket传输,与http协议再无关。
这里的握手指应用协议层,不是tcp层,握手时,tcp连接已建立。
即http请求里带有websocket的请求头,服务端回复也带有websocket的响应头。
浏览器发给服务端的请求会带上跟websocket有关的请求头,比如connection: upgrade和upgrade: websocket
若服务器支持websocket,同样会在http响应加上websocket相关的http头部:
这样websocket连接就建立好了。
websocket的数据传输以frame形式传输,将一条消息分为几个frame,按先后顺序传输出去。为何这样设计?
- 大数据的传输可以分片传输,无需考虑数据大小问题
- 和http的chunk一样,可边生成数据边传输,提高传输效率
tomcat如何支持websocket
websocket聊天室案例
浏览器端核心代码:
var chat = {};
chat.socket = null;
chat.connect = (function(host) {
//判断当前浏览器是否支持websocket
if ('websocket' in window) {
// 若支持,则创建websocket js类
chat.socket = new websocket(host);
} else if ('mozwebsocket' in window) {
chat.socket = new mozwebsocket(host);
} else {
console.log('websocket is not supported by this browser.');
return;
}
// 再实现几个回调方法
// 回调函数,当和服务器的websocket连接建立起来后,浏览器会回调这个方法
chat.socket.onopen = function () {
console.log('info: websocket connection opened.');
document.getelementbyid('chat').onkeydown = function(event) {
if (event.keycode == 13) {
chat.sendmessage();
}
};
};
// 回调函数,当和服务器的websocket连接关闭后,浏览器会回调这个方法
chat.socket.onclose = function () {
document.getelementbyid('chat').onkeydown = null;
console.log('info: websocket closed.');
};
// 回调函数,当服务器有新消息发送到浏览器,浏览器会回调这个方法
chat.socket.onmessage = function (message) {
console.log(message.data);
};
});
服务器端tomcat实现代码:
tomcat端的实现类加上**@serverendpoint**注解,value是url路径
@serverendpoint(value = "/websocket/chat")
public class chatendpoint {
private static final string guest_prefix = "guest";
// 记录当前有多少个用户加入到了聊天室,它是static全局变量。为了多线程安全使用原子变量atomicinteger
private static final atomicinteger connectionids = new atomicinteger(0);
//每个用户用一个charannotation实例来维护,请你注意它是一个全局的static变量,所以用到了线程安全的copyonwritearrayset
private static final set<chatendpoint> connections =
new copyonwritearrayset<>();
private final string nickname;
private session session;
public chatendpoint() {
nickname = guest_prefix connectionids.getandincrement();
}
//新连接到达时,tomcat会创建一个session,并回调这个函数
@onopen
public void start(session session) {
this.session = session;
connections.add(this);
string message = string.format("* %s %s", nickname, "has joined.");
broadcast(message);
}
//浏览器关闭连接时,tomcat会回调这个函数
@onclose
public void end() {
connections.remove(this);
string message = string.format("* %s %s",
nickname, "has disconnected.");
broadcast(message);
}
//浏览器发送消息到服务器时,tomcat会回调这个函数
@onmessage
public void incoming(string message) {
// never trust the client
string filteredmessage = string.format("%s: %s",
nickname, htmlfilter.filter(message.tostring()));
broadcast(filteredmessage);
}
// websocket连接出错时,tomcat会回调这个函数
@onerror
public void onerror(throwable t) throws throwable {
log.error("chat error: " t.tostring(), t);
}
// 向聊天室中的每个用户广播消息
private static void broadcast(string msg) {
for (chatannotation client : connections) {
try {
synchronized (client) {
client.session.getbasicremote().sendtext(msg);
}
} catch (ioexception e) {
...
}
}
}
}
根据java websocket规范的规定,java websocket应用程序由一系列的websocket endpoint组成。endpoint是一个java对象,代表websocket连接的一端,就好像处理http请求的servlet一样,你可以把它看作是处理websocket消息的接口。
跟servlet不同的地方在于,tomcat会给每一个websocket连接创建一个endpoint实例。
可以通过两种方式
定义和实现endpoint
编程式
编写一个java类继承javax.websocket.endpoint,并实现它的onopen、onclose和onerror方法。这些方法跟endpoint的生命周期有关,tomcat负责管理endpoint的生命周期并调用这些方法。并且当浏览器连接到一个endpoint时,tomcat会给这个连接创建一个唯一的session(javax.websocket.session)。session在websocket连接握手成功之后创建,并在连接关闭时销毁。当触发endpoint各个生命周期事件时,tomcat会将当前session作为参数传给endpoint的回调方法,因此一个endpoint实例对应一个session,我们通过在session中添加messagehandler消息处理器来接收消息,messagehandler中定义了onmessage方法。在这里session的本质是对socket的封装,endpoint通过它与浏览器通信。
注解式
实现一个业务类并给它添加websocket相关的注解。
@serverendpoint(value = "/websocket/chat")
注解,它表明当前业务类chatendpoint是个实现了websocket规范的endpoint,并且注解的value值表明chatendpoint映射的url是/websocket/chat
。
chatendpoint类中有@onopen、@onclose、@onerror和在@onmessage注解的方法,见名知义。
我们只需关心具体的endpoint实现,比如聊天室,为向所有人群发消息,chatendpoint在内部使用了一个全局静态的集合copyonwritearrayset维护所有chatendpoint实例,因为每一个chatendpoint实例对应一个websocket连接,即代表了一个加入聊天室的用户。
当某个chatendpoint实例收到来自浏览器的消息时,这个chatendpoint会向集合中其他chatendpoint实例背后的websocket连接推送消息。
- tomcat主要做了哪些事情呢?
endpoint加载和websocket请求处理。
websocket加载
tomcat的websocket加载是通过sci,servletcontainerinitializer,是servlet 3.0规范中定义的用来接收web应用启动事件的接口。
为什么要监听servlet容器的启动事件呢?
这样就有机会在web应用启动时做一些初始化工作,比如websocket需要扫描和加载endpoint类。
将实现servletcontainerinitializer接口的类增加handlestypes注解,并且在注解内指定的一系列类和接口集合。比如tomcat为了扫描和加载endpoint而定义的sci类如下:
定义好sci,tomcat在启动阶段扫描类时,会将handlestypes注解指定的类都扫描出来,作为sci的onstartup参数,并调用sci#onstartup。
wssci#handlestypes注解定义了serverendpoint.class、serverapplicationconfig.class和endpoint.class,因此在tomcat的启动阶段会将这些类的类实例(不是对象实例)传递给wssci#onstartup。
- wssci的onstartup方法做了什么呢?
构造一个websocketcontainer实例,你可以把websocketcontainer理解成一个专门处理websocket请求的endpoint容器。即tomcat会把扫描到的endpoint子类和添加了注解@serverendpoint的类注册到这个容器,并且该容器还维护了url到endpoint的映射关系,这样通过请求url就能找到具体的endpoint来处理websocket请求。
websocket请求处理
- tomcat连接器的组件图
tomcat用protocolhandler组件屏蔽应用层协议的差异,protocolhandler两个关键组件:endpoint和processor。
这里的endpoint跟上文提到的websocket中的endpoint完全是两回事,连接器中的endpoint组件用来处理i/o通信。
websocket本质是个应用层协议,不能用httpprocessor处理websocket请求,而要用专门processor,在tomcat就是upgradeprocessor。
因为tomcat是将http协议升级成websocket协议的,因为websocket是通过http协议握手的,当websocket握手请求到来时,httpprotocolhandler首先接收到这个请求,在处理这个http请求时,tomcat通过一个特殊的filter判断该当前http请求是否是一个websocket upgrade请求(即包含upgrade: websocket的http头信息),如果是,则在http响应里添加websocket相关的响应头信息,并进行协议升级。
就是用upgradeprotocolhandler替换当前的httpprotocolhandler,相应的,把当前socket的processor替换成upgradeprocessor,同时tomcat会创建websocket session实例和endpoint实例,并跟当前的websocket连接一一对应起来。这个websocket连接不会立即关闭,并且在请求处理中,不再使用原有的httpprocessor,而是用专门的upgradeprocessor,upgradeprocessor最终会调用相应的endpoint实例来处理请求。
tomcat对websocket请求的处理没有经过servlet容器,而是通过upgradeprocessor组件直接把请求发到serverendpoint实例,并且tomcat的websocket实现不需要关注具体i/o模型的细节,从而实现了与具体i/o方式的解耦。
总结
websocket技术实现了tomcat与浏览器的双向通信,tomcat可以主动向浏览器推送数据,可以用来实现对数据实时性要求比较高的应用。这需要浏览器和web服务器同时支持websocket标准,tomcat启动时通过sci技术来扫描和加载websocket的处理类serverendpoint,并且建立起了url到serverendpoint的映射关系。
当第一个websocket请求到达时,tomcat将http协议升级成websocket协议,并将该socket连接的processor替换成upgradeprocessor。这个socket不会立即关闭,对接下来的请求,tomcat通过upgradeprocessor直接调用相应的serverendpoint来处理。
还可以通过spring来实现websocket应用。
- 点赞
- 收藏
- 关注作者
评论(0)