netty+websocket

本文介绍如何使用 Netty 框架实现 WebSocket 服务端应用,包括 Maven 工程搭建、依赖配置、服务端启动类编写及消息处理逻辑。同时提供 HTML 客户端测试页面。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

以前写过一片关于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="" onsubmit="return false;">
            <input type="text" name="message" value="..."/>
            <br>
            <input type="button" value="send" value="发送websocket请求消息" onclick="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>



评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值