android socket链接 NIO非阻塞方式

最近在研究android的推送,一开始准备自己搭建推送服务器,并在android机上建立一个socket长连接,由于之前一直是用c++,在网上搜索一些资料后,临时写了一个基于NIO模式的客户端socket链接类,测试后能使用,当然还有很多问题,没有去修改了,因为最后发现,现在国内已经有现成的第三方推送平台,基于项目的开发时间有限,准备使用第三方推送平台,废话不多说,直接贴代码,说明都在代码中.

package cn.kidstone.cartoon.net;

import java.io.ByteArrayOutputStream;
import java.net.InetSocketAddress;
import java.net.SocketAddress;
import java.nio.ByteBuffer;
import java.nio.CharBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.nio.charset.Charset;

/**
 * 通过NIO方式进行socket链接
 * @author He Zhongqiu
 *
 */
public class TCPClient {
	/** 信道选择器 */
	private Selector mSelector;
	
	/** 服务器通信的信道 */
	private SocketChannel mChannel;
	
	/** 远端服务器ip地址 */
	private String mRemoteIp;
	
	/** 远端服务器端口 */
	private int mPort;
	
	/** 是否加载过的标识 */
	private boolean mIsInit = false;
	
	/** 单键实例 */
	private static TCPClient gTcp;
	
	private TCPClientEventListener mEventListener;
	
	/** 默认链接超时时间 */
	public static final int TIME_OUT = 10000;
	
	/** 读取buff的大小 */
	public static final int READ_BUFF_SIZE = 1024;
	
	/** 消息流的格式 */
	public static final String BUFF_FORMAT = "utf-8";
	
	public static synchronized TCPClient instance() {
		if ( gTcp == null ) {
			gTcp = new TCPClient();
		}
		return gTcp;
	}
	
	private TCPClient() {
		
	}
	
	/**
	 * 链接远端地址
	 * @param remoteIp
	 * @param port
	 * @param TCPClientEventListener
	 * @return
	 */
	public void connect( String remoteIp, int port, TCPClientEventListener tcel ) {
		mRemoteIp = remoteIp;
		mPort = port;
		mEventListener = tcel;
		connect();
	}
	
	/**
	 * 链接远端地址
	 * @param remoteIp
	 * @param port
	 * @return
	 */
	public void connect( String remoteIp, int port ) {
		connect(remoteIp,port,null);
	}
	
	private void connect() {
		//需要在子线程下进行链接
		MyConnectRunnable connect = new MyConnectRunnable();
		new Thread(connect).start();
	}
	
	/**
	 * 发送字符
	 * @param msg
	 * @return
	 */
	public boolean sendMsg(String msg) {
		boolean bRes = false;
		try {
			bRes = sendMsg(msg.getBytes(BUFF_FORMAT));
		} catch ( Exception e ) {
			e.printStackTrace();
		}
		
		return bRes;
	}
	
	/**
	 * 发送数据,此函数需要在独立的子线程中完成,可以考虑做一个发送队列
	 * 自己开一个子线程对该队列进行处理,就好像connect一样
	 * @param bt
	 * @return
	 */
	public boolean sendMsg( byte[] bt ) {
		boolean bRes = false;
		if ( !mIsInit ) {
			
			return bRes;
		}
		try {
			ByteBuffer buf = ByteBuffer.wrap(bt);
			int nCount = mChannel.write(buf);
			if ( nCount > 0 ) {
				bRes = true;
			}
		} catch ( Exception e ) {
			e.printStackTrace();
		}
		
		return bRes;
	}
	
	public Selector getSelector() {
		return mSelector;
	}
	
	/**
	 * 是否链接着
	 * @return
	 */
	public boolean isConnect() {
		if ( !mIsInit ) {
			return false;
		}
		return mChannel.isConnected();
	}
	
	/**
	 * 关闭链接
	 */
	public void close() {
		mIsInit = false;
		mRemoteIp = null;
		mPort = 0;
		try {
			if ( mSelector != null ) {
				mSelector.close();
			}
			if ( mChannel != null ) {
				mChannel.close();
			}
		} catch ( Exception e ) {
			e.printStackTrace();
		}
	}
	
	/**
	 * 重连
	 * @return
	 */
	public void reConnect() {
		close();
		connect();
	}
	
	/**
	 * 发送一个测试数据到服务器,检测服务器是否关闭
	 * @return
	 */
	public boolean canConnectServer() {
		boolean bRes = false;
		if ( !isConnect() ) {
			return bRes;
		}
		try {
			mChannel.socket().sendUrgentData(0xff);
		} catch ( Exception e ) {
			e.printStackTrace();
		}
		return bRes;
	}
	
	/**
	 * 每次读完数据后,需要重新注册selector读取数据
	 * @return
	 */
	private synchronized boolean repareRead() {
		boolean bRes = false;
		try {
			//打开并注册选择器到信道
			mSelector = Selector.open();
			if ( mSelector != null ) {
				mChannel.register(mSelector, SelectionKey.OP_READ);
				bRes = true;
			}
		} catch ( Exception e ) {
			e.printStackTrace();
		} 
		return bRes;
	}
	
	public void revMsg() {
		if ( mSelector == null ) {
			return;
		}
		boolean bres = true;
		while ( mIsInit ) {
			if ( !isConnect() ) {
				bres = false;
			}
			if ( !bres ) {
				try {
					Thread.sleep(100);
				} catch ( Exception e ) {
					e.printStackTrace();
				}
				
				continue;
			}
			
			try {
				//有数据就一直接收
				while (mIsInit && mSelector.select() > 0) {
					for ( SelectionKey sk : mSelector.selectedKeys() ) {
						//如果有可读数据
						if ( sk.isReadable() ) {
							//使用NIO读取channel中的数据
							SocketChannel sc = (SocketChannel)sk.channel();
							//读取缓存
							ByteBuffer readBuffer = ByteBuffer.allocate(READ_BUFF_SIZE);
							//实际的读取流
							ByteArrayOutputStream read = new ByteArrayOutputStream();
							int nRead = 0;
							int nLen = 0;
							//单个读取流
							byte[] bytes;
							//读完为止
							while ( (nRead = sc.read(readBuffer) ) > 0 ) {
								//整理
								readBuffer.flip();
								bytes = new byte[nRead];
								nLen += nRead;
								//将读取的数据拷贝到字节流中
								readBuffer.get(bytes);
								//将字节流添加到实际读取流中
								read.write(bytes);
								/
								//@ 需要增加一个解析器,对数据流进行解析
								
								/
								
								readBuffer.clear();
							}
							if ( nLen > 0 ) {
								if ( mEventListener != null ) {
									mEventListener.recvMsg(read);
								} else {
									String info = new String(read.toString(BUFF_FORMAT));
									System.out.println("rev:"+info);
								}
							}
							
							//为下一次读取做准备
							sk.interestOps(SelectionKey.OP_READ);
						}
						
						//删除此SelectionKey
						mSelector.selectedKeys().remove(sk);
					}
				}
			} catch ( Exception e ) {
				e.printStackTrace();
			}
		}
		
	}
	
	public interface TCPClientEventListener {
		/**
		 * 多线程下接收到数据
		 * @param read
		 * @return 
		 */
		void recvMsg(ByteArrayOutputStream read);
	}
	
	/**
	 * 链接线程
	 * @author HeZhongqiu
	 *
	 */
	private class MyConnectRunnable implements Runnable {

		@Override
		public void run() {
			// TODO Auto-generated method stub
			try {
				//打开监听信道,并设置为非阻塞模式
				SocketAddress ad = new InetSocketAddress(mRemoteIp, mPort);
				mChannel = SocketChannel.open( ad );
				if ( mChannel != null ) {
					mChannel.socket().setTcpNoDelay(false);
					mChannel.socket().setKeepAlive(true);
					
					//设置超时时间
					mChannel.socket().setSoTimeout(TIME_OUT);
					mChannel.configureBlocking(false);
					
					mIsInit = repareRead();
					
					//创建读线程
					RevMsgRunnable rev = new RevMsgRunnable();
					new Thread(rev).start();
				}
			} catch ( Exception e ) {
				e.printStackTrace();
			} finally {
				if ( !mIsInit ) {
					close();
				}
			}
		}
	}
	
	private class RevMsgRunnable implements Runnable {

		@Override
		public void run() {
			// TODO Auto-generated method stub
			revMsg();
		}
		
	}
}

其中,还需要完善的是:

1.发送消息,需要自己再创建一个消息队列,在另外的一个线程处理,如果在主线程下,会抛出异常,

2.自定义消息解析器,解析器的基类我就没定义了,可根据实际需求进行添加,根据自己的消息结构体,对消息数据流进行解析

3.链接断开后,收消息的轮询处理需要退出

  • 2
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值