netty的websocket服务端开发

前两篇文章分析了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的吧,因为现在看到的那些都不太好用的样子。。。

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值