Netty + Redis + Websocket IM 分布式集群实现

 Netty + Redis + Websocket IM 分布式集群实现_第1张图片

常量表
Constants.java 

/**
 * 项目中的全局常量定义
 */
public interface Constants {

    String WEBSOCKET_STR = "websocket";
    String UPGRADE_STR = "Upgrade";
    int OK_CODE = 200;

    String HTTP_CODEC = "http-codec";
    String AGGREGATOR = "aggregator";
    String HTTP_CHUNKED = "http-chunked";
    String HANDLER = "handler";
    int MAX_CONTENT_LENGTH = 65536;
    int PORT = 8989;

    String WEB_SOCKET_URL = "ws://localhost:"+PORT+"/ws";

    //订阅者列表
    String IM_QUEUE_CHANNLID = "im-queue-channlid";
}

配置类:

NettyConfig.java

import io.netty.channel.group.ChannelGroup;
import io.netty.channel.group.DefaultChannelGroup;
import io.netty.util.concurrent.GlobalEventExecutor;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @program: Netty-WebSocket
 * @description: 工程的全局配置类
 **/
public class NettyConfig {

    /**
     * 存储每一个客户端接入进来时的channel对象
     */
    public final static ChannelGroup GROUP = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE);

    /**
     * 本地存储一份map 
     */
    public final static Map LOCALCHANNELMAP = new ConcurrentHashMap<>();

    /**
     * 本地存储一份map   送达到key,channel
     */
    public final static Map> LOCALCHANNELLISTMAP = new ConcurrentHashMap<>();

}

 

收发消息 handler

MyWebsocketHandler.java

import com.xx.im.api.WebsocketServer;
import com.xx.im.api.config.Constants;
import com.xx.im.api.config.NettyConfig;
import com.xx.im.api.redis.JedisUtil;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import io.netty.channel.*;
import io.netty.handler.codec.http.*;
import io.netty.handler.codec.http.websocketx.*;
import io.netty.util.CharsetUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.util.Date;

/**
 * @program: Netty-WebSocket
 * @description: 接收处理并响应客户端WebSocket请求的核心业务处理类
 **/
@ChannelHandler.Sharable
public class MyWebsocketHandler extends SimpleChannelInboundHandler {

    private static Logger log = LoggerFactory.getLogger(MyWebsocketHandler.class);

    private WebSocketServerHandshaker handshaker;

    /**
     * 服务端处理客户端WebSocket请求的核心方法
     *
     * @param ctx ctx
     * @param msg msg
     * @throws Exception Exception
     */
    @Override
    protected void channelRead0(ChannelHandlerContext ctx, Object msg) throws Exception {
        // 处理客户端向服务端发起http握手请求的业务
        if (msg instanceof FullHttpRequest) {
            handHttpRequest(ctx, (FullHttpRequest) msg);
        }
        // 处理websocket连接
        else if (msg instanceof WebSocketFrame) {
            handWebsocketFrame(ctx, (WebSocketFrame) msg);
        }
    }

    /**
     * 处理客户端与服务端之间的websocket业务
     *
     * @param ctx   ctx
     * @param frame frame
     */
    private void handWebsocketFrame(ChannelHandlerContext ctx, WebSocketFrame frame) {
        // 判断是否是关闭websocket的指令
        if (frame instanceof CloseWebSocketFrame) {
            handshaker.close(ctx.channel(), ((CloseWebSocketFrame) frame).retain());
            log.debug("接收到关闭websocket的指令");
        }

        // 判断是否是ping消息
        if (frame instanceof PingWebSocketFrame) {
            ctx.channel().write(new PongWebSocketFrame(frame.content().retain()));
            log.debug("接收到ping消息");
            return;
        }

        // 判断是否是二进制消息,如果是二进制消息,则抛出异常
        if (!(frame instanceof TextWebSocketFrame)) {
            log.error("目前不支持二进制消息");
            throw new UnsupportedOperationException("【" + this.getClass().getName() + "】不支持的消息");
        }

        // 获取客户端向服务端发送的消息
        String requestStr = ((TextWebSocketFrame) frame).text();
        log.debug("服务端收到客户端的消息: {}", requestStr);


        // 那个客户端发来的,继续返回给那个客户端
        //Channel channel = NettyConfig.GROUP.find(ctx.channel().id());
        //channel.writeAndFlush(tws);

        // 发布到redis 订阅列表中,进行广播
        String keychannel = ctx.channel().id().asLongText();
        ChannelId id = ctx.channel().id();
        JedisUtil.set(keychannel,requestStr);
        JedisUtil.set(keychannel+"Id",id);
        JedisUtil.pushMsg(keychannel);


        // 返回应答消息
//        String responseStr = new Date().toString()  + ctx.channel().id() +  " ===>>> " + requestStr;
//        TextWebSocketFrame tws = new TextWebSocketFrame(responseStr);
        // 群发,服务端向每个连接上来的客户端群发消息
        //NettyConfig.GROUP.writeAndFlush(tws);

//        log.debug("群发消息完成. 群发的消息为: {}", responseStr);
    }

    /**
     * 处理客户端向服务端发起http握手请求的业务
     *
     * @param ctx     ctx
     * @param request request
     */
    private void handHttpRequest(ChannelHandlerContext ctx, FullHttpRequest request) {
        String upgrade = request.headers().get(Constants.UPGRADE_STR);
        // 非websocket的http握手请求处理
        if (!request.decoderResult().isSuccess() || !Constants.WEBSOCKET_STR.equals(upgrade)) {
            sendHttpResponse(ctx, request,
                    new DefaultFullHttpResponse(HttpVersion.HTTP_1_1, HttpResponseStatus.BAD_REQUEST));
            log.warn("非websocket的http握手请求");
            return;
        }

        WebSocketServerHandshakerFactory wsFactory =  new WebSocketServerHandshakerFactory(Constants.WEB_SOCKET_URL, null, false);
        handshaker = wsFactory.newHandshaker(request);
        if (handshaker == null) {
            // 响应不支持的请求
            WebSocketServerHandshakerFactory.sendUnsupportedVersionResponse(ctx.channel());
            log.warn("不支持的请求");
        } else {
            handshaker.handshake(ctx.channel(), request);
            log.debug("正常处理");
        }
    }

    /**
     * 服务端主动向客户端发送消息
     *
     * @param ctx      ctx
     * @param request  request
     * @param response response
     */
    private void sendHttpResponse(ChannelHandlerContext ctx,
                                  FullHttpRequest request,
                                  DefaultFullHttpResponse response) {
        // 不成功的响应
        if (response.status().code() != Constants.OK_CODE) {
            ByteBuf buf = Unpooled.copiedBuffer(response.status().toString(), CharsetUtil.UTF_8);
            response.content().writeBytes(buf);
            buf.release();
            log.warn("不成功的响应");
        }

        // 服务端向客户端发送数据
        ChannelFuture channelFuture = ctx.channel().writeAndFlush(response);
        if (!HttpUtil.isKeepAlive(request) ||
                response.status().code() != Constants.OK_CODE) {
            // 如果是非Keep-Alive,或不成功都关闭连接
            channelFuture.addListener(ChannelFutureListener.CLOSE);
            log.info("websocket连接关闭");
        }
    }

    /**
     * 客户端与服务端创建连接的时候调用
     *
     * @param ctx ctx
     * @throws Exception Exception
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        // 将channel添加到channel group中
        NettyConfig.GROUP.add(ctx.channel());
        NettyConfig.LOCALCHANNELMAP.put( ctx.channel().id().asLongText(),"1");//存在,并且在线
        log.info("客户端与服务端连接开启...");
    }

    /**
     * 客户端与服务端断开连接的时候调用
     *
     * @param ctx ctx
     * @throws Exception Exception
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        // 从channel group中移除这个channel
        NettyConfig.GROUP.remove(ctx.channel());
        NettyConfig.LOCALCHANNELMAP.remove(ctx.channel().id().asLongText());// 不存在,离线
        log.info("客户端与服务端关闭连接...");
    }

    /**
     * 服务端接收客户端发送过来的数据结束之后调用
     *
     * @param ctx ctx
     * @throws Exception Exception
     */
    @Override
    public void channelReadComplete(ChannelHandlerContext ctx) throws Exception {
        // 清空数据
        ctx.flush();

        log.info("flush数据 {}{}", ctx.name(),ctx.channel().id().asLongText());
    }

    /**
     * 工程出现异常的时候调用
     *
     * @param ctx   ctx
     * @param cause cause
     * @throws Exception Exception
     */
    @Override
    public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
        // 打印异常堆栈
        cause.printStackTrace();
        // 主动关闭连接
        ctx.close();
        log.error("WebSocket连接异常");
    }
} 
  
初始化通道 
MyWebsocketInitializer.java

import com.xx.im.api.config.Constants;
import io.netty.channel.ChannelInitializer;
import io.netty.channel.socket.SocketChannel;
import io.netty.handler.codec.http.HttpObjectAggregator;
import io.netty.handler.codec.http.HttpServerCodec;
import io.netty.handler.stream.ChunkedWriteHandler;

public class MyWebsocketInitializer extends ChannelInitializer {
    @Override
    protected void initChannel(SocketChannel ch) throws Exception {
        ch.pipeline().addLast(Constants.HTTP_CODEC, new HttpServerCodec());
        ch.pipeline().addLast(Constants.AGGREGATOR, new HttpObjectAggregator(Constants.MAX_CONTENT_LENGTH));
        ch.pipeline().addLast(Constants.HTTP_CHUNKED, new ChunkedWriteHandler());
        ch.pipeline().addLast(Constants.HANDLER, new MyWebsocketHandler());
    }
}

redis 工具类

JedisUtil.java

import com.alibaba.fastjson.JSON;
import com.dyuproject.protostuff.LinkedBuffer;
import com.dyuproject.protostuff.ProtostuffIOUtil;
import com.dyuproject.protostuff.runtime.RuntimeSchema;
import com.xx.im.api.config.Constants;
import redis.clients.jedis.GeoCoordinate;
import redis.clients.jedis.GeoRadiusResponse;
import redis.clients.jedis.GeoUnit;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.exceptions.JedisDataException;
import redis.clients.jedis.params.geo.GeoRadiusParam;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

public class JedisUtil {

    private static final int DEFAULT_SETEX_TIMEOUT = 60 * 60;// setex的默认时间

    public static Jedis jedis ;
    public static Jedis jedisSub ;

    public static void init(){
        try {
            new Thread(JedisUtil::run).start();
            System.out.println("im init ");
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    private static void run() {
        jedis = new Jedis("192.168.9.135");
        jedis.auth("xxxx");// 设置密码
        jedis.connect();

        jedisSub = new Jedis("192.168.9.135");
        jedisSub.auth("xxxx");// 设置密码
        RedisMsgPubSubListener listener = new RedisMsgPubSubListener();
        jedisSub.subscribe(listener, Constants.IM_QUEUE_CHANNLID);
    }

    public static void pushMsg(String msg){
        jedis.publish(Constants.IM_QUEUE_CHANNLID, msg);
    }


    /**
     * 添加一个字符串值,成功返回1,失败返回0
     *
     * @param key
     * @param value
     * @return
     */
    public static int set(String key, String value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        try {
            
            if (jedis.set(key, value).equalsIgnoreCase("ok")) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 缓存一个字符串值,成功返回1,失败返回0,默认缓存时间为1小时,以本类的常量DEFAULT_SETEX_TIMEOUT为准
     *
     * @param key
     * @param value
     * @return
     */
    public static int setEx(String key, String value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            if (jedis.setex(key, DEFAULT_SETEX_TIMEOUT, value).equalsIgnoreCase("ok")) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 缓存一个字符串值,成功返回1,失败返回0,缓存时间以timeout为准,单位秒
     *
     * @param key
     * @param value
     * @param timeout
     * @return
     */
    public static int setEx(String key, String value, int timeout) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            if (jedis.setex(key, timeout, value).equalsIgnoreCase("ok")) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 添加一个指定类型的对象,成功返回1,失败返回0
     *
     * @param key
     * @param value
     * @return
     */
    public static  int set(String key, T value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            byte[] data = enSeri(value);
            if (jedis.set(key.getBytes(), data).equalsIgnoreCase("ok")) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 缓存一个指定类型的对象,成功返回1,失败返回0,默认缓存时间为1小时,以本类的常量DEFAULT_SETEX_TIMEOUT为准
     *
     * @param key
     * @param value
     * @return
     */
    public static  int setEx(String key, T value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            byte[] data = enSeri(value);
            if (jedis.setex(key.getBytes(), DEFAULT_SETEX_TIMEOUT, data).equalsIgnoreCase("ok")) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 缓存一个指定类型的对象,成功返回1,失败返回0,缓存时间以timeout为准,单位秒
     *
     * @param key
     * @param value
     * @param timeout
     * @return
     */
    public static  int setEx(String key, T value, int timeout) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            byte[] data = enSeri(value);
            if (jedis.setex(key.getBytes(), timeout, data).equalsIgnoreCase("ok")) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 将一个数值+1,成功返回+后的结果,失败返回null
     *
     * @param key
     * @return
     * @throws JedisDataException
     */
    public static Long incr(String key) throws JedisDataException {
        if (isValueNull(key)) {
            return null;
        }
        
        try {
            
            return jedis.incr(key);
        } finally {
            
        }
    }

    /**
     * 将一个数值+n,成功返回+后的结果,失败返回null
     *
     * @param key
     * @return
     * @throws JedisDataException
     */
    public static Long incrBy(String key, long integer) throws JedisDataException {
        if (isValueNull(key)) {
            return null;
        }
        
        try {
            
            return jedis.incrBy(key,integer);
        } finally {
            
        }
    }

    /**
     * 将一个数值-1,成功返回-后的结果,失败返回null
     *
     * @param key
     * @return
     * @throws JedisDataException
     */
    public static Long decr(String key) throws JedisDataException {
        if (isValueNull(key)) {
            return null;
        }
        
        try {
            
            return jedis.decr(key);
        } finally {
            
        }
    }

    /**
     * 将一个数值-n,成功返回-后的结果,失败返回null
     *
     * @param key
     * @return
     * @throws JedisDataException
     */
    public static Long decrBy(String key,long integer) throws JedisDataException {
        if (isValueNull(key)) {
            return null;
        }
        
        try {
            
            return jedis.decrBy(key, integer);
        } finally {
            
        }
    }

    /**
     * 添加一个字符串值到list中,,成功返回1,失败返回0
     *
     * @param key
     * @param value
     * @return
     */
    public static int setList(String key, String... value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            Long result = jedis.rpush(key, value);
            if (result != null && result != 0) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 缓存一个字符串值到list中,全部list的key默认缓存时间为1小时,成功返回1,失败返回0
     *
     * @param key
     * @param value
     * @return
     */
    public static int setExList(String key, String... value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            Long result = jedis.rpush(key, value);
            jedis.expire(key, DEFAULT_SETEX_TIMEOUT);
            if (result != null && result != 0) {
                return 1;
            } else {
                return 0;
            }

        } finally {
            
        }
    }

    /**
     * 缓存一个字符串值到list中,全部list的key缓存时间为timeOut,单位为秒,成功返回1,失败返回0
     *
     * @param key
     * @param value
     * @return
     */
    public static int setExList(String key, int timeOut, String... value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            Long result = jedis.rpush(key, value);
            jedis.expire(key, timeOut);
            if (result != null && result != 0) {
                return 1;
            } else {
                return 0;
            }

        } finally {
            
        }
    }

    /**
     * 添加一个类型对象值到list中,成功返回1,失败返回0
     *
     * @param key
     * @param value
     * @return
     */
    @SafeVarargs
    public static  int setList(String key, T... value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            int res = 0;
            for (T t : value) {
                byte[] data = enSeri(t);
                Long result = jedis.rpush(key.getBytes(), data);
                if (result != null && result != 0) {
                    res++;
                }
            }
            if (res != 0) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 缓存一个类型对象值到list中,全部list的key默认缓存时间为1小时,成功返回1,失败返回0
     *
     * @param key
     * @param value
     * @return
     */
    @SafeVarargs
    public static  int setExList(String key, T... value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            int res = 0;
            for (T t : value) {
                byte[] data = enSeri(t);
                Long result = jedis.rpush(key.getBytes(), data);
                if (result != null && result != 0) {
                    res++;
                }
            }
            jedis.expire(key, DEFAULT_SETEX_TIMEOUT);
            if (res != 0) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 缓存一个类型对象值到list中,全部list的key缓存时间为timeOut,单位秒,成功返回1,失败返回0
     *
     * @param key
     * @param value
     * @return
     */
    @SafeVarargs
    public static  int setExList(String key, int timeOut, T... value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            int res = 0;
            for (T t : value) {
                byte[] data = enSeri(t);
                Long result = jedis.rpush(key.getBytes(), data);
                if (result != null && result != 0) {
                    res++;
                }
            }
            jedis.expire(key, timeOut);
            if (res != 0) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 添加一个List集合成功返回1,失败返回0
     *
     * @param key
     * @param value
     * @return
     * @throws IOException
     * @throws RuntimeException
     */
    public static  int setList(String key, List value) throws RuntimeException, IOException {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            byte[] data = enSeriList(value);
            if (jedis.set(key.getBytes(), data).equalsIgnoreCase("ok")) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 缓存一个List集合,成功返回1,失败返回0,默认缓存时间为1小时,以本类的常量DEFAULT_SETEX_TIMEOUT为准
     *
     * @param key
     * @param value
     * @return
     * @throws IOException
     * @throws RuntimeException
     */

    public static  int setExList(String key, List value) throws RuntimeException, IOException {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            byte[] data = enSeriList(value);
            if (jedis.setex(key.getBytes(), DEFAULT_SETEX_TIMEOUT, data).equalsIgnoreCase("ok")) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 缓存一个List集合,成功返回1,失败返回0,缓存时间以timeout为准,单位秒
     *
     * @param key
     * @param value
     * @param timeout
     * @return
     * @throws IOException
     * @throws RuntimeException
     */
    public static  int setExList(String key, List value, int timeout) throws RuntimeException, IOException {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            byte[] data = enSeriList(value);
            if (jedis.setex(key.getBytes(), timeout, data).equalsIgnoreCase("ok")) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 添加一个字符串到set,如果key存在就在就最追加,如果key不存在就创建,成功返回1,失败或者没有受影响返回0
     *
     * @param key
     * @param value
     * @return
     */
    public static int setSet(String key, String... value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            Long result = jedis.sadd(key, value);
            if (result != null && result != 0) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 添加一个字符串set,如果key存在就在就最追加,整个set的key默认一小时后过期,如果key存在就在可以种继续添加,如果key不存在就创建,成功返回1,失败或者没有受影响返回0
     *
     * @param key
     * @param value
     * @return
     */
    public static int setExSet(String key, String... value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            Long result = jedis.sadd(key, value);
            jedis.expire(key, DEFAULT_SETEX_TIMEOUT);
            if (result != null && result != 0) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 添加一个字符串set,如果key存在就在就最追加,整个set的key有效时间为timeOut时间,单位秒,如果key存在就在可以种继续添加,如果key不存在就创建,,成功返回1,失败或者没有受影响返回0
     *
     * @param key
     * @param value
     * @return
     */
    public static int setExSet(String key, int timeOut, String... value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            Long result = jedis.sadd(key, value);
            jedis.expire(key, timeOut);
            if (result != null && result != 0) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 添加一个类型到set集合,如果key存在就在就最追加,成功返回1,失败或者没有受影响返回0
     *
     * @param key
     * @param value
     * @return
     */
    @SafeVarargs
    public static  int setSet(String key, T... value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            int res = 0;
            for (T t : value) {
                byte[] data = enSeri(t);
                Long result = jedis.sadd(key.getBytes(), data);
                if (result != null && result != 0) {
                    res++;
                }
            }
            if (res != 0) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 缓存一个类型到set集合,如果key存在就在就最追加,整个set的key默认有效时间为1小时,成功返回1,失败或者没有受影响返回0
     *
     * @param key
     * @param value
     * @return
     */
    @SafeVarargs
    public static  int setExSet(String key, T... value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            int res = 0;
            for (T t : value) {
                byte[] data = enSeri(t);
                Long result = jedis.sadd(key.getBytes(), data);
                if (result != null && result != 0) {
                    res++;
                }
            }
            jedis.expire(key, DEFAULT_SETEX_TIMEOUT);
            if (res != 0) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 缓存一个类型到set集合,如果key存在就在就最追加,整个set的key有效时间为timeOut,单位秒,成功返回1,失败或者没有受影响返回0
     *
     * @param key
     * @param value
     * @return
     */
    @SafeVarargs
    public static  int setExSet(String key, int timeOut, T... value) {
        if (isValueNull(key, value)) {
            return 0;
        }
        
        try {
            
            int res = 0;
            for (T t : value) {
                byte[] data = enSeri(t);
                Long result = jedis.sadd(key.getBytes(), data);
                if (result != null && result != 0) {
                    res++;
                }
            }
            jedis.expire(key, timeOut);
            if (res != 0) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 添加一个Map集合,成功返回1,失败返回0
     *
     * @param key
     * @param value
     * @return
     */
    public static  int setMap(String key, Map value) {
        if (value == null || key == null || "".equals(key)) {
            return 0;
        }
        
        try {
            
            String data = JSON.toJSONString(value);
            if (jedis.set(key, data).equalsIgnoreCase("ok")) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 缓存一个Map集合,成功返回1,失败返回0,默认缓存时间为1小时,以本类的常量DEFAULT_SETEX_TIMEOUT为准
     *
     * @param key
     * @param value
     * @return
     */
    public static  int setExMap(String key, Map value) {
        if (value == null || key == null || "".equals(key)) {
            return 0;
        }
        
        try {
            
            String data = JSON.toJSONString(value);
            if (jedis.setex(key, DEFAULT_SETEX_TIMEOUT, data).equalsIgnoreCase("ok")) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 缓存一个Map集合,成功返回1,失败返回0,缓存时间以timeout为准,单位秒
     *
     * @param key
     * @param value
     * @param timeout
     * @return
     */
    public static  int setExMap(String key, Map value, int timeout) {
        if (value == null || key == null || "".equals(key)) {
            return 0;
        }
        
        try {
            
            String data = JSON.toJSONString(value);
            if (jedis.setex(key, timeout, data).equalsIgnoreCase("ok")) {
                return 1;
            } else {
                return 0;
            }
        } finally {
            
        }
    }

    /**
     * 获取一个字符串值
     *
     * @param key
     * @return
     */
    public static String get(String key) {
        if (isValueNull(key)) {
            return null;
        }
        
        try {
            
            return jedis.get(key);
        } finally {
            
        }
    }

    /**
     * 获得一个指定类型的对象
     *
     * @param key
     * @param clazz
     * @return
     */
    public static  T get(String key, Class clazz) {
        if (isValueNull(key)) {
            return null;
        }
        
        try {
            

            byte[] data = jedis.get(key.getBytes());
            T result = deSeri(data, clazz);
            return result;
        } finally {
            
        }
    }

    /**
     * 获得一个字符串集合,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1
     * 表示列表的第二个元素,以此类推。 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public static List getList(String key, long start, long end) {
        if (isValueNull(key)) {
            return null;
        }
        
        try {
            
            List result = jedis.lrange(key, start, end);
            return result;
        } finally {
            
        }
    }

    /**
     * 获得一个类型的对象集合,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。
     * 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
     *
     * @param key
     * @param start
     * @param end
     * @return
     */
    public static  List getList(String key, long start, long end, Class clazz) {
        if (isValueNull(key)) {
            return null;
        }
        
        try {
            
            List lrange = jedis.lrange(key.getBytes(), start, end);
            List result = null;
            if (lrange != null) {
                for (byte[] data : lrange) {
                    if (result == null) {
                        result = new ArrayList<>();
                    }
                    result.add(deSeri(data, clazz));
                }
            }
            return result;
        } finally {
            
        }
    }

    /**
     * 获得list中存了多少个值
     *
     * @return
     */
    public static long getListCount(String key) {
        if (isValueNull(key)) {
            return 0;
        }
        
        try {
            
            return jedis.llen(key);
        } finally {
            
        }
    }

    /**
     * 获得一个List的集合,
     *
     * @param key   键
     * @param clazz 返回集合的类型
     * @return
     * @throws IOException
     */
    public static  List getList(String key, Class clazz) throws IOException {
        if (isValueNull(key)) {
            return null;
        }
        
        try {
            
            byte[] data = jedis.get(key.getBytes());
            List result = deSeriList(data, clazz);
            return result;
        } finally {
            
        }
    }

    /**
     * 获得一个字符串set集合
     *
     * @param key
     * @return
     */
    public static Set getSet(String key) {
        if (isValueNull(key)) {
            return null;
        }
        
        try {
            
            Set result = jedis.smembers(key);
            return result;
        } finally {
            
        }
    }

    /**
     * 获得一个字符串set集合
     *
     * @param key
     * @return
     */
    public static  Set getSet(String key, Class clazz) {
        if (isValueNull(key)) {
            return null;
        }
        
        try {
            
            Set smembers = jedis.smembers(key.getBytes());
            Set result = null;
            if (smembers != null) {
                for (byte[] data : smembers) {
                    if (result == null) {
                        result = new HashSet<>();
                    }
                    result.add(deSeri(data, clazz));
                }
            }
            return result;
        } finally {
            
        }
    }

    /**
     * 获得集合中存在多少个值
     *
     * @param key
     * @return
     */
    public static long getSetCount(String key) {
        if (isValueNull(key)) {
            return 0;
        }
        
        try {
            
            return jedis.scard(key);
        } finally {
            
        }
    }

    /**
     * 获得一个Map的集合
     *
     * @param key
     * @param v
     * @param k
     * @return
     */
    public static  Map getMap(String key, Class k, Class v) {
        if (key == null || "".equals(key)) {
            return null;
        }
        
        try {
            
            String data = jedis.get(key);
            @SuppressWarnings("unchecked")
            Map result = (Map) JSON.parseObject(data);
            return result;
        } finally {
            
        }
    }

    /**
     * 判斷key是否存在
     * @param key
     * @return
     */
    public static boolean exists(String key){
        
        try {
            
            return jedis.exists(key);
        } finally {
            
        }
    }

    /**
     * 判斷key是否存在
     * @param keys
     * @return
     */
    public static Long exists(String... keys){
        
        try {
            
            return jedis.exists(keys);
        } finally {
            
        }
    }

    /**
     * 删除一个值
     *
     * @param key
     */
    public static void del(String... key) {
        
        try {
            
            for (int i = 0; i < key.length; i++) {
                jedis.del(key);
            }
        } finally {
            
        }
    }

    // --------------------------公用方法区------------------------------------

    /**
     * 检查值是否为null,如果为null返回true,不为null返回false
     *
     * @param obj
     * @return
     */
    private static boolean isValueNull(Object... obj) {
        for (int i = 0; i < obj.length; i++) {
            if (obj[i] == null || "".equals(obj[i])) {
                return true;
            }
        }
        return false;
    }

    /**
     * 序列化一个对象
     *
     * @param value
     * @return
     */
    private static  byte[] enSeri(T value) {
        @SuppressWarnings("unchecked")
        RuntimeSchema schema = (RuntimeSchema) RuntimeSchema.createFrom(value.getClass());
        byte[] data = ProtostuffIOUtil.toByteArray(value, schema,
                LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
        return data;
    }

    /**
     * 反序列化一个对象
     *
     * @param data
     * @param clazz
     * @return
     */
    private static  T deSeri(byte[] data, Class clazz) {
        if (data == null || data.length == 0) {
            return null;
        }
        RuntimeSchema schema = RuntimeSchema.createFrom(clazz);
        T result = schema.newMessage();
        ProtostuffIOUtil.mergeFrom(data, result, schema);
        return result;
    }

    /**
     * 序列化List集合
     *
     * @param list
     * @return
     * @throws IOException
     */
    private static  byte[] enSeriList(List list) throws RuntimeException, IOException {
        if (list == null || list.size() == 0) {
            throw new RuntimeException("集合不能为空!");
        }
        @SuppressWarnings("unchecked")
        RuntimeSchema schema = (RuntimeSchema) RuntimeSchema.getSchema(list.get(0).getClass());
        ByteArrayOutputStream out = new ByteArrayOutputStream();
        ProtostuffIOUtil.writeListTo(out, list, schema, LinkedBuffer.allocate(LinkedBuffer.DEFAULT_BUFFER_SIZE));
        byte[] byteArray = out.toByteArray();
        return byteArray;
    }

    /**
     * 反序列化List集合
     *
     * @param data
     * @param clazz
     * @return
     * @throws IOException
     */
    private static  List deSeriList(byte[] data, Class clazz) throws IOException {
        if (data == null || data.length == 0) {
            return null;
        }
        RuntimeSchema schema = RuntimeSchema.createFrom(clazz);
        List result = ProtostuffIOUtil.parseListFrom(new ByteArrayInputStream(data), schema);
        return result;
    }

    //----------------------geo start------------------------------------------

    /**
     * 增加地理位置的坐标
     *
     * @param key
     * @param coordinate
     * @param member
     * @return Long
     */
    public static Long geoadd(String key, GeoCoordinate coordinate, String member) {
        
        try {
            
            return jedis.geoadd(key, coordinate.getLongitude(), coordinate.getLatitude(), member);
        } finally {
            
        }
    }

    /**
     * 批量添加地理位置
     *
     * @param key
     * @param memberCoordinateMap
     * @return Long
     */
    public static Long geoadd(String key, Map memberCoordinateMap) {
        
        try {
            
            return jedis.geoadd(key, memberCoordinateMap);
        } finally {
            
        }
    }

    /**
     * 根据给定地理位置坐标获取指定范围内的地理位置集合(返回匹配位置的经纬度 + 匹配位置与给定地理位置的距离 + 从近到远排序,)
     *
     * @param key
     * @param coordinate
     * @param radius
     * @return List
     */
    public static List geoRadius(String key, GeoCoordinate coordinate, double radius) {
        
        try {
            
            return jedis.georadius(key, coordinate.getLongitude(), coordinate.getLatitude(), radius, GeoUnit.KM, GeoRadiusParam.geoRadiusParam().withDist().withCoord().sortAscending());
        } finally {
            
        }
    }



    /**
     * 根据给定地理位置获取指定范围内的地理位置集合(返回匹配位置的经纬度 + 匹配位置与给定地理位置的距离 + 从近到远排序,)
     *
     * @param key
     * @param member
     * @param radius
     * @return List
     */
    public List georadiusByMember(String key, String member, double radius) {
        
        try {
            
            return jedis.georadiusByMember(key, member, radius, GeoUnit.KM, GeoRadiusParam.geoRadiusParam().withDist().withCoord().sortAscending());
        } finally {
            
        }
    }

    /**
     * 查询2位置距离
     *
     * @param key
     * @param member1
     * @param member2
     * @param unit
     * @return Double
     */
    public static Double geoDist(String key, String member1, String member2, GeoUnit unit) {
        
        try {
            
            Double dist = jedis.geodist(key, member1, member2, unit);
            return dist;
        } finally {
            
        }
    }

    /**
     * 查询位置的geohash
     *
     * @param key
     * @param members
     * @return List
     */
    public static List geoHash(String key, String... members) {
        
        try {
            
            List resultList = jedis.geohash(key, members);
            return resultList;
        } finally {
            
        }
    }

    /**
     * 获取地理位置的坐标
     *
     * @param key
     * @param member
     * @return List
     */
    public static List geopos(String key, String... member) {
        
        try {
            
            List result = jedis.geopos(key, member);
            return result;
        } finally {
            
        }
    }


}

 

redis 订阅监听

RedisMsgPubSubListener.java

import com.xx.im.api.config.NettyConfig;
import io.netty.channel.Channel;
import io.netty.channel.ChannelId;
import io.netty.channel.DefaultChannelId;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.util.internal.StringUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.JedisPubSub;

import java.util.Date;

/**
 */
public class RedisMsgPubSubListener extends JedisPubSub {

    private static Logger log = LoggerFactory.getLogger(RedisMsgPubSubListener.class);

    @Override
    public void onMessage(String msgChannel, String iMChannelId) {

        if (StringUtil.isNullOrEmpty(iMChannelId)){
            return;
        }

        // 避免 如果不是在本机,则丢弃  避免 压力透彻到 redis
        String isExists = NettyConfig.LOCALCHANNELMAP.get(iMChannelId);
        if (StringUtil.isNullOrEmpty(isExists)){
            // 离线则丢弃,不推送
            return;
        }

        String msg = JedisUtil.get(iMChannelId);
        if (StringUtil.isNullOrEmpty(msg)){
            return;
        }


        ChannelId id = JedisUtil.get(iMChannelId+"Id", DefaultChannelId.class);
        if (id == null){
            return;
        }

        log.info("iMChannelId:"+iMChannelId+"  msg:"+msg);
        Channel channel = NettyConfig.GROUP.find(id);
        if (channel != null){
            String responseStr = new Date().toString()  + channel.id() +  " ===>>> " + msg;
            TextWebSocketFrame tws = new TextWebSocketFrame(responseStr);
            channel.writeAndFlush(tws);

            //如果推送完成,在清理到 redis中的 消息
            JedisUtil.del(iMChannelId);
        }
    }

    @Override
    public void onSubscribe(String channel, int subscribedChannels) {
        log.info("channel:" + channel + "is been subscribed:" + subscribedChannels);
    }

    @Override
    public void onUnsubscribe(String channel, int subscribedChannels) {
        log.info("channel:" + channel + "is been unsubscribed:" + subscribedChannels);
    }

}

 

启动类

WebsocketServer.java

import com.xx.im.api.config.Constants;
import com.xx.im.api.handler.MyWebsocketInitializer;
import com.xx.im.api.redis.JedisUtil;
import io.netty.bootstrap.ServerBootstrap;
import io.netty.channel.Channel;
import io.netty.channel.EventLoopGroup;
import io.netty.channel.nio.NioEventLoopGroup;
import io.netty.channel.socket.nio.NioServerSocketChannel;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

public class WebsocketServer {
    private static Logger log = LoggerFactory.getLogger(WebsocketServer.class);

    public static void main(String[] args) {
        EventLoopGroup bossGroup = new NioEventLoopGroup();
        EventLoopGroup workGroup = new NioEventLoopGroup();

        try {

            //启动订阅
            JedisUtil.init();

            ServerBootstrap bootstrap = new ServerBootstrap();
            bootstrap.group(bossGroup, workGroup);
            bootstrap.channel(NioServerSocketChannel.class);
            bootstrap.childHandler(new MyWebsocketInitializer());
            log.info("服务端开启等待客户端连接...");

            Channel channel = bootstrap.bind(Constants.PORT).sync().channel();
            channel.closeFuture().sync();
        } catch (Exception e) {
            log.error("服务端启动失败", e);
        } finally {
            // 退出程序
            bossGroup.shutdownGracefully();
            workGroup.shutdownGracefully();
            log.info("服务端已关闭");
        }
    }
}

 

 

 

测试客户端

ws123.html




    
    WebSocket客户端
    







客户端接收到服务端返回的应答消息:

 

你可能感兴趣的:(java)