前面两篇文章分别给大家介绍了原理篇,应用篇,这回给大家介绍WebSocket在Netty中的应用。
传统的Http协议只能客户端发起通信,而不能做到服务端主动通知。这里可能有人说可以采用long polling,也就是客户端不断的向服务端请求,获取新数据,虽然能解决问题,但效率低下,浪费资源,只能说是笨办法。所以WebSocket就出现了。
webSocket协议是2008年诞生,2011年成为国际标准,所有浏览器都支持。他最大的特点就是服务端和客户端全双工通信,即客户端能主动给服务端发消息,服务端也能给客户端主动发消息。
该协议默认端口80或者443 协议标识符是ws(如果加密,则为wss),服务器网址就是 URL,例如:
ws://example.com:8080/path
不管是HTTP和Websocket都是建立在TCP协议之上, 如下图:
所不一样的就是与服务端交互流程上有所区别,如下图:
从图中可以看到,传统的Http只有请求和响应,而WebSocket分为这几个阶段。
1. 握手阶段(handshake)
客户端发起握手请求,如下报文:
服务端响应握手请求,如下报文:
可以看出握手阶段采用的是HTTP协议,能通过http的各种代理服务器,与http协议有良好的兼容性。
2. 长连接阶段
通过握手从http协议升级到了websocket协议,
这个时你可以向服务端发送任何消息,服务也可以向你主动推送消息。数据格式可以根据业务需要与服务端商议,灵活度高,通信效率高效。
3. 连接关闭
客户端与服务端其中一方发起关闭即为关闭。
我们主要使用Netty框架来实现服务端,采用js来实现客户端。
主要使用了 Netty-socketio 这个开源框架,基于Netty框架里面封装了WebSocket的协议解析,响应,如果你是maven工程,可以在pom.xml中添加dependecy
<dependency>
<groupId>com.corundumstudio.socketiogroupId>
<artifactId>netty-socketioartifactId>
<version>1.7.12version>
dependency>
应用起来比较简单,如下代码
public static void main( String[] args ) throws InterruptedException {
final SocketIOServer server = new SocketIOServer(loadSocketIOConfig(doc)); // (1)
// 启动login
final SocketIONamespace ns = server.addNamespace("/echo"); // (2)
EchoConnectListener listener = new EchoConnectListener(); // (3)
EchoEventListener evtlistener = new EchoEventListener(); // (4)
ns.addConnectListener(listener);
ns.addDisconnectListener(listener);
ns.addEventListener(WebEventMessage.EVT_TAG, WebEventMessage.class, evtlistener); // (5)
server.start();
logger.info("============>WSServer started ... ok");
}
private static Configuration loadSocketIOConfig(Document doc) {
Configuration config = new Configuration();
config.setHostname(getNodeStr(doc, "host"));
config.setPort(getNodeInt(doc, "port"));
config.setBossThreads(getNodeInt(doc, "bossThreads"));
config.setWorkerThreads(getNodeInt(doc, "workerThreads"));
//config.setUseLinuxNativeEpoll(PlatformDependent.isWindows() ? false : true);
return config;
}
public class EchoEventListener implements DataListener<WebEventMessage> {
private static final Logger logger = LoggerFactory.getLogger(EchoEventListener.class);
public EchoEventListener() {
}
@Override
public void onData(SocketIOClient client, WebEventMessage data, AckRequest ackSender) throws Exception {
System.out.println("message:" + data.getMessage());
WebEventMessage hello = new WebEventMessage();
JSONObject object = new JSONObject(data.getMessage());
if (UserTokenMgr.getInstance().verify(object.getInt("userid"), object.getString("token"))) {
hello.setMessage("login success");
} else {
hello.setMessage("login failed");
}
client.getNamespace().getBroadcastOperations().sendEvent(WebEventMessage.EVT_TAG, hello); // (6)
}
}
加载配置,实例化SocketIOServer, 配置主要是ip,端口,处理线程和工作线程数量。
加入一个命名为echo的Namespace,这个具体访问是在url体现的,如下:
ws://example.com:8080/echo
只要是这个url的访问,都会在这个namespace处理。
创建连接监听器
创建业务消息处理器
将连接监听器,和业务消息处理器添加到 namespace, 在添加业务消息处理器时,需要指定消息协议对象,比如 WebEventMessage。
服务端接收数据,并返回给客户端。
客户端这里采用的是socket.io, 采用js实现,与前端对接。
var socket = io.connect('http://127.0.0.1:8080/echo');
socket.on('connect', function() {
output('Client has connected to the server!');
}); // (1)
socket.on('message', function(data) {
output(' echo' + ': ' + data.message);
}); // (2)
socket.on('disconnect', function() {
output('The client has disconnected!');
}); // (3)
发送数据
var message = JSON.stringify(jsonmsg);
var cmd = 1;
var jsonObject = {'@class': 'WebEventMessage',
cmd:cmd,
message: message};
socket.emit('message', jsonObject, function(arg1, arg2) {}); // (4)
connect连接建立后回调函数。
message用于指定收到服务器数据后的回调函数。
disconnect断开连接后回调函数。
emit向服务器发送消息。
基于Netty的websocket,在提供良好性能的同时大大增强直播,聊天等产品在前端页面的体验。
Sokect.io
netty-socketio
WebSocket 教程