服务端要实现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自带的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() {
@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
将demo封装成一个解码器
public class WSClientHandler extends MessageToMessageDecoder
代码解释:
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 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());