Netty实现WebSocket通信

服务端要实现websocket,编解码器中必须加入对websocket的支持

ChannelInitializer的initChannel方法实现

@Override
protected void initChannel(SocketChannel channel) throws Exception {
	try {
		ChannelPipeline pipeline = channel.pipeline();
		pipeline.addLast("httpServerCodec", new HttpServerCodec());
		pipeline.addLast("chunkedWriteHandler", new ChunkedWriteHandler());
		pipeline.addLast("httpObjectAggregator", new HttpObjectAggregator(8192));
		pipeline.addLast("webSocketServerProtocolHandler",
				(ChannelHandler) new WebSocketServerProtocolHandler("/ws"));
		pipeline.addLast((ChannelOutboundHandlerAdapter) (自己的消息编码器));
		pipeline.addLast(消息处理器);
	} catch (Exception e) {
		e.printStackTrace();
		throw e;
	}
}

WebSocketServerProtocolHandler中实现了对websocket的封装,不用我们去考虑握手的问题,构造器要传入一个路径参数,客户端访问时要加入这个参数,如ws://ip:port/ws

如果要求部署在https环境下,则服务端要加入对ssl链接的支持

	@Override
	protected void initChannel(SocketChannel channel) throws Exception {
		try {
			ChannelPipeline pipeline = channel.pipeline();
			if (sslFilePath != null) {
				SSLContext sslContext = SslUtil.createSSLContext(keyType, sslFilePath, keyPassword);
				// SSLEngine 此类允许使用ssl安全套接层协议进行安全通信
				SSLEngine engine = sslContext.createSSLEngine();
				engine.setUseClientMode(false);
				pipeline.addLast("ssl",new SslHandler(engine));
//				pipeline.addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));
			}
			pipeline.addLast("httpServerCodec", new HttpServerCodec());
			pipeline.addLast("chunkedWriteHandler", new ChunkedWriteHandler());
			pipeline.addLast("httpObjectAggregator", new HttpObjectAggregator(8192));
			pipeline.addLast("webSocketServerProtocolHandler", .....
	/**
	 * 创建 SSL上下文
	 * @param type 证书类型, java中它的值是JKS
	 * @param path 证书的本地路径,jks文件路径
	 * @param password 证书密钥
	 * @return
	 * @throws Exception
	 */
	public static SSLContext createSSLContext(String type ,String path ,String password) throws Exception {
	    KeyStore ks = KeyStore.getInstance(type); /// "JKS"
	    InputStream ksInputStream = new FileInputStream(path); /// 证书存放地址
	    try{
		    ks.load(ksInputStream, password.toCharArray());
		 	//KeyManagerFactory充当基于密钥内容源的密钥管理器的工厂。
		    KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());//getDefaultAlgorithm:获取默认的 KeyManagerFactory 算法名称。
		    kmf.init(ks, password.toCharArray());
		    //SSLContext的实例表示安全套接字协议的实现,它充当用于安全套接字工厂或 SSLEngine 的工厂。
		    SSLContext sslContext = SSLContext.getInstance("TLS");
		    sslContext.init(kmf.getKeyManagers(), null, null);
		    return sslContext;
	    }finally{
	    	if(ksInputStream != null){
	    		try{
	    			ksInputStream.close();
	    		}catch(Exception e){}
	    	}
	    }
	}
}

 阿里云申请的证书中有这些文件

需要把pfx文件转化成.jks文件

 

将PFX格式证书转换为JKS格式

您可以使用JDK中自带的Keytool工具,将PFX格式证书文件转换成JKS格式。例如,您可以执行以下命令将 server.pfx证书文件转换成 server.jks证书文件:

keytool -importkeystore -srckeystore D:\server.pfx -destkeystore D:\server.jks
        -srcstoretype PKCS12 -deststoretype JKS

证书介绍

websocket服务器的访问地址会有变化, wss://ip:port/ws

 

netty客户端实现websocket通信

netty也有对websocket客户端封装好的解码器,但不会用,这里通过netty自带的demo实现websocket客户端

demo代码:

public final class WebSocketClient {
 
    static final String URL = System.getProperty("url", "ws://127.0.0.1:8080/websocket");
 
    public static void main(String[] args) throws Exception {
        URI uri = new URI(URL);
        String scheme = uri.getScheme() == null? "ws" : uri.getScheme();
        final String host = uri.getHost() == null? "127.0.0.1" : uri.getHost();
        final int port;
        if (uri.getPort() == -1) {
            if ("ws".equalsIgnoreCase(scheme)) {
                port = 80;
            } else if ("wss".equalsIgnoreCase(scheme)) {
                port = 443;
            } else {
                port = -1;
            }
        } else {
            port = uri.getPort();
        }
 
        if (!"ws".equalsIgnoreCase(scheme) && !"wss".equalsIgnoreCase(scheme)) {
            System.err.println("Only WS(S) is supported.");
            return;
        }
 
        final boolean ssl = "wss".equalsIgnoreCase(scheme);
        final SslContext sslCtx;
        if (ssl) {
            sslCtx = SslContextBuilder.forClient()
                .trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        } else {
            sslCtx = null;
        }
 
        EventLoopGroup group = new NioEventLoopGroup();
        try {
            final WebSocketClientHandler handler =
                    new WebSocketClientHandler(
                            WebSocketClientHandshakerFactory.newHandshaker(
                                    uri, WebSocketVersion.V13, null, false, new DefaultHttpHeaders()));
 
            Bootstrap b = new Bootstrap();
            b.group(group)
             .channel(NioSocketChannel.class)
             .handler(new ChannelInitializer<SocketChannel>() {
                 @Override
                 protected void initChannel(SocketChannel ch) {
                     ChannelPipeline p = ch.pipeline();
                     if (sslCtx != null) {
                         p.addLast(sslCtx.newHandler(ch.alloc(), host, port));
                     }
                     p.addLast(
                             new HttpClientCodec(),
                             new HttpObjectAggregator(8192),
                             handler);
                 }
             });
 
            Channel ch = b.connect(uri.getHost(), port).sync().channel();
            handler.handshakeFuture().sync();
 
            BufferedReader console = new BufferedReader(new InputStreamReader(System.in));
            while (true) {
                String msg = console.readLine();
                if (msg == null) {
                    break;
                } else if ("bye".equals(msg.toLowerCase())) {
                    ch.writeAndFlush(new CloseWebSocketFrame());
                    ch.closeFuture().sync();
                    break;
                } else if ("ping".equals(msg.toLowerCase())) {
                    WebSocketFrame frame = new PingWebSocketFrame(Unpooled.wrappedBuffer(new byte[] { 8, 1, 8, 1 }));
                    ch.writeAndFlush(frame);
                } else {
                    WebSocketFrame frame = new TextWebSocketFrame(msg);
                    ch.writeAndFlush(frame);
                }
            }
        } finally {
            group.shutdownGracefully();
        }
    }
}

public class WebSocketClientHandler extends SimpleChannelInboundHandler<Object> {
 
    private final WebSocketClientHandshaker handshaker;
    private ChannelPromise handshakeFuture;
 
    public WebSocketClientHandler(WebSocketClientHandshaker handshaker) {
        this.handshaker = handshaker;
    }
 
    public ChannelFuture handshakeFuture() {
        return handshakeFuture;
    }
 
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        handshakeFuture = ctx.newPromise();
    }
 
    @Override
    public void channelActive(ChannelHandlerContext ctx) {
        handshaker.handshake(ctx.channel());
    }
 
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        System.out.println("WebSocket Client disconnected!");
    }
 
    @Override
    public void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        Channel ch = ctx.channel();
        if (!handshaker.isHandshakeComplete()) {
            handshaker.finishHandshake(ch, (FullHttpResponse) msg);
            System.out.println("WebSocket Client connected!");
            handshakeFuture.setSuccess();
            return;
        }
 
        if (msg instanceof FullHttpResponse) {
            FullHttpResponse response = (FullHttpResponse) msg;
            throw new IllegalStateException(
                    "Unexpected FullHttpResponse (getStatus=" + response.getStatus() +
                            ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')');
        }
 
        WebSocketFrame frame = (WebSocketFrame) msg;
        if (frame instanceof TextWebSocketFrame) {
            TextWebSocketFrame textFrame = (TextWebSocketFrame) frame;
            System.out.println("WebSocket Client received message: " + textFrame.text());
        } else if (frame instanceof PongWebSocketFrame) {
            System.out.println("WebSocket Client received pong");
        } else if (frame instanceof CloseWebSocketFrame) {
            System.out.println("WebSocket Client received closing");
            ch.close();
        }
    }
 
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        if (!handshakeFuture.isDone()) {
            handshakeFuture.setFailure(cause);
        }
        ctx.close();
    }
}

将demo封装成一个解码器


public class WSClientHandler extends MessageToMessageDecoder<Object> {

    private final WebSocketClientHandshaker handshaker;
    private ChannelPromise handshakeFuture;
 
    public WSClientHandler(WebSocketClientHandshaker shaker) {
        this.handshaker = shaker;
    }
 
    public ChannelFuture handshakeFuture() {
        return handshakeFuture;
    }
 
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) {
        handshakeFuture = ctx.newPromise();
    }
 
    @Override
    public void channelActive(ChannelHandlerContext ctx)  throws Exception{
    	handshaker.handshake(ctx.channel());
    	super.channelActive(ctx);
    }
 
    @Override
    public void channelInactive(ChannelHandlerContext ctx) {
        System.out.println("WebSocket Client disconnected!");
    }
 
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) {
        cause.printStackTrace();
        if (!handshakeFuture.isDone()) {
            handshakeFuture.setFailure(cause);
        }
        ctx.close();
    }

	@Override
	protected void decode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
        Channel ch = ctx.channel();
        if (!handshaker.isHandshakeComplete()) {
            handshaker.finishHandshake(ch, (FullHttpResponse) msg);
            handshakeFuture.setSuccess();
            //发送握手完成事件
            ctx.fireUserEventTriggered(EventConst.USER_EVENT_HANDSHAKER);
            return;
        }
 
        if (msg instanceof FullHttpResponse) {
            FullHttpResponse response = (FullHttpResponse) msg;
            throw new IllegalStateException(
                    "Unexpected FullHttpResponse (getStatus=" + response.getStatus() +
                            ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')');
        }
 
        TextWebSocketFrame frame = (TextWebSocketFrame) msg;
        out.add(frame.text());
	}

}

代码解释:

ChannelPromise handshakeFuture; 表示握手的状态

    @Override
    public void channelActive(ChannelHandlerContext ctx)  throws Exception{
    	handshaker.handshake(ctx.channel());
    	super.channelActive(ctx);
    }

当连接上了服务器后,发送握手消息,这里要把super.channelActive(ctx)加上,不加的话,在它之后的解码器都收到不active事件了,如果不加,那么后面decode方法里也要加,在decode里加这样会好些,因为不用传自定义事件了。

@Override
	protected void decode(ChannelHandlerContext ctx, Object msg, List<Object> out) throws Exception {
        Channel ch = ctx.channel();
        if (!handshaker.isHandshakeComplete()) {
            handshaker.finishHandshake(ch, (FullHttpResponse) msg);
            handshakeFuture.setSuccess();
            //发送握手完成事件
            ctx.fireUserEventTriggered(EventConst.USER_EVENT_HANDSHAKER);
            return;
        }
 
        if (msg instanceof FullHttpResponse) {
            FullHttpResponse response = (FullHttpResponse) msg;
            throw new IllegalStateException(
                    "Unexpected FullHttpResponse (getStatus=" + response.getStatus() +
                            ", content=" + response.content().toString(CharsetUtil.UTF_8) + ')');
        }
 
        TextWebSocketFrame frame = (TextWebSocketFrame) msg;
        out.add(frame.text());
	}

这段代码的意思是:当收到一条消息时,如果没有完成握手,那这条消息就是握手的响应消息,设置为完成握手状态,并发出一个自定义的事件(握手完成事件,在之后的解码器中要捕获这个事件,若channelAcive中没有写super.channelAcive,这里可以改成super.channelActive,这样比较好吧);如果完成的握手,则表示是正常消息,把消息解码成字符串String格式,交给下一个解码器处理.

 

使用这个解码器:

ChannelInitializer实现类中

	@Override
	protected void initChannel(SocketChannel ch) throws Exception {
		ChannelPipeline pipeline = ch.pipeline();
        if (ssl) {
            sslCtx = SslContextBuilder.forClient().trustManager(InsecureTrustManagerFactory.INSTANCE).build();
        } else {
            sslCtx = null;
        }
        if(sslCtx != null){
        	pipeline.addLast(sslCtx.newHandler(ch.alloc(), hostUri.getHost(), hostUri.getPort()));
        }
		pipeline.addLast("httpClientCodec", new HttpClientCodec());
		pipeline.addLast("chunkedWriteHandler", new ChunkedWriteHandler());
		pipeline.addLast("httpObjectAggregator", new HttpObjectAggregator(8192));
		pipeline.addLast("webSocketclientProtocolHandler", (ChannelHandler) new WSClientHandler(shaker));
		pipeline.addLast((ChannelOutboundHandlerAdapter) new WSMsgEncoder());
		//这里加上自己的消息处理器对象
	}

实例化WSClientHandler时,要传入一个WebSocketClientHandshaker,

	private final WebSocketClientHandshaker shaker;
	
	private SslContext sslCtx;
	
	private boolean ssl = false;
	
	private URI hostUri;
shaker = WebSocketClientHandshakerFactory.newHandshaker(hostUri,WebSocketVersion.V13, null, false, new DefaultHttpHeaders());

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值