netty-netty websocket协议实现
摘自<netty权威指南>
http协议弊端
- http 协议采用半双工协议。半双工通信指数据可以在客户端和服务器端上传输,但不能同时传输
- http 消息冗长繁琐
WebSocket协议
websocket
特点:
- 单一的TCP连接,采用全双工模式通信
- 对代理,防火墙和路由器透明
- 无头部信息、cookie 和身份认证
- 无安全开销
- 通过
ping/pong
保持链路激活 - 服务器可以主动传递消息给客户端,不再需要客户端轮询
netty实现websocket协议
服务器端:
public class WebsocketServer {
private int port;
public WebsocketServer(int port) {
this.port = port;
}
public static void main(String[] args) throws InterruptedException {
int port = 8080;
new WebsocketServer(port).start();
}
private void start() throws InterruptedException {
EventLoopGroup boss = new NioEventLoopGroup();
EventLoopGroup work = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(boss, work)
.channel(NioServerSocketChannel.class)
.option(ChannelOption.SO_BACKLOG, 100)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
ch.pipeline().addLast("http-decoder", new HttpServerCodec());
ch.pipeline().addLast("http-aggregate", new HttpObjectAggregator(65536));
ch.pipeline().addLast("http-chunk",new ChunkedWriteHandler());
ch.pipeline().addLast("", new WebSocketServerHandler());
}
});
ChannelFuture future = b.bind(port);
future.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()){
System.out.println("server start success...");
}else {
System.out.println("server start failed...");
}
}
});
future.channel().closeFuture().sync();
} finally {
boss.shutdownGracefully();
work.shutdownGracefully();
}
}
}
WebSocketServerHandler
:
public class WebSocketServerHandler extends SimpleChannelInboundHandler<Object> {
private WebSocketServerHandshaker handshaker;
@Override
protected void channelRead0(ChannelHandlerContext ctx,
Object msg) throws Exception {
if (msg instanceof FullHttpRequest) {
handleHttpRequest(ctx, (FullHttpRequest) msg);
} else if (msg instanceof WebSocketFrame) {
handleWebSocketFrame(ctx, (WebSocketFrame) msg);
}
}
private void handleWebSocketFrame(ChannelHandlerContext ctx,
WebSocketFrame frame) {
if (frame instanceof CloseWebSocketFrame) {
handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
return;
}
if (frame instanceof PingWebSocketFrame) {
ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
return;
}
if (!(frame instanceof TextWebSocketFrame)) {
throw new UnsupportedOperationException(
String.format("%s frame types not supported",
frame.getClass().getName()));
}
String text = ((TextWebSocketFrame) frame).text();
System.out.println(String.format("%s recv %s", ctx.channel(), text));
ctx.channel().write(new TextWebSocketFrame(text
+ ",欢迎使用websocket服务,现在时刻:" + new Date().toString()));
}
private void handleHttpRequest(ChannelHandlerContext ctx,
FullHttpRequest request) {
if (!request.decoderResult().isSuccess() ||
(!"websocket".equals(request.headers().get("Upgrade")))) {
sendHttpResponse(ctx, request, new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
}
WebSocketServerHandshakerFactory factory = new WebSocketServerHandshakerFactory(
"ws://localhost:8080/websocket", null, false
);
handshaker = factory.newHandshaker(request);
if (handshaker == null) {
WebSocketServerHandshakerFactory
.sendUnsupportedVersionResponse(ctx.channel());
} else {
handshaker.handshake(ctx.channel(), request);
}
}
private void sendHttpResponse(ChannelHandlerContext ctx, FullHttpRequest request,
FullHttpResponse response) {
if (response.status().code() != 200) {
ByteBuf byteBuf = Unpooled.copiedBuffer(response.status().toString(),
CharsetUtil.UTF_8);
response.content().writeBytes(byteBuf);
byteBuf.release();
response.headers().set(HttpHeaderNames.CONTENT_LENGTH,
response.content().readableBytes());
}
ChannelFuture future = ctx.writeAndFlush(response);
if (!HttpUtil.isKeepAlive(request) || response.status().code() != 200) {
future.addListener(ChannelFutureListener.CLOSE);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx)
throws Exception {
ctx.flush();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx,
Throwable cause) throws Exception {
cause.printStackTrace();
ctx.close();
}
}
客户端:
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>时间服务器</title>
</head>
<body>
<script type="text/javascript">
var socket;
if (!window.WebSocket){
window.WebSocket = window.MozWebSocket;
}
if (window.WebSocket){
socket = new WebSocket("ws://localhost:8080/websocket");
socket.onmessage = function (event) {
var resp = document.getElementById("responseText");
resp.value = '';
resp.value = event.data;
}
socket.onopen = function (event) {
var txt = document.getElementById("responseText");
txt.value = "打开WebSocket正常,浏览器支持WebSocket!";
}
socket.onclose = function (event) {
var resp = document.getElementById("responseText");
resp.value = '';
resp.value = event.data;
}
}else {
alert("抱歉,您的浏览器不支持WebSocket协议");
}
function sendMsg(message) {
if (!window.WebSocket){
return;
}
if (socket.readyState == WebSocket.OPEN){
socket.send(message);
}else {
alert("WebSocket连接没有成功");
}
}
</script>
<form onsubmit="false">
<input type="text" name="message" value="netty definitive guide">
<br>
<br>
<input type="button" value="send websocket request"
onclick="sendMsg(this.form.message.value)">
<hr color="red">
<h3>服务器响应消息</h3>
<textarea id="responseText" style="width:500px;height: 300px;"></textarea>
</form>
</body>
</html>
运行结果:
server start success...
[id: 0x0a496855, L:/0:0:0:0:0:0:0:1:8080 - R:/0:0:0:0:0:0:0:1:53948]
recv netty definitive guide