准备工作
依赖:
implementation 'io.netty:netty-all:4.1.36.Final'
compileOnly "org.projectlombok:lombok:1.16.18"
implementation 'org.glassfish:javax.annotation:10.0-b28'
导入jar包:
libs–>fastjson-1.2.49.jar
*下方有demo下载获取
netty
ChatProto自定义消息格式与消息文体
/**
* Created by ICE on 2021年1月11日.
*/
public class ChatProto {
//header
public static final int PING_PROTO = 1 << 8 | 220; //ping消息
public static final int PONG_PROTO = 2 << 8 | 220; //pong消息
public static final int SYS_PROTO = 3 << 8 | 220; //系统消息
public static final int ERROR_PROTO = 4 << 8 | 220; //错误消息
public static final int AUTH_PROTO = 5 << 8 | 220; //认证消息
public static final int MSG_PROTO = 6 << 8 | 220; //普通消息
public int getHead() {
return head;
}
public void setHead(int head) {
this.head = head;
}
public String getBody() {
return timestamp;
}
public void setBody(String timestamp) {
this.timestamp = timestamp;
}
public Map<String, Object> getExtend() {
return extend;
}
public void setExtend(Map<String, Object> extend) {
this.extend = extend;
}
private int head;
private String timestamp;
private Map<String, Object> extend;
public ChatProto(int head, String timestamp) {
this.head = head;
this.timestamp = timestamp;
this.extend = new HashMap<>();
}
public static MessageProtocol buildPingProto() {
// Log.i("TAG","ping");
return buildProto(PING_PROTO);
}
public static MessageProtocol buildMsgProto(@NonNull Integer code) {
ChatProto chatProto = new ChatProto(MSG_PROTO, null);
Log.i("TAG", JSONObject.toJSONString(chatProto)+"");
return buildProto(MSG_PROTO, code, null);
}
public static MessageProtocol buildPongProto() {
return buildProto(PONG_PROTO);
}
public static MessageProtocol buildSysProto(int code, @NonNull String msg) {
return buildProto(SYS_PROTO, code, msg);
}
public static MessageProtocol buildErrProto(int code, @NonNull String msg) {
return buildProto(ERROR_PROTO, code, msg);
}
public static MessageProtocol buildMsgProto(@NonNull Integer code, @NonNull Long apkId) {
ChatProto chatProto = new ChatProto(MSG_PROTO, null);
chatProto.extend.put("code", code);
chatProto.extend.put("apkId", apkId);
chatProto.extend.put("time", System.currentTimeMillis());
return new MessageProtocol(JSONObject.toJSONString(chatProto).getBytes());
}
public static MessageProtocol buildMsgProto(@NonNull Integer code, @Nullable Integer count) {
ChatProto chatProto = new ChatProto(MSG_PROTO, null);
chatProto.extend.put("code", code);
if (null != count) {
chatProto.extend.put("count", count);
}
chatProto.extend.put("time", System.currentTimeMillis());
return new MessageProtocol(JSONObject.toJSONString(chatProto).getBytes());
}
public static MessageProtocol buildAuthProto(@Nullable @NonNull String token, @Nullable @NonNull Long giftId, Object o) {
ChatProto chatProto = new ChatProto(AUTH_PROTO, null);
// if (null != token) {
// chatProto.extend.put("nickName", token);
// }
// if (null != giftId) {
// chatProto.extend.put("giftId", giftId);
// }
// chatProto.extend.put("time", System.currentTimeMillis());
chatProto.extend.put("发送数据","发送数据");
return new MessageProtocol(JSONObject.toJSONString(chatProto).getBytes());
}
public static MessageProtocol buildMsgProto(@Nullable Long giftId, @NonNull String nickName, @Nullable @NonNull Integer level, String msg) {
ChatProto chatProto = new ChatProto(MSG_PROTO, null);
if (null != level) {
chatProto.extend.put("level", level);
}
if (!nickName.isEmpty()) {
chatProto.extend.put("nickName", nickName);
}
if (null != giftId) {
chatProto.extend.put("giftId", giftId);
}
chatProto.extend.put("time", System.currentTimeMillis());
return new MessageProtocol(JSONObject.toJSONString(chatProto).getBytes());
}
public static MessageProtocol buildMsgProto(int code, @NonNull String nickName, @Nullable Integer level, @Nullable Long giftId, @Nullable Integer count, String msg) {
ChatProto chatProto = new ChatProto(MSG_PROTO, msg);
chatProto.extend.put("code", code);
if (!nickName.isEmpty()) {
chatProto.extend.put("nickName", nickName);
}
if (null != level) {
chatProto.extend.put("level", level);
}
if (null != giftId) {
chatProto.extend.put("giftId", giftId);
}
if (null != count) {
chatProto.extend.put("count", count);
}
chatProto.extend.put("time", System.currentTimeMillis());
return new MessageProtocol(JSONObject.toJSONString(chatProto).getBytes());
}
private static MessageProtocol buildProto(int head) {
ChatProto chatProto = new ChatProto(head, null);
Log.i("TAG","发送数据-------JSONObject "+ JSONObject.toJSONString(chatProto));
return new MessageProtocol(JSONObject.toJSONString(chatProto).getBytes());
}
@NonNull
private static MessageProtocol buildProto(int head, int code, @NonNull String msg) {
ChatProto chatProto = new ChatProto(head, null);
chatProto.extend.put("code", code);
chatProto.extend.put("msg", msg);
String bytes = JSONObject.toJSONString(chatProto);
MessageProtocol messageProtocol = new MessageProtocol(JSONObject.toJSONString(chatProto).getBytes());
Log.i("TAG","发送数据------- "+messageProtocol);
return new MessageProtocol(JSONObject.toJSONString(chatProto).getBytes());
}
public static void main(String[] args){
ChatProto chatProto = new ChatProto(AUTH_PROTO, "哈喽,world!!!");
chatProto.extend.put("userId", 2L);
chatProto.extend.put("userName", "n_7777777");
chatProto.extend.put("level", 1);
chatProto.extend.put("time", System.currentTimeMillis());
String message = JSONObject.toJSONString(chatProto);
System.out.println(">>>>>" + message);
System.out.println("======" + Unpooled.copiedBuffer(message.getBytes()).toString(CharsetUtil.UTF_8));
ChatProto chatProto1 = new ChatProto(PING_PROTO, null);
String message1 = JSONObject.toJSONString(chatProto1);
System.out.println(">>>>>" + message1);
try {
String enStr = URLEncoder.encode(message, "UTF-8");
System.out.println(">>>>>" + enStr);
String enStr1 = URLEncoder.encode(message1, "UTF-8");
System.out.println(">>>>>" + enStr1);
}catch (Exception e) {
e.printStackTrace();
}
}
}
MessageEncoder MessageDecoder 消息加密处理类
/**
* Created by ICE on 2021年1月11日.
*/
public class MessageDecoder extends ByteToMessageDecoder {
/**
* <pre>
* 协议开始的标准head_data,int类型,占据4个字节.
* 表示数据的长度contentLength,int类型,占据4个字节.
* </pre>
*/
public static final int BASE_LENGTH = 4 + 4;
@Override
protected void decode(ChannelHandlerContext channelHandlerContext, @NonNull ByteBuf byteBuf, @NonNull List<Object> list) throws Exception {
//可读长度大于基本长度
if (byteBuf.readableBytes() > BASE_LENGTH) {
//防止socket字节流攻击
//防止客户端传来的数据过大
if (byteBuf.readableBytes() > 2048) {
byteBuf.skipBytes(byteBuf.readableBytes());
}
//记录包头开始的index
int beginReader;
while (true) {
//获取包头开始的index
beginReader = byteBuf.readerIndex();
//标记包头开始的index
byteBuf.markReaderIndex();
//读到了协议的开始标志,结束while循环
if (byteBuf.readInt() == ConstantValue.HEAD_DATA) {
break;
}
//未读到包头,略过一个字符,继续读取包头的开始标记信息
byteBuf.resetReaderIndex();
byteBuf.readByte();
//当略过一个字符后,数据包长度不符合,等待后续数据到达
if (byteBuf.readableBytes() < BASE_LENGTH) {
return;
}
}
//消息的长度
int length = byteBuf.readInt();
//判断数据包是否完整
if (byteBuf.readableBytes() < length) {
//还原读指针
byteBuf.readerIndex(beginReader);
return;
}
//读取消息内容
byte[] data = new byte[length];
byteBuf.readBytes(data);
MessageProtocol protocol = new MessageProtocol(data);
list.add(protocol);
}
}
}
/**
* Created by ICE on 2021年1月11日.
*/
public class MessageEncoder extends MessageToByteEncoder<MessageProtocol> {
@Override
protected void encode(ChannelHandlerContext channelHandlerContext, @NonNull MessageProtocol messageProtocol, @NonNull ByteBuf byteBuf) throws Exception {
//写入消息
//1.写入消息的开头信息标志(int)
byteBuf.writeInt(messageProtocol.getHeadData());
//2.写入消息的长度(int)
byteBuf.writeInt(messageProtocol.getContentLength());
//3.写入消息的内容(byte[])
byteBuf.writeBytes(messageProtocol.getContent());
}
}
SocketUtils文本消息转换类
/**
* Created by ICE on 2021年1月11日.
*/
public class SocketUtils {
@RequiresApi(api = Build.VERSION_CODES.N)
public static String transfer(@NonNull ByteBuf buf) {
if (Objects.isNull(buf)) {
return "";
}
String str;
if (buf.hasArray()) {
str = new String(buf.array(), buf.arrayOffset() + buf.readerIndex(), buf.readableBytes());
}else {
byte[] bytes = new byte[buf.readableBytes()];
buf.getBytes(buf.readerIndex(), bytes);
str = new String(bytes, 0, buf.readableBytes());
}
return str;
}
}
SocketClient启动类
/**
* Created by ICE on 2019-06-10.
*/
@Slf4j
public class SocketClient implements Start {
private Channel channel;
private final Bootstrap bs = new Bootstrap();
private final EventLoopGroup group = new NioEventLoopGroup();
private final SocketClientHandler handler = new SocketClientHandler(this);
private String host;
private int port;
private MessageListener listener;
private String token;
public MessageListener getListener() {
return listener;
}
public String getToken() {
return token;
}
public void addListener(MessageListener listener) {
this.listener = listener;
}
public SocketClient() {
bs.group(group)
.channel(NioSocketChannel.class)
// .option(ChannelOption.TCP_NODELAY, true)
// .option(ChannelOption.SO_KEEPALIVE, true)
.handler(new ChannelInitializer<SocketChannel>() {
@Override
protected void initChannel(@NonNull SocketChannel socketChannel) throws Exception {
ChannelPipeline p = socketChannel.pipeline();
//定时器
p.addLast(new IdleStateHandler(40, 30, 0, TimeUnit.SECONDS));
//编码器 解码器
// p.addLast("encoder", new StringDecoder(StandardCharsets.UTF_8));
// p.addLast("decoder", new StringDecoder(StandardCharsets.UTF_8));
p.addLast(new MessageDecoder());
p.addLast(new MessageEncoder());
p.addLast(handler);
}
});
}
@Override
public void run(String host, int port) {
this.host = host;
this.port = port;
if (null != channel && channel.isActive()) {
return;
}
connect();
}
public void connect() {
try {
ChannelFuture f = bs.connect(new InetSocketAddress(host, port)).sync();
// ChannelFuture f = bs.connect(host, port);
f.addListener(new ChannelFutureListener() {
@Override
public void operationComplete(ChannelFuture channelFuture) throws Exception {
if (channelFuture.isSuccess()) {
channel = channelFuture.channel();
Log.i("TAG", "【socket】客户端启动成功,START SUCCESS!!!");
channelFuture.channel().writeAndFlush(ChatProto.buildPingProto());
}
}
});
f.channel().closeFuture().sync();
} catch (InterruptedException e) {
Log.i("TAG", "【socket】客户端启动异常, {}" + e.getMessage());
} finally {
try {
group.shutdownGracefully().sync();
group.shutdownNow();
Log.i("TAG", "【socket】客户端断开连接,重新连接");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public void stop() {
Log.i("TAG", "【socket】主动客户端终止");
group.shutdownGracefully();
}
public void authToken(String token) {
if (null != token && token.trim().length() > 0) {
this.token = token;
}
}
@RequiresApi(api = Build.VERSION_CODES.N)
public void sendMsg(Long userId, String userName, Integer level, String message) {
if (Objects.nonNull(channel)) {
channel.writeAndFlush(ChatProto.buildMsgProto(userId, userName, level, message));
}
}
}
SocketClientHandler监听类
/**
* Created by ICE on 2021年1月11日.
*/
@Slf4j
@ChannelHandler.Sharable
public class SocketClientHandler extends ChannelInboundHandlerAdapter {
private SocketClient client;
public SocketClientHandler(SocketClient client) {
this.client = client;
}
/**
* 收到消息
*
* @param ctx
* @param msg
*/
@Override
public void channelRead(ChannelHandlerContext ctx, @Nullable Object msg) {
Log.i("TAG", "channelRead");
if (null == msg) return;
if (!(msg instanceof MessageProtocol)) return;
MessageProtocol message = (MessageProtocol) msg;
String body = new String(message.getContent());
Log.i("TAG", "【Socket】客户端收到消息:{}" + body);
JSONObject json = JSONObject.parseObject(body);
if (null == json) return;
//pong
if (json.getInteger("head") == ChatProto.PONG_PROTO) {
Log.i("TAG", "【Socket】服务端返回pong消息" + json.toString());
}
//普通消息
if (json.getInteger("head") == ChatProto.MSG_PROTO) {
Log.i("TAG", "收到端消息:{}" + body);
}
// client.getListener().listen(body);
}
@Override
public void channelActive(@NonNull ChannelHandlerContext ctx) {
//发送一次授权
ctx.writeAndFlush(ChatProto.buildAuthProto(client.getToken(), 1L, null));
// ctx.channel().read();
}
@Override
public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception {
ctx.close();
}
@Override
public void channelInactive(ChannelHandlerContext ctx) throws Exception {
Log.i("TAG", "【socket】 SocketClientHandler 客户端终止---正在重新连接");
client.connect();
super.channelInactive(ctx);
}
@Override
public void userEventTriggered(@NonNull ChannelHandlerContext ctx, Object evt) throws Exception {
if (evt instanceof IdleStateEvent) {
IdleStateEvent event = (IdleStateEvent) evt;
if (event.state().equals(IdleState.READER_IDLE)) {
Log.i("TAG", "长期没收到服务器推送数据");
//可以选择重新连接
// client.connect();
}
if (event.state().equals(IdleState.WRITER_IDLE)) {
// Log.i("TAG","长期没发送信息到服务器服务器");
ctx.writeAndFlush(ChatProto.buildPingProto());
}
}
ctx.fireUserEventTriggered(evt);
}
}