前两篇文章分析了node.js版本的socket.io的服务端与客户端的代码,其实他们socket.io就是在websocket的基础上进一步做了一层封装,添加了一些心跳,重连接。。。
这篇文章就先来看看在netty中是如何建立websocket服务端的吧,先来回顾一下websocket建立连接以及通信的过程:
(1)客户端向服务端发送http报文格式的请求,而且是GET方法的请求,不过这里与普通的http请求有稍微不同的地方,那就是头部connection字段是Upgrade,然后又Upgrad字段,值是websocket,其请求格式如下:
* GET /chat HTTP/1.1
* Host: server.example.com
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Key: dGhlIHNhbXBsZSBub25jZQ==
* Sec-WebSocket-Origin: http://example.com
* Sec-WebSocket-Protocol: chat, superchat
* Sec-WebSocket-Version: 13
(2)由服务器端向客户端返回特定格式的http报文,表示当前websocket建立,报文格式如下:
* HTTP/1.1 101 Switching Protocols
* Upgrade: websocket
* Connection: Upgrade
* Sec-WebSocket-Accept: s3pPLMBiTxaQ9kYGzzhZRbK+xOo=
* Sec-WebSocket-Protocol: chat
(3)当连接建立以后,那么双方就可以通过刚刚建立连接用的socket来发送数据了,这里发送的数据必须经过websocket的帧格式的编码,具体它的信息就去看websocket的协议标准就知道了。。。
其实知道了整个建立连接以及通信的过程,那么就能够知道如何在channel的pipeline上面安排各种handler的顺序了,如下:
(1)首先要安排http报文的decode,aggregator以及encode的handler,因为最开始还是采用http通信的
(2)接收到websocket的建立连接报文之后,通过一些处理按照规定的格式将http返回发送给客户端,那么这就表示websocket的连接已经建立了
(3)移除前面设置的所有的http报文处理的handler,然后加上websocket的frame的decode以及encodehandler,用于将从客户端收到的数据转化为封装好的格式,以及将要发送的数据转化成byte.
那么接下来来看看具体是如何在netty中建立websocket的服务端的吧,直接上主程序的代码:
public class Fjs {
public void run() throws Exception {
EventLoopGroup bossGroup = new NioEventLoopGroup(); //这个是用于serversocketchannel的eventloop
EventLoopGroup workerGroup = new NioEventLoopGroup(); //这个是用于处理accept到的channel
try {
ServerBootstrap b = new ServerBootstrap(); //构建serverbootstrap对象
b.group(bossGroup, workerGroup); //设置时间循环对象,前者用来处理accept事件,后者用于处理已经建立的连接的io
b.channel(NioServerSocketChannel.class); //用它来建立新accept的连接,用于构造serversocketchannel的工厂类
b.childHandler(new ChannelInitializer<SocketChannel>(){ //为accept channel的pipeline预添加的inboundhandler
@Override //当新连接accept的时候,这个方法会调用
protected void initChannel(SocketChannel ch) throws Exception {
//ch.pipeline().addLast(new ReadTimeoutHandler(10));
//ch.pipeline().addLast(new WriteTimeoutHandler(1));
ch.pipeline().addLast("decoder", new HttpRequestDecoder()); //用于解析http报文的handler
ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536)); //用于将解析出来的数据封装成http对象,httprequest什么的
ch.pipeline().addLast("encoder", new HttpResponseEncoder()); //用于将response编码成httpresponse报文发送
ch.pipeline().addLast("handshake", new WebSocketServerProtocolHandler("", "", true)); //websocket的handler部分定义的,它会自己处理握手等操作
//ch.pipeline().addLast("chunkedWriter", new ChunkedWriteHandler());
//ch.pipeline().addLast(new HttpHanlder());
ch.pipeline().addLast(new WebSocketHandler());
}
});
//bind方法会创建一个serverchannel,并且会将当前的channel注册到eventloop上面,
//会为其绑定本地端口,并对其进行初始化,为其的pipeline加一些默认的handler
ChannelFuture f = b.bind(80).sync();
f.channel().closeFuture().sync(); //相当于在这里阻塞,直到serverchannel关闭
} finally {
bossGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}
}
public static void main(String args[]) throws Exception {
new Fjs().run();
}
}
这里可以看看在channel上面安排的handler的情况,主要是就是http部分的decode以及encode的handler,另外这里有一个比较重要的handler,WebSocketServerProtocolHandler它对整个websocket的通信进行了初始化,包括握手,以及以后的一些通信控制。。。
然后再来看看我们自己定义的handler的处理吧:
@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg)
throws Exception {
// TODO Auto-generated method stub
WebSocketFrame frame = (WebSocketFrame)msg;
ByteBuf buf = frame.content(); //真正的数据是放在buf里面的
String aa = buf.toString(Charset.forName("utf-8")); //将数据按照utf-8的方式转化为字符串
System.out.println(aa);
WebSocketFrame out = new TextWebSocketFrame(aa); //创建一个websocket帧,将其发送给客户端
ctx.pipeline().writeAndFlush(out).addListener(new ChannelFutureListener(){
@Override
public void operationComplete(ChannelFuture future)
throws Exception {
// TODO Auto-generated method stub
ctx.pipeline().close(); //从pipeline上面关闭的时候,会关闭底层的chanel,而且会从eventloop上面取消注册
}
});
}
其实这里也就主要是channelRead方法,将从客户端发送过来的数据读出来输出,然后在返回一个字符串就好了。。。还是比较的简单。。
上面的两段代码就基本上能够使用netty来建立websocket规范的服务端了,可以看到整个用法还是很简单的,这里还有必要去看看WebSocketServerProtocolHandler做了些什么事情。。。
//当当前这个handler被添加到pipeline之后会调用这个方法
public void handlerAdded(ChannelHandlerContext ctx) {
ChannelPipeline cp = ctx.pipeline();
if (cp.get(WebSocketServerProtocolHandshakeHandler.class) == null) {
// 这里相当于要在当前的handler之前添加用于处理websocket建立连接时候的握手handler
ctx.pipeline().addBefore(ctx.name(), WebSocketServerProtocolHandshakeHandler.class.getName(),
new WebSocketServerProtocolHandshakeHandler(websocketPath, subprotocols, allowExtensions));
}
}
首先可以看到它覆盖了这个方法,那么在当前这个handler加入pipeline之后会在这个pipeline之前添加另外一个handler,这个handler是用于websocket的握手的。。。
好吧,那么我们来看看这个用于websocket建立连接的握手的handler是怎么个用法吧:
@Override
public void channelRead(final ChannelHandlerContext ctx, Object msg) throws Exception {
FullHttpRequest req = (FullHttpRequest) msg;
if (req.getMethod() != GET) { //如果不是get请求,那么就出错了
sendHttpResponse(ctx, req, new DefaultFullHttpResponse(HTTP_1_1, FORBIDDEN));
return;
}
//创建爱你websocket进行连接握手的工厂类,因为不同版本的连接握手不太一样
final WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory(
getWebSocketLocation(ctx.pipeline(), req, websocketPath), subprotocols, allowExtensions);
final WebSocketServerHandshaker handshaker = wsFactory.newHandshaker(req); //这里会根据不同的websocket版本来安排不同的握手handler
if (handshaker == null) {
WebSocketServerHandshakerFactory.sendUnsupportedWebSocketVersionResponse(ctx.channel());
} else {
//其实这里在将用于建立连接的http报文发送回去之后,会将前面添加的http部分的handler都移除,然后加上用于decode和encode针对websocket帧的handler
final ChannelFuture handshakeFuture = handshaker.handshake(ctx.channel(), req); //这里说白了就是进行握手,向客户端返回用于建立连接的报文
handshakeFuture.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (!future.isSuccess()) {
ctx.fireExceptionCaught(future.cause());
} else {
ctx.fireUserEventTriggered( //用于激活握手已经完成的事件,可以让用户的代码收到通知
WebSocketServerProtocolHandler.ServerHandshakeStateEvent.HANDSHAKE_COMPLETE);
}
}
});
WebSocketServerProtocolHandler.setHandshaker(ctx, handshaker); //保存当前的shaker
ctx.pipeline().replace(this, "WS403Responder",
WebSocketServerProtocolHandler.forbiddenHttpRequestResponder()); //将当前这个handler替换,因为只有刚开始使用http协议进行通信,接下来就没有了
}
}
其实基本还是很简单的,无非就是根据收到的http请求,获取当前建立连接的websocket客户端的版本信息,然后通过版本获取相应的用于握手的handler,然后进行相应的处理,将用于建立websocket连接的报文发送给客户端就可以了,不过这里还有一些细节,那就是会在之后将处理http的handler移除,换成处理websocket帧的handler,因为以后的通信就不是按照http来的了,而且会将当前这个handler也替换掉,毕竟以后就不会再用了嘛。。
那么接下来回到WebSocketServerProtocolHandler,因为本身它是一个messagetomessagedecoder,那么来看看它的decode方法:
protected void decode(ChannelHandlerContext ctx, WebSocketFrame frame, List<Object> out) throws Exception {
if (frame instanceof CloseWebSocketFrame) { //如果是用于关闭
WebSocketServerHandshaker handshaker = getHandshaker(ctx);
frame.retain();
handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame); //发送用于关闭的websocket帧
return;
}
super.decode(ctx, frame, out);
}
上面获取的shaker是在前面保存的,毕竟不同版本的websocket对应不同的。。。再来看看它父类的方法吧:
protected void decode(ChannelHandlerContext ctx, WebSocketFrame frame, List<Object> out) throws Exception {
if (frame instanceof PingWebSocketFrame) {
frame.content().retain();
ctx.channel().writeAndFlush(new PongWebSocketFrame(frame.content()));
return;
}
if (frame instanceof PongWebSocketFrame) {
// Pong frames need to get ignored
return;
}
out.add(frame.retain());
}
那么到这里,整个websocket在netty中建立服务端的流程都已经很清楚了。。。
以后如果有时间的自己写一个socket.io的吧,因为现在看到的那些都不太好用的样子。。。