最近比较有空,花了点时间写了个android局域网聊天工具,使用java的异步tcp通信。基本功能实现(简单的界面,聊天记录,发送文字,发送语音),在此小结一下。
Java (非android)局域网聊天工具源码,跟android的差别不大,参考:
http://download.csdn.net/detail/yarkey09/7052573
0,整个程序源码结构
1,聊天功能 (ServerSocketChannel & SocketChannel)
实现这个功能的时候有一个非常大的感受,就是写java程序真是方便!因为自己以前就写过windows上的java异步socket通信程序,所以这次几乎不需要修改很多代码,就可以搬过来。颇有Write one, run everywhere的feel。
个人认为java.nio的核心就是Selector和Buffer吧。通过Selector轮询各个已注册的socket的事件。若没有事件,则阻塞,若有事件则返回。因为在android,主线程不能做太多事情,
所以我起了一个新的线程,让Selector自个儿跑去。
以下是TcpWorkerThread类的源码,主要完成三件事
1,"开启"一个Selector
2,提供registToSelector方法
3,处理客户端的连接事件,处理socket接收消息事件
Class : TcpWorkerThread
package com.yarkey.tcp;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
import android.os.Handler;
import android.util.Log;
public class TcpWorkerThread extends Thread {
private static final String TAG = "TcpWorkerThread";
/** 出错!返回String,描述出错原因 */
public static final int EVENT_ERROR = 0;
/** 线程结束, 停止运行 */
public static final int EVENT_STOPPED = 1;
/** 收到来自客户端的tcp连接, 报告一个socketchannel */
public static final int EVENT_ACCEPTED = 2;
/** 收到来自客户端的tcp消息, 报告一个TcpArgs, content为FileSerial对象 */
public static final int EVENT_RECEIVED = 3;
/** SocketChannel,ServerSocketChannel关闭 */
public static final int EVENT_CLOSED = 4;
/** TCP线程往主线程通信 */
private Handler mHandler;
private Selector mSelector;
private boolean mIsRun = true;
protected static class TcpArgs {
SocketChannel sc;
Object content;// 接收消息
}
/**
* 如果抛出异常,不能进行异步通信了
*
* @throws Exception
*/
public TcpWorkerThread(Handler handler) throws Exception {
Log.d(TAG, "TcpWorkerThread contructor");
if (handler == null) {
throw new Exception("Handler is null!");
} else {
mHandler = handler;
}
mSelector = Selector.open();
}
/**
* 将一个服务端的ServerSocketChannel设置为非阻塞模式,并将其注册到selector中(OP_ACCEPT)
*
* @param ssc
* @throws IOException
*/
public void registToSelector(ServerSocketChannel ssc) throws IOException {
Log.d(TAG, "registToSelector, ServerSocketChannel");
ssc.configureBlocking(false);
mSelector.wakeup();
ssc.register(mSelector, SelectionKey.OP_ACCEPT);
}
/**
* 将一个客户端的SocketChannel设置为非阻塞模式,并将其注册到selector中(OP_READ)
*
* @param ss
* @throws IOException
*/
public void registToSelector(SocketChannel ss) throws IOException {
Log.d(TAG, "registToSelector, SocketChannel");
ss.configureBlocking(false);
mSelector.wakeup();
ss.register(mSelector, SelectionKey.OP_READ);
}
/**
* 停止线程运行
*/
public void stopWorkerThread() {
Log.d(TAG, "stopWorkerThread");
mIsRun = false;
mSelector.wakeup();
}
@Override
public void run() {
// TODO Auto-generated method stub
Log.d(TAG, "线程开始运行,run()");
// 用于装入接收到的数据
ByteBuffer buffer = ByteBuffer.allocate(1024);
while (mIsRun) {
int events = 0;
try {
events = mSelector.select();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
mHandler.obtainMessage(EVENT_ERROR, "Selector IOException").sendToTarget();// 出错
break;
}
if (events <= 0) {
// 走到这里,只能说明被wakeup了,应该是别的地方需要,因此这里暂停100ms
Log.d(TAG, "sleep 100 ms >>>");
try {
sleep(100);
} catch (InterruptedException e) {
// interrupt! ignore this
e.printStackTrace();
}
Log.d(TAG, "sleep 100 ms <<< wake up.");
continue;
}
Log.d(TAG, "mSelector.select(), events ===========================> " + events);
Set<SelectionKey> selectionKeys = mSelector.selectedKeys();
Iterator<SelectionKey> iter = selectionKeys.iterator();
// 代表连接成功后的socket
SocketChannel socketChannel;
while (iter.hasNext()) {
SelectionKey key = iter.next();
socketChannel = null;
// 服务端收到连接
if ((key.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT) {
ServerSocketChannel ssc = (ServerSocketChannel) key.channel();
try {
socketChannel = ssc.accept();
Log.d(TAG, "ssc.accept()");
} catch (IOException e) {
e.printStackTrace();
try {
ssc.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
mHandler.obtainMessage(EVENT_CLOSED, ssc).sendToTarget();
}
if (socketChannel != null) {
try {
socketChannel.configureBlocking(false);
socketChannel.register(mSelector, SelectionKey.OP_READ);
Log.d(TAG, "来自客户端的新连接");
mHandler.obtainMessage(EVENT_ACCEPTED, socketChannel).sendToTarget();
} catch (IOException e) {
e.printStackTrace();
try {
socketChannel.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
}
mHandler.obtainMessage(EVENT_CLOSED, socketChannel).sendToTarget();
}
} else {
Log.e(TAG, "socketChannel is null !");
}
iter.remove();
}
// 接收到客户的消息
else if ((key.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ) {
socketChannel = (SocketChannel) key.channel();
Log.d(TAG, "接收到新消息");
boolean hasException = false;
ByteArrayOutputStream byteOutput = new ByteArrayOutputStream();
while (true) {
// 把position设为0,把limit设为capacity
buffer.clear();
int a = 0;
try {
a = socketChannel.read(buffer);
} catch (Exception e) {
e.printStackTrace();
try {
socketChannel.close();
} catch (IOException e1) {
// TODO Auto-generated catch b