socketChannel 是java中用于建立异步socket连接的工具类,他和socket 非常类似。相似的 ServerSocketChannel 对应于 ServerSocket,即socket 的服务端。在java 中这两个类是实现异步socket的关键类。
此外还有几个十分关键的工具类Selector ,顾名思义seletor 是异步socket中的一个通道选择器,我的理解中类似一个socket的map集合,凭借selector 的强大功能我们不再需要为每一个socket通道维持一个专门的线程,我们可以在一个线程里处理所有的连接。
先看看服务端的初始化
// 打开通道管理器
selector = Selector.open();
// 打开一个未绑定的服务端通道对象
serverChannel = ServerSocketChannel.open();
// 将此服务端对象绑定端口,Ip默认是本机的Ip,如果本机有多个ip,需要指定ip
serverChannel.socket().bind(new InetSocketAddress(PORT));
// 设定服务端为非阻塞方式工作
serverChannel.configureBlocking(false);
// 把设定好的服务端注册到通道管理器中 ,该操作会创建一个选择键 serverChannel.register(selector, SelectionKey.OP_ACCEPT, BUFFER_SIZE);
必须经过注册之后selector 通道管理器中的值才会大于零
selector的selectionKey 有四种状态,acceptable , connectable, readable,writable 可用isXXX判断
在线程中执行以下代码:(服务端)
while (selector.select() > 0) { //此方法是一个阻塞操作
//Log.e(TAG,"外层循环");
Set<SelectionKey> selectionKeys= selector.selectedKeys(); // 获取全部的已选择键,
//可以直接移除单不可直接添加
Iterator<SelectionKey> it= selectionKeys.iterator();
while (it.hasNext()){
SelectionKey skey=it.next(); //对已经注册的socket的集合进行迭代
it.remove(); // 被移除的是集合selectionKeys中的对象不是selector的对象
//该操作用于结束内层循环
Boolean flag = false;
if (skey.isAcceptable()) { //有新的连接请求
SocketChannel channel = serverChannel.accept();
String clientIp = getClientIp(skey,channel);
channel.configureBlocking(false); //不阻塞
Log.e(TAG,"client online "+ clientIp);
new NSServerClientOnline(onlines, clientIp).run(); //自定义客户端上线回调
try {
channel.register(selector, SelectionKey.OP_READ); //重新注册为可读状态
} catch (Exception e) {
flag = true;
skey.cancel();
if (skey.channel() != null) {
skey.channel().close();
}
}
//
}
if (skey.isReadable()) {
StringBuffer message = new StringBuffer();
SocketChannel channel = (SocketChannel) skey.channel();
String clientIp = getClientIp(skey,channel);
ByteBuffer buffer = ByteBuffer.allocate(BUFFER_SIZE);
try {
if(channel.read(buffer)<0){
flag = true;
skey.cancel();
if (skey.channel() != null) {
skey.channel().close();
}
}
while (channel.read(buffer) > 0) {
}
buffer.flip(); //此方法刷新缓存的buffer 此方法慎用他回修改selectionKey的状态
message.append(charse.decode(buffer));
if (null != message && message.length() > 0) {
Log.e(TAG,"服务端收到消息" + message);
new NSServerReceive(receives, message, clientIp).run();
}
skey.interestOps(SelectionKey.OP_READ); //修改 读写状态
} catch (Exception e) {
flag = true;
skey.cancel();
if (skey.channel() != null) {
skey.channel().close();
}
}
}
if (flag) {
new NSServerClientOffline(offLines, getClientIp(skey, null)).run();
}
}
}
instance().start(); // 如果异常退出大循环 重启服务
} catch (IOException e) {
e.printStackTrace();
Log.e(TAG," socekct server crash");
}
客户端代码类似服务端,这里提供一个客户端判断连接断开的方法,,网上找的不知道可靠不
就是当连接状态可读的时候去试读取信息
if(clientChannel.read(buffer)<0){
Log.e(TAG,"socket client read buffer");
flag = true;
skey.cancel();
if (skey.channel() != null) {
skey.channel().close();
}
break TT; // 读取的内容为-1 判断为连接断开
// 因为这是一个异步的socket 所以可以放入死循环线程中,所以基本可以立马收到断开连接的消息
}