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的吧,因为现在看到的那些都不太好用的样子。。。

Netty is a Java-based framework for building high-performance, scalable, and reliable network applications. It includes support for various protocols, including HTTP, WebSocket, and TCP. Netty's WebSocket support makes it easy to build real-time, bidirectional communication between clients and servers. WebSocket is a protocol that enables real-time communication between clients and servers. It allows for full-duplex communication, meaning that both the client and server can send data to each other at any time. WebSocket is especially useful for applications that require real-time updates, such as chat applications, online gaming, and financial trading platforms. Netty provides a WebSocket server implementation that makes it easy to build WebSocket applications. The WebSocket server implementation is based on the WebSocket specification and supports the latest version of the protocol (RFC 6455). Netty's WebSocket server includes support for sub-protocols, extensions, and binary messaging. To build a WebSocket application with Netty, you can use the WebSocketServerHandler class. This class provides an easy-to-use API for handling WebSocket connections, sending and receiving messages, and managing the lifecycle of the WebSocket session. Some of the benefits of using Netty for WebSocket applications include: - High-performance, scalable, and reliable network communication - Support for multiple protocols, including HTTP, WebSocket, and TCP - Easy-to-use API for building WebSocket applications - Support for sub-protocols, extensions, and binary messaging - Built-in support for SSL/TLS encryption and compression - Active community and ongoing development Overall, Netty's WebSocket support makes it easy to build real-time, bidirectional communication between clients and servers, and provides a reliable, high-performance, and scalable solution for building WebSocket applications.
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值