/*
* (C) Copyright 2015-2016 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* Contributors:
* ohun@live.cn (夜色)
*/
package com.mpush.netty.connection;
import com.mpush.api.connection.Cipher;
import com.mpush.api.connection.Connection;
import com.mpush.api.connection.SessionContext;
import com.mpush.api.protocol.Packet;
import com.mpush.api.spi.core.CipherFactory;
import com.mpush.netty.codec.PacketEncoder;
import com.mpush.tools.log.Logs;
import io.netty.channel.Channel;
import io.netty.channel.ChannelFuture;
import io.netty.channel.ChannelFutureListener;
import io.netty.channel.socket.DatagramPacket;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Created by ohun on 2015/12/22.
*
* @author ohun@live.cn
*/
public final class NettyConnection implements Connection, ChannelFutureListener {
private static final Logger LOGGER = LoggerFactory.getLogger(NettyConnection.class);
private static final Cipher RSA_CIPHER = CipherFactory.create();
private SessionContext context;
private Channel channel;
private volatile byte status = STATUS_NEW;
private long lastReadTime;
private long lastWriteTime;
@Override
public void init(Channel channel, boolean security) {
this.channel = channel;
this.context = new SessionContext();
this.lastReadTime = System.currentTimeMillis();
this.status = STATUS_CONNECTED;
if (security) {
this.context.changeCipher(RSA_CIPHER);
}
}
@Override
public void setSessionContext(SessionContext context) {
this.context = context;
}
@Override
public SessionContext getSessionContext() {
return context;
}
@Override
public String getId() {
return channel.id().asShortText();
}
@Override
public ChannelFuture send(Packet packet) {
return send(packet, null);
}
@Override
public ChannelFuture send(Packet packet, final ChannelFutureListener listener) {
if (channel.isActive()) {
ChannelFuture future = channel.writeAndFlush(packet.toFrame(channel)).addListener(this);
if (listener != null) {
future.addListener(listener);
}
if (channel.isWritable()) {
return future;
}
//阻塞调用线程还是抛异常?
//return channel.newPromise().setFailure(new RuntimeException("send data too busy"));
if (!future.channel().eventLoop().inEventLoop()) {
future.awaitUninterruptibly(100);
}
return future;
} else {
/*if (listener != null) {
channel.newPromise()
.addListener(listener)
.setFailure(new RuntimeException("connection is disconnected"));
}*/
return this.close();
}
}
@Override
public ChannelFuture close() {
if (status == STATUS_DISCONNECTED) return null;
this.status = STATUS_DISCONNECTED;
return this.channel.close();
}
@Override
public boolean isConnected() {
return status == STATUS_CONNECTED;
}
@Override
public boolean isReadTimeout() {
return System.currentTimeMillis() - lastReadTime > context.heartbeat + 1000;
}
@Override
public boolean isWriteTimeout() {
return System.currentTimeMillis() - lastWriteTime > context.heartbeat - 1000;
}
@Override
public void updateLastReadTime() {
lastReadTime = System.currentTimeMillis();
}
@Override
public void operationComplete(ChannelFuture future) throws Exception {
if (future.isSuccess()) {
lastWriteTime = System.currentTimeMillis();
} else {
LOGGER.error("connection send msg error", future.cause());
Logs.CONN.error("connection send msg error={}, conn={}", future.cause().getMessage(), this);
}
}
@Override
public void updateLastWriteTime() {
lastWriteTime = System.currentTimeMillis();
}
@Override
public String toString() {
return "[channel=" + channel
+ ", context=" + context
+ ", status=" + status
+ ", lastReadTime=" + lastReadTime
+ ", lastWriteTime=" + lastWriteTime
+ "]";
}
@Override
public Channel getChannel() {
return channel;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
NettyConnection that = (NettyConnection) o;
return channel.id().equals(that.channel.id());
}
@Override
public int hashCode() {
return channel.id().hashCode();
}
}