使用Netty搭建建议WebSocket服务器

1.导包

		<dependency>
            <groupId>io.netty</groupId>
            <artifactId>netty-all</artifactId>
            <version>4.1.36.Final</version>
        </dependency>

        <!-- io常用工具类 -->
        <dependency>
            <groupId>commons-io</groupId>
            <artifactId>commons-io</artifactId>
            <version>2.5</version>
        </dependency>

        <!-- 阿里JSON解析器 -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>fastjson</artifactId>
            <version>1.2.68</version>
        </dependency>

        <dependency>
            <groupId>commons-codec</groupId>
            <artifactId>commons-codec</artifactId>
            <version>1.10</version>
        </dependency>

2.编写启动类

import netty.NettyServer;

public class SocketService {

    public static void main(String[] args) {
        try {
        	//这里是端口号
            new NettyServer(8089).start();
        }catch(Exception e) {
            System.out.println("NettyServerError:"+e.getMessage());
        }
    }
}

package netty;

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 NettyServer {
    private final int port;

    public NettyServer(int port) {
        this.port = port;
    }

    public void start() throws Exception {
        EventLoopGroup bossGroup = new NioEventLoopGroup();

        EventLoopGroup group = new NioEventLoopGroup();
        try {
            ServerBootstrap sb = new ServerBootstrap();
            sb.option(ChannelOption.SO_BACKLOG, 1024);
            sb.group(group, bossGroup) // 绑定线程池
                    .channel(NioServerSocketChannel.class) // 指定使用的channel
                    .localAddress(this.port)// 绑定监听端口
                    .childHandler(new ChannelInitializer<SocketChannel>() { // 绑定客户端连接时候触发操作

                        @Override
                        protected void initChannel(SocketChannel ch) throws Exception {
                            System.out.println("收到新连接");
                            //websocket协议本身是基于http协议的,所以这边也要使用http解编码器
                            ch.pipeline().addLast(new HttpServerCodec());
                            //以块的方式来写的处理器
                            ch.pipeline().addLast(new ChunkedWriteHandler());
                            ch.pipeline().addLast(new HttpObjectAggregator(8192));
                            ch.pipeline().addLast(new MyWebSocketHandler());
                            ch.pipeline().addLast(new WebSocketServerProtocolHandler("/dongwo", null, true, 65536 * 10));
                        }
                    });
            ChannelFuture cf = sb.bind().sync(); // 服务器异步创建绑定
            System.out.println(NettyServer.class + " 启动正在监听: " + cf.channel().localAddress());
            cf.channel().closeFuture().sync(); // 关闭服务器通道
        } finally {
            group.shutdownGracefully().sync(); // 释放线程池资源
            bossGroup.shutdownGracefully().sync();
        }
    }
}


package netty;

import java.util.concurrent.ConcurrentHashMap;

/**
 * 双向hashMap  按值/键 查找、添加、删除元素对
 * @author liushang
 */
public class BiDirectionHashMap<K,V> {

    //存储用户id和channel的容器   双向hashMap  按值/键 查找、添加、删除元素对
    private ConcurrentHashMap<K, V> k2v; // key -> value
    private ConcurrentHashMap<V, K> v2k; // value -> key

    /**
     * 默认构造函数
     */
    BiDirectionHashMap() {
        this.k2v = new ConcurrentHashMap<>();
        this.v2k = new ConcurrentHashMap<>();
    }

    /**
     * 添加
     * @param k 键
     * @param v 值
     */
    public void put(K k, V v) {
        k2v.put(k, v);
        v2k.put(v, k);
    }

    /**
     * 查看大小
     * @return 大小
     */
    public int size () {
        return k2v.size();
    }

    /**
     * 是否有键
     * @param k 键
     * @return
     */
    public boolean containsKey(K k) {
        return k2v.containsKey(k);
    }

    /**
     * 是否有Value
     * @param v 值
     * @return
     */
    public boolean containsValue(V v) {
        return v2k.containsKey(v);
    }

    /**
     * 通过键删除
     * @param k 键
     * @return
     */
    public boolean removeByKey(K k) {
        if (!k2v.containsKey(k)) {
            return false;
        }

        V value = k2v.get(k);
        k2v.remove(k);
        v2k.remove(value);
        return true;
    }

    /**
     * 通过值删除
     * @param v 值
     * @return
     */
    public boolean removeByValue(V v) {
        if (!v2k.containsKey(v)) {
            return false;
        }

        K key = v2k.get(v);
        v2k.remove(v);
        k2v.remove(key);
        return true;
    }

    /**
     * 通过键获取值
     * @param k
     * @return
     */
    public V getByKey(K k) {
        return k2v.getOrDefault(k, null);
    }

    /**
     * 通过值获取键
     * @param v
     * @return
     */
    public K getByValue(V v) {
        return v2k.getOrDefault(v, null);
    }
}

package netty;

import io.netty.channel.Channel;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.AttributeKey;
import io.netty.util.concurrent.GlobalEventExecutor;
import pojo.User;

/**
 * MyChannelHandlerPool
 * 通道组池,管理所有websocket连接
 * @author liushang
 */
public class MyChannelHandlerPool {

    //客户端组
    public static ChannelGroup channelGroup;

    public static final String USER_INFO = "userInfo";

    static {
        channelGroup=new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);
    }

    public static int i = 0;

    /**
     * 获取在线人数
     * @return
     */
    public static int getOnlyNumsChannelGroup(){
        return channelGroup.size();
    }

    /**
     * 设置通道参数
     */
    public static void setChannelParameter(ChannelHandlerContext channelHandlerContext, String key, Object vaule){
        channelHandlerContext.attr(AttributeKey.valueOf(key)).set(vaule);
    }

    /**
     * 设置通道参数
     */
    public static void setChannelParameter(Channel channel, String key, Object vaule){
        channel.attr(AttributeKey.valueOf(key)).set(vaule);
    }

    /**
     * 取出通道参数
     */
    public static Object getChannelParameter(ChannelHandlerContext channelHandlerContext,String key){

        return getChannelParameter(channelHandlerContext.channel(),key);
    }

    /**
     * 取出通道参数
     */
    public static Object getChannelParameter(Channel channel,String key){
        return channel.attr(AttributeKey.valueOf(key)).get();
    }

    /**
     * 向全体用户推送消息
     * @param message
     */
    public static void sendAllMessage(String message){
        System.out.println("向所有客户端推送消息:"+message+"");
        MyChannelHandlerPool.channelGroup.writeAndFlush( new TextWebSocketFrame(message));
    }

    /**
     * 向指定用户推送消息
     * @param message
     * @param userId
     */
    public static void sendMessgae(String message, Long userId){

        Channel channel = MyChanneMapManager.biDirectionHashMap.getByKey(userId);
        if(channel!=null){
            Object channelParameter = MyChannelHandlerPool.getChannelParameter(channel, MyChannelHandlerPool.USER_INFO);
            User user = new User();
            if(channelParameter!=null){
                user = (User) channelParameter;
            }

            System.err.println("向["+user.getNickName()+"]推送消息:"+message+"");

            channel.writeAndFlush( new TextWebSocketFrame(message));
        }else{
            System.err.println("推送用户不存在");
        }


    }
}

package netty;

import io.netty.channel.Channel;

public class MyChanneMapManager {

    public static BiDirectionHashMap<Long, Channel> biDirectionHashMap = new BiDirectionHashMap<>();

    /**
     * 连接是否存在
     * @param id
     * @return
     */
    public static boolean existConnectionByID (Long id) {
        return biDirectionHashMap.containsKey(id);
    }
}
package netty;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONObject;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.FullHttpRequest;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import pojo.User;
import utils.NetUtils;

import java.util.Map;

public class MyWebSocketHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {


    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("与客户端建立连接,通道开启!");

        //添加到channelGroup通道组
        MyChannelHandlerPool.channelGroup.add(ctx.channel());
    }

    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        System.out.println("与客户端断开连接,通道关闭!");
        //添加到channelGroup 通道组
        MyChannelHandlerPool.channelGroup.remove(ctx.channel());

        MyChanneMapManager.biDirectionHashMap.removeByValue(ctx.channel());
    }

    @Override
    public void channelRead(ChannelHandlerContext ctx, Object msg) throws Exception {

        //首次连接是FullHttpRequest
        if (null != msg && msg instanceof FullHttpRequest) {
            httpdoMessage(ctx,(FullHttpRequest)msg);
        }else if(msg instanceof TextWebSocketFrame){

            textdoMessage(ctx,(TextWebSocketFrame)msg);
        }
        super.channelRead(ctx, msg);
    }

    @Override
    protected void channelRead0(ChannelHandlerContext channelHandlerContext, TextWebSocketFrame textWebSocketFrame) throws Exception {

    }


    private void textdoMessage(ChannelHandlerContext ctx, TextWebSocketFrame textWebSocketFrame){
        JSONObject parse = JSONObject.parseObject(textWebSocketFrame.text());
        System.out.println("后续webSocket参数:"+ parse);

        /**
         *
         * 此处业务逻辑
         *
         *
         */
    }

    protected void httpdoMessage(ChannelHandlerContext ctx, FullHttpRequest fullHttpRequest){
        String uri = fullHttpRequest.uri();

        Map paramMap= NetUtils.getUrlParams(uri);
        System.out.println("首次http连接参数是:"+ JSON.toJSONString(paramMap));

        long i = MyChannelHandlerPool.i;

        User user = new User();
        user.setId(i);
        user.setNickName(i+"号用户");

        MyChannelHandlerPool.setChannelParameter(ctx,MyChannelHandlerPool.USER_INFO,user);

        if(!MyChanneMapManager.existConnectionByID(i)){
            MyChanneMapManager.biDirectionHashMap.put(i,ctx.channel());
        }

        System.err.println("当前用户编号:"+i);

        MyChannelHandlerPool.i++;

        //如果url包含参数,需要处理
        if(uri.contains("?")){
            String newUri=uri.substring(0,uri.indexOf("?"));
            System.out.println(newUri);
            fullHttpRequest.setUri(newUri);
        }

        System.out.println("当前在线人数:"+MyChannelHandlerPool.getOnlyNumsChannelGroup()+";"+MyChanneMapManager.biDirectionHashMap.size());
    }
}

package utils;

import java.util.HashMap;
import java.util.Map;

public class NetUtils {

    /**
     * 获取HTTP请求路径参数
     * @param url
     * @return
     */
    public static Map getUrlParams(String url){
        Map<String,String> map = new HashMap<String,String>();
        url = url.replace("?",";");
        if (!url.contains(";")){
            return map;
        }
        if (url.split(";").length > 0){
            String[] arr = url.split(";")[1].split("&");
            for (String s : arr){
                String key = s.split("=")[0];
                String value = s.split("=")[1];
                map.put(key,value);
            }
            return  map;

        }else{
            return map;
        }
    }
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值