背景

利用netty创建服务端,然后与websocket建立连接,发现一直出现能够接收到前端的请求,但是无法建立正确的websocket连接。

正确代码如下

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {
System.out.println("收到客户端消息,此时一共有多少个用户建立连接:" + NettyServer.user.keySet());
if (msg instanceof FullHttpRequest) {
FullHttpRequest request = (FullHttpRequest) msg;
if (!request.decoderResult().isSuccess() || !"websocket".equals(request.headers().get("Upgrade"))) {
System.out.println("提前结束");
ctx.channel().close();
return;
}
String uri = request.uri();
QueryStringDecoder decoder = new QueryStringDecoder(uri);
Map<String, List<String>> parameters = decoder.parameters();

String userId = parameters.get("uid").toString();
String newUri = uri.substring(0,uri.indexOf("?"));
// 保存信息
NettyServer.user.put(userId, ctx.channel());
/**
* 这里就是一切问题的根源。因为请求的uri是/ws?uid=666&amp;gid=777
* 而我定义的new WebSocketServerProtocolHandler("/ws", null, true, 65536 * 10)
* 只能处理/ws,所以这里要重新设定uri
*/
request.setUri(newUri);
} else if (msg instanceof TextWebSocketFrame) {
channelRead0(ctx, (TextWebSocketFrame) msg);
}

// 这里,就是将后续的请求转发给下一个handler,也就是我定义的
// WebSocketServerProtocolHandler("/ws", null, true, 65536 * 10)
// 去建立连接
super.channelRead(ctx, msg);
}

netty创建的代码如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
public void start() throws InterruptedException {
// 创建两个线程组boosGroup和workerGroup,含有的子线程NioEventLoop的个数默认为cpu核数的两倍
EventLoopGroup boosGroup = new NioEventLoopGroup();
// boosGroup只是处理链接请求,真正的和客户端业务处理,会交给workerGroup完成
EventLoopGroup workerGroup = new NioEventLoopGroup();
// 服务端的启动对象
ServerBootstrap bootstrap = new ServerBootstrap();
// 设置两个线程组
bootstrap.group(boosGroup, workerGroup)
// 设置对应的channel
.channel(NioServerSocketChannel.class)
.localAddress(1000)
// 初始化服务器链接队列大小,服务端处理客户端链接请求是顺序处理的,所以同一时间只能处理一个客户端链接
// 多个客户端同时来的时候,服务端将不能处理的客户端链接请求放在队列中等待处理
.option(ChannelOption.SO_BACKLOG, 1024)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
// websocket协议本身是基于http协议的,所以这边也要使用http解编码器
ch.pipeline().addLast(new HttpServerCodec());
// 以块的方式来写的处理器
ch.pipeline().addLast(new ChunkedWriteHandler());
// 对http消息做聚合操作,返回一个FullHttpRequest 和FullHttpResponse
ch.pipeline().addLast(new HttpObjectAggregator(1024 * 64));
// 添加测试的聊天消息处理类,即添加自定义的handler
ch.pipeline().addLast(new HttpRequestHandler());
// 这里是添加支持websocket协议
ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536 * 10));
}
});
System.out.println("netty server start..");

// 绑定一个端口并且同步,生成一个ChannelFuture异步对象,通过isDone()等方法判断异步事件的执行情况
// 启动服务器(并绑定端口),bind是异步操作,sync方法是等待异步操作执行完毕
ChannelFuture cf = bootstrap.bind(this.port).sync();
// 给cf注册监听器,监听我们关心的事件
cf.addListener((ChannelFutureListener) channelFuture -> {
if (cf.isSuccess()){
System.out.println("监听端口成功");
}else {
System.out.println("监听端口失败");
}
});

cf.channel().closeFuture().sync();
boosGroup.shutdownGracefully();
workerGroup.shutdownGracefully();
}

原因分析

ch.pipeline().addLast(new WebSocketServerProtocolHandler("/ws", null, true, 65536 * 10));这一行代码,表示让netty自动去完成websocket握手,而且支持的路径为/ws

但是由于前端建立连接时拼接了参数,也就导致了request的uri为/ws?uid=xxx&oid=xxx。这也就意味着自动握手是无法匹配该路径的。

两种解决方案:

  • 第一种就是我们手动的去写对应的握手代码。

  • 第二种就是上边写道的解决方案

    1
    2
    3
    4
    5
    6
    // 重新设置uri
    String newUri = uri.substring(0,uri.indexOf("?"));
    request.setUri(newUri);

    // 调用父类的方法后,会将请求交由下一个handler处理,也就是我们创建的完成握手的WebSocketServerProtocolHandler
    super.channelRead(ctx, msg);