
常量表
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 <channel, 1>
*/
public final static Map<String,String> LOCALCHANNELMAP = new ConcurrentHashMap<>();
/**
* 本地存储一份map <tokey, channel> 送达到key,channel
*/
public final static Map<String, List<String>> 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<Object> {
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<SocketChannel> {
@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 <T> 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 <T> 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 <T> 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 {
}
}
/**
* 添加一个<T>类型对象值到list中,成功返回1,失败返回0
*
* @param key
* @param value
* @return
*/
@SafeVarargs
public static <T> 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 {
}
}
/**
* 缓存一个<T>类型对象值到list中,全部list的key默认缓存时间为1小时,成功返回1,失败返回0
*
* @param key
* @param value
* @return
*/
@SafeVarargs
public static <T> 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 {
}
}
/**
* 缓存一个<T>类型对象值到list中,全部list的key缓存时间为timeOut,单位秒,成功返回1,失败返回0
*
* @param key
* @param value
* @return
*/
@SafeVarargs
public static <T> 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 <T> int setList(String key, List<T> 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<T>集合,成功返回1,失败返回0,默认缓存时间为1小时,以本类的常量DEFAULT_SETEX_TIMEOUT为准
*
* @param key
* @param value
* @return
* @throws IOException
* @throws RuntimeException
*/
public static <T> int setExList(String key, List<T> 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<T>集合,成功返回1,失败返回0,缓存时间以timeout为准,单位秒
*
* @param key
* @param value
* @param timeout
* @return
* @throws IOException
* @throws RuntimeException
*/
public static <T> int setExList(String key, List<T> 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 {
}
}
/**
* 添加一个<T>类型到set集合,如果key存在就在就最追加,成功返回1,失败或者没有受影响返回0
*
* @param key
* @param value
* @return
*/
@SafeVarargs
public static <T> 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 {
}
}
/**
* 缓存一个<T>类型到set集合,如果key存在就在就最追加,整个set的key默认有效时间为1小时,成功返回1,失败或者没有受影响返回0
*
* @param key
* @param value
* @return
*/
@SafeVarargs
public static <T> 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 {
}
}
/**
* 缓存一个<T>类型到set集合,如果key存在就在就最追加,整个set的key有效时间为timeOut,单位秒,成功返回1,失败或者没有受影响返回0
*
* @param key
* @param value
* @return
*/
@SafeVarargs
public static <T> 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<K, V>集合,成功返回1,失败返回0
*
* @param key
* @param value
* @return
*/
public static <K, V> int setMap(String key, Map<K, V> 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<K, V>集合,成功返回1,失败返回0,默认缓存时间为1小时,以本类的常量DEFAULT_SETEX_TIMEOUT为准
*
* @param key
* @param value
* @return
*/
public static <K, V> int setExMap(String key, Map<K, V> 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<K, V>集合,成功返回1,失败返回0,缓存时间以timeout为准,单位秒
*
* @param key
* @param value
* @param timeout
* @return
*/
public static <K, V> int setExMap(String key, Map<K, V> 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> T get(String key, Class<T> 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<String> getList(String key, long start, long end) {
if (isValueNull(key)) {
return null;
}
try {
List<String> result = jedis.lrange(key, start, end);
return result;
} finally {
}
}
/**
* 获得一个<T>类型的对象集合,区间以偏移量 START 和 END 指定。 其中 0 表示列表的第一个元素, 1 表示列表的第二个元素,以此类推。
* 你也可以使用负数下标,以 -1 表示列表的最后一个元素, -2 表示列表的倒数第二个元素,以此类推。
*
* @param key
* @param start
* @param end
* @return
*/
public static <T> List<T> getList(String key, long start, long end, Class<T> clazz) {
if (isValueNull(key)) {
return null;
}
try {
List<byte[]> lrange = jedis.lrange(key.getBytes(), start, end);
List<T> 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<T>的集合,
*
* @param key 键
* @param clazz 返回集合的类型
* @return
* @throws IOException
*/
public static <T> List<T> getList(String key, Class<T> clazz) throws IOException {
if (isValueNull(key)) {
return null;
}
try {
byte[] data = jedis.get(key.getBytes());
List<T> result = deSeriList(data, clazz);
return result;
} finally {
}
}
/**
* 获得一个字符串set集合
*
* @param key
* @return
*/
public static Set<String> getSet(String key) {
if (isValueNull(key)) {
return null;
}
try {
Set<String> result = jedis.smembers(key);
return result;
} finally {
}
}
/**
* 获得一个字符串set集合
*
* @param key
* @return
*/
public static <T> Set<T> getSet(String key, Class<T> clazz) {
if (isValueNull(key)) {
return null;
}
try {
Set<byte[]> smembers = jedis.smembers(key.getBytes());
Set<T> 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<v,k>的集合
*
* @param key
* @param v
* @param k
* @return
*/
public static <K, V> Map<K, V> getMap(String key, Class<K> k, Class<V> v) {
if (key == null || "".equals(key)) {
return null;
}
try {
String data = jedis.get(key);
@SuppressWarnings("unchecked")
Map<K, V> result = (Map<K, V>) 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 <T> byte[] enSeri(T value) {
@SuppressWarnings("unchecked")
RuntimeSchema<T> schema = (RuntimeSchema<T>) 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> T deSeri(byte[] data, Class<T> clazz) {
if (data == null || data.length == 0) {
return null;
}
RuntimeSchema<T> schema = RuntimeSchema.createFrom(clazz);
T result = schema.newMessage();
ProtostuffIOUtil.mergeFrom(data, result, schema);
return result;
}
/**
* 序列化List集合
*
* @param list
* @return
* @throws IOException
*/
private static <T> byte[] enSeriList(List<T> list) throws RuntimeException, IOException {
if (list == null || list.size() == 0) {
throw new RuntimeException("集合不能为空!");
}
@SuppressWarnings("unchecked")
RuntimeSchema<T> schema = (RuntimeSchema<T>) 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 <T> List<T> deSeriList(byte[] data, Class<T> clazz) throws IOException {
if (data == null || data.length == 0) {
return null;
}
RuntimeSchema<T> schema = RuntimeSchema.createFrom(clazz);
List<T> 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<String, GeoCoordinate> memberCoordinateMap) {
try {
return jedis.geoadd(key, memberCoordinateMap);
} finally {
}
}
/**
* 根据给定地理位置坐标获取指定范围内的地理位置集合(返回匹配位置的经纬度 + 匹配位置与给定地理位置的距离 + 从近到远排序,)
*
* @param key
* @param coordinate
* @param radius
* @return List<GeoRadiusResponse>
*/
public static List<GeoRadiusResponse> 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<GeoRadiusResponse>
*/
public List<GeoRadiusResponse> 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<String>
*/
public static List<String> geoHash(String key, String... members) {
try {
List<String> resultList = jedis.geohash(key, members);
return resultList;
} finally {
}
}
/**
* 获取地理位置的坐标
*
* @param key
* @param member
* @return List<GeoCoordinate>
*/
public static List<GeoCoordinate> geopos(String key, String... member) {
try {
List<GeoCoordinate> 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
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" http-equiv="Content-Type" content="text/html;charset=utf-8">
<title>WebSocket客户端</title>
<script type="text/javascript">
var socket;
if (!window.WebSocket) {
window.WebSocket = window.MozWebSocket;
}
if (window.WebSocket) {
socket = new WebSocket("ws://localhost:8989/ws");
socket.onmessage = function (ev) {
var ta = document.getElementById('responseContent');
ta.value += ev.data + "\r\n";
};
socket.onopen = function (ev) {
var ta = document.getElementById('responseContent');
ta.value = "您当前的浏览器支持WebSocket, 请进行后续操作\r\n";
};
socket.onclose = function (ev) {
var ta = document.getElementById('responseContent');
ta.value = "WebSocket连接已经关闭\r\n";
};
socket.onerror = function (ev) {
var ta = document.getElementById('responseContent');
ta.value = ev.data + "WebSocket连接异常\r\n";
};
} else {
alert("您的浏览器不支持WebSocket");
}
var userNo="123";
var toUserNo="124";
function send(message) {
if (!window.WebSocket) {
return;
}
if (socket.readyState === WebSocket.OPEN) {
var json = "{" +
"\"ufrom\":\""+userNo+"\"," + // ufrom 来至用户
"\"dfrom\":\"\"," + // dfrom 来至医生
"\"uto\":\""+toUserNo+"\"," + // uto 送达用户
"\"dto\":\"\"," + // dto 送达医生
"\"msg\":\""+message+"\"" +
"}"
socket.send(json);
} else {
alert("WebSocket连接建立失败, 请重试");
console.log(socket.readyState)
}
}
</script>
</head>
<body>
<form onsubmit="return false;">
<label for="message">
<input type="text" id="message" name="message" value=""/>
</label>
<br><br>
<input type="button" value="发送WebSocket请求消息" onclick="send(this.form.message.value)"/>
<br><br>
<hr color="red" size="5">
<h2>客户端接收到服务端返回的应答消息: </h2>
<label for="responseContent">
<textarea id="responseContent" style="width: 1024px;height: 300px"></textarea>
</label>
</form>
</body>
</html>
本文档详细介绍了如何使用Netty、WebSocket和Redis构建一个IM即时通讯系统的分布式集群。配置类中涵盖了连接设置,收发消息Handler处理通信逻辑,Redis工具类用于存储和检索消息,同时利用Redis的订阅监听功能实现实时推送。此外,还包含启动类以运行服务及测试客户端,以及一个简单的HTML页面ws123.html用于测试WebSocket连接。
8851





