以前写过一片关于springmvc的websocket 由于性能考虑 写一篇 netty+websocket
1,什么是netty,怎么搭建netty?
自己去百度点击打开链接
2,搭建maven工程,pom.xml依赖
<dependency><groupId>io.netty</groupId>
<artifactId>netty-all</artifactId>
<version>5.0.0.Alpha2</version>
</dependency>
3,netty 的 websocketServer搭建
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.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.stream.ChunkedWriteHandler;
public class websocketServer{
public void run(int port){
EventLoopGroup bossGroup = new NioEventLoopGroup();
EventLoopGroup workerGroup = new NioEventLoopGroup();
try {
ServerBootstrap b = new ServerBootstrap();
b.group(bossGroup, workerGroup)
.channel(NioServerSocketChannel.class)
.childHandler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(SocketChannel ch) throws Exception {
//HttpServerCodec将请求和应答消息编码或解码为HTTP消息
//通常接收到的http是一个片段,如果想要完整接受一次请求所有数据,我们需要绑定HttpObjectAggregator
//然后就可以收到一个FullHttpRequest完整的请求信息了
//ChunkedWriteHandler 向客户端发送HTML5文件,主要用于支持浏览器和服务器进行WebSocket通信
//WebSocketServerHandler自定义Handler
ch.pipeline().addLast("http-codec", new HttpServerCodec())
.addLast("aggregator", new HttpObjectAggregator(65536)) //定义缓冲大小
.addLast("http-chunked", new ChunkedWriteHandler())
.addLast("handler", new WebSocketHandler());
}
});
ChannelFuture f = b.bind(port).sync();
System.out.println("start...");
f.channel().closeFuture().sync();
} catch (Exception e) {
e.printStackTrace();
} finally {
workerGroup.shutdownGracefully();
bossGroup.shutdownGracefully();
}
}
public static void main(String[] args) {
new websocketServer().run(7777);
}
}
4,WebSocketHandler搭建
import Demo_netty.Demo_netty.Constant;
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.handler.codec.http.DefaultFullHttpResponse;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.FullHttpResponse;
import io.netty.handler.codec.http.HttpHeaderUtil;
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.PingWebSocketFrame;
import io.netty.handler.codec.http.websocketx.PongWebSocketFrame;
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.AttributeKey;
import io.netty.util.CharsetUtil;
import io.netty.util.ResourceLeakDetector.Level;
public class WebSocketHandler extends BaseWebSocketServerHandler{
/**
* 全局websocket
*/
private WebSocketServerHandshaker handshaker;
@Override
protected void messageReceived(ChannelHandlerContext ctx, Object msg)
throws Exception {
//普通HTTP接入
if(msg instanceof FullHttpRequest){
System.out.println("普通HTTP接入");
handleHttpRequest(ctx, (FullHttpRequest) msg);
}else if(msg instanceof WebSocketFrame){ //websocket帧类型 已连接
//BinaryWebSocketFrame CloseWebSocketFrame ContinuationWebSocketFrame
//PingWebSocketFrame PongWebSocketFrame TextWebScoketFrame
System.out.println("websocket帧类型 已连接");
handleWebSocketFrame(ctx, (WebSocketFrame) msg);
}
}
@Override
public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
ctx.flush();
}
private void handleHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request){
//如果http解码失败 则返回http异常 并且判断消息头有没有包含Upgrade字段(协议升级)
if(!request.decoderResult().isSuccess()
|| (!"websocket".equals( request.headers().get("Upgrade"))) ){
sendHttpResponse(ctx, request, new DefaultFullHttpResponse(
HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
return ;
}
//构造握手响应返回
WebSocketServerHandshakerFactory ws = new WebSocketServerHandshakerFactory("", null, false);
handshaker = ws.newHandshaker(request);
if(handshaker == null){
//版本不支持
WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
}else{
handshaker.handshake(ctx.channel(), request);
}
}
/**
* websocket帧
* @param ctx
* @param frame
*/
private void handleWebSocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame){
//判断是否关闭链路指令
if(frame instanceof CloseWebSocketFrame){
handshaker.close(ctx.channel(), (CloseWebSocketFrame) frame.retain());
return ;
}
//判断是否Ping消息 -- ping/pong心跳包
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()));
}
//返回应答消息 text文本帧
String request = ((TextWebSocketFrame) frame).text();
//打印日志
/*if(logger.isLoggable(Level.FINE)){
logger.fine(String.format("%s received %s", ctx.channel(), request));
}*/
//发送到客户端websocket
push(ctx, request
+ ", 欢迎使用Netty WebSocket服务, 现在时刻:"
+ new java.util.Date().toString());
}
/**
* response
* @param ctx
* @param request
* @param response
*/
private static void sendHttpResponse(ChannelHandlerContext ctx,
FullHttpRequest request, FullHttpResponse response){
//返回给客户端
if(response.status().code() != HttpResponseStatus.OK.code()){
ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);
response.content().writeBytes(buf);
buf.release();
HttpHeaderUtil.setContentLength(response, response.content().readableBytes());
}
//如果不是keepalive那么就关闭连接
ChannelFuture f = ctx.channel().writeAndFlush(response);
if(!HttpHeaderUtil.isKeepAlive(response)
|| response.status().code() != HttpResponseStatus.OK.code()){
f.addListener(ChannelFutureListener.CLOSE);
}
}
/**
* 异常 出错
*/
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause)
throws Exception {
cause.printStackTrace();
ctx.close();
}
@Override
public void channelActive(ChannelHandlerContext ctx) throws Exception {
System.out.println(ctx.channel().id());
// 添加
System.out.println("客户端与服务端连接开启");
Constant.AddpushCtxMap(ctx);
pushAll("新用户上线");
pushAll("共"+Constant.pushCtxMap.size()+"人");
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
//LoginBean loginCurrent = NettyChannelMap.get("" + ctx.channel().id());
/*//用户离开通知
for (String channelHash : NettyChannelMap.map.keySet()) {
LoginBean loginBean = NettyChannelMap.map.get(channelHash);
if(loginBean.getGameId().equals(loginCurrent.getGameId())){
// 向客户端发送消息
String response = "0103" + "0" + loginCurrent.getNickName();
BaseHandle.responseText(loginBean.getChannel() , response);
}
}*/
// 移除
Constant.RemovepushCtxMap(ctx);
pushAll("有人下线");
System.out.println("客户端与服务端连接关闭:" + ctx.channel().id());
}
}
5,WebSocketHandler的父类搭建,将推送方法封装,便于调用
import Demo_netty.Demo_netty.Constant;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.channel.group.ChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
public abstract class BaseWebSocketServerHandler extends SimpleChannelInboundHandler<Object>{
/**
* 推送单个
*
* */
public static final void push(final ChannelHandlerContext ctx,final String message){
TextWebSocketFrame tws = new TextWebSocketFrame(message);
ctx.channel().writeAndFlush(tws);
}
/**
* 群发
*
* */
public static final void pushAll(final String message){
System.out.println("pushCtxMap="+Constant.pushCtxMap.size());
for (String id : Constant.pushCtxMap.keySet()) {
push(Constant.pushCtxMap.get(id),message);
}
}
}
6,Constant类定义 (存放所有的ChannelHandlerContext,用于群体推送,封装add,和remove方法)
import io.netty.channel.ChannelHandlerContext;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
/**
* 常量
* */
public class Constant {
//存放所有的ChannelHandlerContext
public static Map<String, ChannelHandlerContext> pushCtxMap = new ConcurrentHashMap<String, ChannelHandlerContext>() ;
public static void AddpushCtxMap(ChannelHandlerContext ctx){
pushCtxMap.put(ctx.channel().id().toString(),ctx );
}
public static void RemovepushCtxMap(ChannelHandlerContext ctx){
for (String id : pushCtxMap.keySet()) {
if (ctx.channel().id().toString().equals(id)) {
pushCtxMap.remove(id);
break;
}
}
}
}
7,html测试(纯html即可,无需框架,先启动websocketServer的main方法,开服务)
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<title>Netty websocket 时间服务器</title>
</head>
<body>
<form action="" οnsubmit="return false;">
<input type="text" name="message" value="..."/>
<br>
<input type="button" value="send" value="发送websocket请求消息" οnclick="send(this.form.message.value);" />
<hr color="blue">
<h3>服务器返回信息</h3>
<textarea id="responseText" rows="10" cols=""></textarea>
</form>
</body>
<script type="text/javascript">
var socket;
if(!window.WebSocket){
window.WebSocket = window.MozWebSocket;
}
if(window.WebSocket){
socket = new WebSocket("ws://localhost:7777/websocket");
socket.onmessage = function(event){
var ta = document.getElementById('responseText');
//ta.value="";
ta.value=ta.value+event.data;
};
socket.onopen = function(event){
var ta = document.getElementById('responseText');
ta.value = ta.value+"打开websocket服务正常";
}
socket.onclose = function(event){
var ta = document.getElementById('responseText');
ta.value="";
ta.value="websocket关闭";
}
}else{
alert("对不起,您的浏览器不支持WebSocket.");
}
function send(message){
if(!window.WebSocket){
return ;
}
if(socket.readyState == WebSocket.OPEN){
socket.send(message);
}else{
alert("WebSocket 连接创建失败.");
}
}
</script>
</html>