package cn.sunsharp.ycpn.server.xmpp.net;
import java.io.UnsupportedEncodingException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder;
import org.apache.log4j.Logger;
import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.session.IoSession;
import org.dom4j.io.OutputFormat;
import org.jivesoftware.util.XMLWriter;
import org.xmpp.packet.Packet;
import cn.sunsharp.ycpn.server.xmpp.session.Session;
/**
* 一个Connect代表一个xmpp和server的连接
* @author sunsharp
*/
public class Connection {
private static final Logger log = Logger.getLogger(Connection.class);
// 声明一个mina中用于通信的session
private IoSession ioSession;
// 自定义session
private Session session;
// 声明通信语言
private String language;
// 连接是否已经关闭
private Boolean closed =Boolean.TRUE;
// 指定xmpp协议的版本(主要版本) <?xml version="1.0"?>
private int majorVersion = 1;
// 指定xmpp协议的版本(小版本) <?xml version="1.0"?>
private int minorVersion = 0;
// 声明一个线程安全的encoder
@SuppressWarnings("rawtypes")
private ThreadLocal encoder = new ThreadLocalEncoder();
//
private ConnectionCloseListener closeListener;
/**
* 构造方法为创建一个连接,需要传入Server的session,并且打开连状态
* @param ioSession
* @param closed
*/
public Connection(IoSession ioSession) {
this.ioSession = ioSession;
this.closed = Boolean.FALSE;
}
public void init(Session session){
this.session = session;
}
/**
* 关闭连接,包括相关的套接字连接
* 通知所有连接的listeners通道已经关闭
*/
public void closeConnect(){
// 连接成功关闭标标记
boolean closedSuccessfully = Boolean.FALSE;
synchronized(this){
if(!isConnectClosed()){
// 发送结束标志
deliverRawText("</stream:stream>", false);
if(null !=session){
session.setSessionStatus(Session.SESSION_CLOSED);
}
ioSession.close(Boolean.FALSE);
closed = closedSuccessfully = Boolean.TRUE;
}
}
if(closedSuccessfully){
notifyCloseListeners();
}
}
/**
* 注册一个监听器当连接关闭的时候能够通知到
* @param listener 要监听连接事件
*/
public void registerCloseListener(ConnectionCloseListener listener) {
if (closeListener != null) {
throw new IllegalStateException("listener 已经注册了");
}
if (isConnectClosed()) {
listener.onConnectionClose(session);
} else {
closeListener = listener;
}
}
/**
* 移除已经注册了的监听事件
* @param listener
*/
public void unregisterCloseListener(ConnectionCloseListener listener) {
if (closeListener == listener) {
closeListener = null;
}
}
/**
* 通知监听者,连接关闭了
*/
private void notifyCloseListeners() {
if (closeListener != null) {
try {
closeListener.onConnectionClose(session);
} catch (Exception e) {
log.error("通知监听失败!: " + closeListener, e);
e.printStackTrace();
}
}
}
/**
* 向当前连接发送一个包(这是client端并没有进行auth验证)
* @param packet
*/
public void deliver(Packet packet){
log.debug("Connection发送packet SENT:"+packet.toXML());
if(!isConnectClosed()){
boolean deliverSuccessfully = false;
// IoBuffer这个接口是对JAVA NIO 的ByteBuffer的封装
IoBuffer buffer = IoBuffer.allocate(4096);
// 设置缓冲区大小不够的时候可以自己扩大
buffer.setAutoExpand(true);
try {
/**
* 这里重写了sun中IO类的writer方法,将其中实现改为MINA提供的方法,用于XMLWriter
* XMLWriter把packet中的xml文件转换成一个流,写进IoBuffer在通过session向client端发送
*
*/
XMLWriter xmlSerializer = new XMLWriter(new IoBufferWriter(
buffer, (CharsetEncoder) encoder.get()),
new OutputFormat());
xmlSerializer.write(packet.getElement());
xmlSerializer.flush();
// IoBuffer流写完
buffer.flip();
// 通过session向外写
ioSession.write(buffer);
deliverSuccessfully = true;
} catch (Exception e) {
log.error("Connection:向发送packet的时候出错 了!"+this.toString());
e.printStackTrace();
}
// 发送失败就关闭连接
if(deliverSuccessfully){
// 记录server发送packet数量
session.incrementServerPacketCount();
}else{
closeConnect();
}
}
}
/**
* 向client发送一个原始的文本数据(字符串)
* @param text
*/
public void deliverRawText(String text) {
deliverRawText(text, true);
}
/**
* 可选择异步发送还是同步发送
* 异步:无需client响应
* 同步 :需要client响应
* @param text
* @param asynchronous
*/
private void deliverRawText(String text, boolean asynchronous) {
log.info("Connection发送普通文本 SENT:"+text);
if (!isConnectClosed()) {
IoBuffer buffer = IoBuffer.allocate(text.length());
buffer.setAutoExpand(true);
boolean deliverSuccessfully = false;
try {
buffer.put(text.getBytes("UTF-8"));
buffer.flip();
if(asynchronous){
// 异步
ioSession.write(buffer);
}else{
// 同步,设置等待client响应时间
boolean response = ioSession.write(buffer).awaitUninterruptibly(9000);
if(!response){
log.warn("Connection发送raw text时超过响应时间"+this.toString());
}
}
deliverSuccessfully = true;
} catch (UnsupportedEncodingException e) {
log.error("Connection:向发送raw text的时候出错 了!"+this.toString());
e.printStackTrace();
}
// 发送失败后关闭连接
if((!deliverSuccessfully) && asynchronous){
closeConnect();
}
}
}
/**
* 如果session关闭就返回true
* @return
*/
private Boolean isConnectClosed(){
if(session ==null){
return closed;
}
// 查看session状态是否已经关闭,反之为false
return session.getSessionStatus() == Session.SESSION_CLOSED;
}
/**
* 这个内部类的作用是返回一个字符编码,因为这里用线程安全的原因是IoBffer向外写的时候不是线程安全的
* 所以这里用一个线程安全的ThreadLocal来返回一个字符编码一达到IoBuffer写的时候线程是安全的
* @author sunsharp
*
*/
@SuppressWarnings("rawtypes")
private static class ThreadLocalEncoder extends ThreadLocal{
// 重写ThreadLocal的initialValue方法
@Override
protected Object initialValue() {
return Charset.forName("UTF-8").newEncoder();
}
}
public String getLanguage() {
return language;
}
public void setLanguage(String language) {
this.language = language;
}
/**
* 构建xmpp 版本信息 <?xml version="1.0"?>
* @param majorVersion
* @param minorVersion
*/
public void setXMPPVersion(int majorVersion, int minorVersion) {
this.majorVersion = majorVersion;
this.minorVersion = minorVersion;
}
/**
* 指定xmpp协议的版本(主要版本) <?xml version="1.0"?>
* 用于通信前构建xml
* @return
*/
public int getMajorVersion() {
return majorVersion;
}
/**
* 指定xmpp协议的版本(小版本) <?xml version="1.0"?>
* 用于通信前构建xml
* @return
*/
public int getMinorVersion() {
return minorVersion;
}
}