netty做服务端支持ssl协议实现websocket的wss协议(客户端为浏览器)

也是在网上查的资料,整理一下相互学习下

第一步:生成SSL证书:

    因为是测试,直接使用jdk自带的keytool工具生成自签名证书(注:自签名证书是不被浏览器认可的,只能用于测试),

    --打开cmd

   --输入命令(复制啊):keytool -genkey -keysize 2048 -validity 365 -keyalg RSA -keypass netty123 -storepass netty123 -keystore wss.jks

第二步:在ChannelPipeline添加SslHandler:

    首先写个工具类:SslUtil配置SSLContext

	public static SSLContext createSSLContext(String type ,String path ,String password) throws Exception {
	    KeyStore ks = KeyStore.getInstance(type); /// "JKS"
	    InputStream ksInputStream = new FileInputStream(path); /// 证书存放地址
	    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;
	}

添加SslHandler(放在第一个)

        @Override
        public void initChannel(SocketChannel ch) throws Exception {
            SSLContext sslContext = SslUtil.createSSLContext("JKS","D://wss.jks","netty123");
            //SSLEngine 此类允许使用ssl安全套接层协议进行安全通信
            SSLEngine engine = sslContext.createSSLEngine(); 
            engine.setUseClientMode(false); 
            
            ch.pipeline().addLast(new SslHandler(engine));
            ch.pipeline().addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));    
            ch.pipeline().addLast("http-codec", new HttpServerCodec());            
            ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));  
            ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());    
            ch.pipeline().addLast(new AcceptorIdleStateTrigger());    
            ch.pipeline().addLast("handler", new WebSocketHandler());            
        }

 JS文件的URL:

var url = "wss://localhost:8000/ws";

第三步:运行

    运行服务端,在浏览器地址栏输入https://localhost:8000/  浏览器会提示这是不安全的连接(浏览器不信任自签名证书,如果有域名可以自己申请一个证书,网上有免费测试版的证书),添加例外信任,再在html页面上右键获得它的本地路径 在浏览器中运行   连接成功

补充:(因为发现一些小伙伴不清楚部分类的来源,特此补充)--2021.09.08

目录:

  

AcceptorIdleStateTrigger .java

package com.netty.wss;

import io.netty.channel.ChannelHandler;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;

@ChannelHandler.Sharable
public class AcceptorIdleStateTrigger extends ChannelInboundHandlerAdapter {
    private int unReceivedCounts=0;
   
    
    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg)
    		throws Exception {
    	//有消息时归零
    	unReceivedCounts=0;
    	super.channelRead(ctx, msg);
    }
    
	@Override
	public void userEventTriggered(ChannelHandlerContext ctx, Object evt)
			throws Exception {
		if (evt instanceof IdleStateEvent) {
			//在这里并处理业务逻辑
			IdleState state = ((IdleStateEvent) evt).state();
			if (state == IdleState.READER_IDLE) {				
				unReceivedCounts++;
				System.out.println(unReceivedCounts);
				if (unReceivedCounts>10) {
					ctx.close();
					unReceivedCounts = 0;
				}				
			}
		} else {
			super.userEventTriggered(ctx, evt);
		}		
	}

}

SslUtil.java

package com.netty.wss;

import java.io.FileInputStream;
import java.io.InputStream;
import java.security.KeyStore;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;

public class SslUtil {
	
	public static SSLContext createSSLContext(String type ,String path ,String password) throws Exception {
	    KeyStore ks = KeyStore.getInstance(type); /// "JKS"
	    InputStream ksInputStream = new FileInputStream(path); /// 证书存放地址
	    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;
	}
}

WebSocketHandler.java

package com.netty.wss;


import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.ChannelInboundHandlerAdapter;
import io.netty.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.HttpResponseStatus;
import io.netty.handler.codec.http.HttpVersion;
import io.netty.handler.codec.http.websocketx.CloseWebSocketFrame;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshaker;
import io.netty.handler.codec.http.websocketx.WebSocketServerHandshakerFactory;
import io.netty.util.CharsetUtil;

/**
 * websocket 具体业务处理方法
 * 
 * */
public class WebSocketHandler extends ChannelInboundHandlerAdapter{
	private WebSocketServerHandshaker handshaker;
			
	/**
	 * 当客户端连接成功,返回个成功信息
	 * */
	@Override
	public void channelActive(ChannelHandlerContext ctx) throws Exception {
	
	}
		
	/**
	 * 当客户端断开连接
	 * */
	@Override
	public void channelInactive(ChannelHandlerContext ctx) throws Exception {
	}

	@Override
	public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
		ctx.flush();
	}

	@Override
	public void channelRead(ChannelHandlerContext ctx, Object msg)
			throws Exception {
		// 传统的HTTP接入
		if(msg instanceof FullHttpRequest){	
			handleHttpRequest(ctx,(FullHttpRequest)msg);						
		}else if(msg instanceof WebSocketFrame){
			handlerWebSocketFrame(ctx,(WebSocketFrame)msg);				

		}
		super.channelRead(ctx, msg);
	}
			
	public void handlerWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception{
		//关闭请求
		if(frame instanceof CloseWebSocketFrame){		
			handshaker.close(ctx.channel(), (CloseWebSocketFrame)frame.retain());
			return;		
		}
		//只支持文本格式,不支持二进制消息
		if(!(frame instanceof TextWebSocketFrame)){			
			throw new Exception("仅支持文本格式");
		}
	}
	
	//第一次请求是http请求,请求头包括ws的信息
	public void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest req){
		if(!req.decoderResult().isSuccess()){
			sendHttpResponse(ctx,req, new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
			return;
		}		
		// 构造握手响应返回
		WebSocketServerHandshakerFactory wsFactory = new WebSocketServerHandshakerFactory("ws:/"+ctx.channel()+ "/ws",null,false);		
		handshaker = wsFactory.newHandshaker(req);
		// 请求头不合法, 导致handshaker没创建成功 
		if(handshaker == null){
			WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
		}else{
			handshaker.handshake(ctx.channel(), req);
		}
	}
	
	public static void sendHttpResponse(ChannelHandlerContext ctx,FullHttpRequest req,DefaultFullHttpResponse res){		
        // 返回应答给客户端
        if (res.status().code() != 200){
            ByteBuf buf = Unpooled.copiedBuffer(res.status().toString(), CharsetUtil.UTF_8);
            res.content().writeBytes(buf);
            buf.release();
        }
        // 如果是非Keep-Alive,关闭连接
        ChannelFuture f = ctx.channel().writeAndFlush(res);
        if (!isKeepAlive(req) || res.status().code() != 200){
            f.addListener(ChannelFutureListener.CLOSE);
        }		
	}	
    private static boolean isKeepAlive(FullHttpRequest req){
        return false;
    }
   
    //异常处理,netty默认是关闭channel
	@Override
	public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
			throws Exception {
		//输出日志
		 cause.printStackTrace();
		 ctx.close();
	}
	

}

WebsocketServer.java (启动类)

package com.netty.wss;

import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.ChannelOption;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.SocketChannel;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.ssl.SslHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
import io.netty.handler.timeout.IdleStateHandler;
import java.util.concurrent.TimeUnit;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLEngine;

public class WebsocketServer {
	public static   WebsocketServer instance;
	
	public static final WebsocketServer getInstance(){
		return instance;
	}
	
	private int port;
	public WebsocketServer(int port) {
		if(instance!=null){
			return;
		}
		
		this.port = port;
		try {
			this.start();
			instance=this;			
		} catch (Exception e) {
			e.printStackTrace();
		}
		
	}
	
	private void start() throws Exception{
		EventLoopGroup bossGroup = new NioEventLoopGroup();
		EventLoopGroup workerGroup = new NioEventLoopGroup();
		try {
			ServerBootstrap b = new ServerBootstrap();
			b.group(bossGroup, workerGroup);
			b.channel(NioServerSocketChannel.class); 
			b.childHandler(new myChannelInitializer());
			b.option(ChannelOption.SO_BACKLOG, 128);
			b.childOption(ChannelOption.SO_KEEPALIVE, true);
			ChannelFuture f = b.bind(port); 
			System.out.println("Netty Websocket is start .........");
			f.channel().closeFuture().sync();
		} finally {
			workerGroup.shutdownGracefully();
			bossGroup.shutdownGracefully();
		}
	}
	
	private class  myChannelInitializer extends  ChannelInitializer<SocketChannel>{
		@Override
		public void initChannel(SocketChannel ch) throws Exception {
			SSLContext sslContext = SslUtil.createSSLContext("JKS","D://wss.jks","netty123");
			//SSLEngine 此类允许使用ssl安全套接层协议进行安全通信
			SSLEngine engine = sslContext.createSSLEngine(); 
			engine.setUseClientMode(false); 
			
			ch.pipeline().addLast(new SslHandler(engine));
			ch.pipeline().addLast(new IdleStateHandler(5, 0, 0, TimeUnit.SECONDS));	
			ch.pipeline().addLast("http-codec", new HttpServerCodec());			
			ch.pipeline().addLast("aggregator", new HttpObjectAggregator(65536));  
			ch.pipeline().addLast("http-chunked", new ChunkedWriteHandler());	
			ch.pipeline().addLast(new AcceptorIdleStateTrigger());	
			ch.pipeline().addLast("handler", new WebSocketHandler());			
		}
	}
	
	public static void main(String[] args) throws Exception {
		try {
			new WebsocketServer(8000);	
		} catch (Exception e) {
			// TODO: handle exception
		}

	}
}

源码地址:点击打开链接

参考:http://www.360doc.com/content/17/1228/16/51390869_717156716.shtml

           https://www.cnblogs.com/guogangj/p/5209330.html

要在 Netty实现 WebSocket 服务端主动向客户端推送消息,可以使用 `ChannelGroup` 来管理连接到服务器的 WebSocket 客户端的 `Channel`,然后通过遍历 `ChannelGroup` 并将消息写入每个 `Channel` 来实现消息的推送。 下面是一个示例代码,演示了如何在 Netty实现 WebSocket 服务端主动向客户端推送消息: ```java public class WebSocketServerHandler extends SimpleChannelInboundHandler<WebSocketFrame> { private static ChannelGroup channelGroup = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); @Override protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) throws Exception { // 处理 WebSocket 请求 if (frame instanceof TextWebSocketFrame) { // 处理文本消息 String text = ((TextWebSocketFrame) frame).text(); System.out.println("Received message: " + text); // 推送消息给所有连接的客户端 channelGroup.writeAndFlush(new TextWebSocketFrame("Server: " + text)); } else { // 其他类型的消息,如二进制消息、Ping/Pong 消息等 // ... } } @Override public void handlerAdded(ChannelHandlerContext ctx) throws Exception { // 当有客户端连接时,将其添加到 ChannelGroup 中 Channel channel = ctx.channel(); channelGroup.add(channel); } @Override public void handlerRemoved(ChannelHandlerContext ctx) throws Exception { // 当有客户端断开连接时,将其从 ChannelGroup 中移除 Channel channel = ctx.channel(); channelGroup.remove(channel); } // 主动向客户端推送消息的方法 public void pushMessageToClients(String message) { channelGroup.writeAndFlush(new TextWebSocketFrame("Server: " + message)); } } ``` 在上述示例中,我们创建了一个静态的 `ChannelGroup` 对象 `channelGroup`,用于存储连接到服务器的 WebSocket 客户端的 `Channel`。当有客户端连接时,将其添加到 `channelGroup` 中;当客户端断开连接时,将其从 `channelGroup` 中移除。 在处理 WebSocket 请求时,如果收到文本消息,我们可以通过调用 `channelGroup.writeAndFlush()` 方法将消息写入每个客户端的 `Channel` 中,实现消息的推送。 此外,我们还添加了一个名为 `pushMessageToClients()` 的方法,用于在服务端主动向所有客户端推送消息。 你可以在适当的时候调用 `pushMessageToClients()` 方法来推送消息给所有连接的客户端。例如,可以在定时任务或其他事件触发的地方调用该方法来主动向客户端推送消息。 希望对你有所帮助!如果还有其他问题,请继续提问。
评论 42
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值