相对于非阻塞通信的复杂性,通常客户端并不需要使用非阻塞通信以提高性能,故这里只有服务端使用非阻塞通信方式实现

客户端:

package com.test.client;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.nio.channels.SocketChannel; import org.apache.log4j.Logger; import com.test.util.SocketIO; public class Client {
static Logger logger = Logger.getLogger(Client.class);
private int port = 10000;
private SocketChannel socketChannel; public Client(){
try {
socketChannel = SocketChannel.open();
InetAddress host = InetAddress.getLocalHost();
InetSocketAddress addr = new InetSocketAddress(host, port); socketChannel.connect(addr); logger.debug("***");
logger.debug("client ip:"+socketChannel.socket().getLocalAddress());
logger.debug("client port:"+socketChannel.socket().getLocalPort());
logger.debug("server ip:"+socketChannel.socket().getInetAddress());
logger.debug("server port:"+socketChannel.socket().getPort());
logger.debug("***");
} catch (IOException e) {
e.printStackTrace();
logger.error("Cilent socket establish failed!");
}
logger.info("Client socket establish success!");
} public void request(String request){
try{
DataInputStream input = SocketIO.getInput(socketChannel.socket());
DataOutputStream output = SocketIO.getOutput(socketChannel.socket()); if(null != request && !request.equals("")){
byte[] bytes = request.getBytes("utf-8");
output.write(bytes); bytes = new byte[64];
int num = input.read(bytes);
byte[] answer = new byte[num];
System.arraycopy(bytes, 0, answer, 0, num);
if(num > 0){
logger.info("server answer:"+new String(answer,"utf-8"));
}else{
logger.info("No server answer.");
}
}
}catch(Exception e){
e.printStackTrace();
logger.error("client request error");
}finally{
if(null != socketChannel){
try{
socketChannel.close();
}catch(Exception e){
e.printStackTrace();
logger.error("socket close error");
}
}
}
} public static void main(String[] args){
Client client1 = new Client();
//Client client2 = new Client();
client1.request("your name?");
//client2.request("your name?");
}
}

服务端:

package com.test.server;

import java.net.InetSocketAddress;
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.nio.charset.Charset;
import java.util.Iterator;
import java.util.Set; import org.apache.log4j.Logger; public class Server {
static Logger logger = Logger.getLogger(Server.class);
private Selector selector;
private ServerSocketChannel serverSocketChannel;
private int queueNum = 10;
private int bindPort = 10000;
private int step = 0;
private Charset charset = Charset.forName("utf-8");
private ByteBuffer buffer = ByteBuffer.allocate(64); public Server(){
try{
//为ServerSocketChannel监控接收连接就绪事件
//为SocketChannel监控连接就绪事件、读就绪事件以及写就绪事件
selector = Selector.open();
//作用相当于传统通信中的ServerSocket
//支持阻塞模式和非阻塞模式
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.socket().setReuseAddress(true);
//非阻塞模式
serverSocketChannel.configureBlocking(false);
//serverSocketChannel.socket()会获得一个和当前信道相关联的socket
serverSocketChannel.socket().bind(new InetSocketAddress(bindPort),queueNum); //注册接收连接就绪事件
//注册事件后会返回一个SelectionKey对象用以跟踪注册事件句柄
//该SelectionKey将会放入Selector的all-keys集合中,如果相应的事件触发
//该SelectionKey将会放入Selector的selected-keys集合中
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
}catch(Exception e){
e.printStackTrace();
logger.error("Server establish error!");
}
logger.info("Server start up!");
} public void service() throws Exception{
//判断是否有触发事件
while(selector.select() > 0){
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator(); while(iterator.hasNext()){
SelectionKey selectionKey = iterator.next();
//处理事件后将事件从Selector的selected-keys集合中删除
iterator.remove();
try{
if(selectionKey.isAcceptable()){
this.Acceptable(selectionKey);
}else if(selectionKey.isReadable()){
this.Readable(selectionKey);
}else if(selectionKey.isWritable()){
this.Writable(selectionKey);
}
}catch(Exception e){
e.printStackTrace();
logger.error("event deal exception!");
}
}
}
} private void Acceptable(SelectionKey selectionKey) throws Exception{
logger.info("accept:"+(++step)); ServerSocketChannel ssc = (ServerSocketChannel)selectionKey.channel();
SocketChannel sc = (SocketChannel)ssc.accept(); sc.configureBlocking(false);
sc.register(selector, SelectionKey.OP_READ); logger.info(selectionKey.hashCode());
} private void Readable(SelectionKey selectionKey) throws Exception{
logger.info("read:"+(++step)); SocketChannel sc = (SocketChannel)selectionKey.channel(); buffer.clear();
int num = sc.read(buffer);
String request = "";
if(num > 0){
buffer.flip(); request = charset.decode(buffer).toString();
sc.register(selector, SelectionKey.OP_WRITE,request);
}else{
sc.close();
} logger.info(selectionKey.hashCode()+":"+request);
} private void Writable(SelectionKey selectionKey) throws Exception{
logger.info("write:"+(++step)); String request = (String)selectionKey.attachment();
SocketChannel sc = (SocketChannel)selectionKey.channel(); String answer = "not supported";
if(request.equals("your name?")){
answer = "server";
} logger.info(selectionKey.hashCode()+":"+answer); buffer.clear();
buffer.put(charset.encode(answer));
buffer.flip();
while(buffer.hasRemaining())
sc.write(buffer); sc.close();
} public static void main(String[] args) {
Server server = new Server();
try{
server.service();
}catch(Exception e){
e.printStackTrace();
logger.error("Server run exception!");
}
}
}

IO工具类:

package com.test.util;

import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.Socket; public class SocketIO{
public static DataInputStream getInput(Socket socket) throws IOException{
//接收缓存区大小,socket获取输入流之前设置
socket.setReceiveBufferSize(10);
InputStream input = socket.getInputStream();
return new DataInputStream(input);
} public static DataOutputStream getOutput(Socket socket) throws IOException{
//发送缓存区大小,socket获取输出流之前设置
socket.setSendBufferSize(10);
OutputStream output = socket.getOutputStream();
return new DataOutputStream(output);
}
}

log4j日志配置文件:

log4j.rootLogger=debug,logOutput

log console out put
log4j.appender.logOutput=org.apache.log4j.ConsoleAppender
log4j.appender.logOutput.layout=org.apache.log4j.PatternLayout
log4j.appender.logOutput.layout.ConversionPattern=%p%d{[yy-MM-dd HH:mm:ss]}[%c] -> %m%n

server端的运行结果:

INFO[13-10-16 11:40:41][com.test.server.Server] -> Server start up!
INFO[13-10-16 11:40:53][com.test.server.Server] -> accept:1
INFO[13-10-16 11:41:14][com.test.server.Server] -> 20469344
INFO[13-10-16 11:41:21][com.test.server.Server] -> read:2
INFO[13-10-16 11:41:37][com.test.server.Server] -> 11688861:your name?
INFO[13-10-16 11:43:00][com.test.server.Server] -> write:3
INFO[13-10-16 11:43:00][com.test.server.Server] -> 11688861:server

可以看到readable方法中的SelectionKey和writable方法中的SelectionKey的哈希码是完全相同的,是同一个SelectionKey

SelectionKey是在SocketChannel类或ServerSocketChannel类注册要监控的事件时产生的,这两个类本身并没有register方法,需要查看它们共同父类AbstractSelectableChannel(只有关键代码):

public abstract class AbstractSelectableChannel
extends SelectableChannel{
......
// Keys that have been created by registering this channel with selectors.
// They are saved because if this channel is closed the keys must be
// deregistered. Protected by keyLock.
private SelectionKey[] keys = null; public final SelectionKey register(Selector sel, int ops, Object att)
throws ClosedChannelException{
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
synchronized (regLock) {
if (blocking)
throw new IllegalBlockingModeException();
SelectionKey k = findKey(sel);
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
if (k == null) {
// New registration
k = ((AbstractSelector)sel).register(this, ops, att);
addKey(k);
}
return k;
}
} private SelectionKey findKey(Selector sel) {
synchronized (keyLock) {
if (keys == null)
return null;
for (int i = 0; i < keys.length; i++)
if ((keys[i] != null) && (keys[i].selector() == sel))
return keys[i];
return null;
}
} void removeKey(SelectionKey k) { // package-private
synchronized (keyLock) {
for (int i = 0; i < keys.length; i++)
if (keys[i] == k) {
keys[i] = null;
keyCount--;
}
((AbstractSelectionKey)k).invalidate();
}
}
......
}

ServerSocketChannel和Socketchannel向Selector中注册了特定事件,Selector就会监控这些事件是否发生。ServerSocketChannel和Socketchannel都为AbstractSelectableChannel类的子类,AbstractSelectableChannel类的register方法负责注册事件,该方法会返回一个SelectionKey对象,该对象用于跟踪被注册事件

public abstract class SelectionKey {
protected SelectionKey() { } public abstract SelectableChannel channel(); public abstract Selector selector();
......
}

一个Selector对象中包含了3种类型的键集(即SelectionKey集合,SelectionKey在以下部分被称为“键”)

1,all-keys:所有注册至该Selector的事件键集(selector.keys())

2,selected-keys:相关事件已经被Selector捕获的键集(selector.selectedKeys())

3,cancel-keys:已被取消的键集(无法访问该集合)

selected-keys和cancel-keys都为all-keys的子集,对于一个新建的Selector,这3个键集都为空

注册事件时会将相应的SelectionKey加入Selector的all-keys键集中

取消SelectionKey或者关闭了SelectionKey相关联的Channel,则会将相应的SelectionKey加入cancel-keys键集中

当执行选择器的选择操作时(selector.select(),对于选择器来说,这个方法应该是相当重要的):

1,将cancel-keys中的每个SelectionKey从3个键集中移除(如果存在的话),并注销SelectionKey所关联的Channel,cancel-keys键集变为空集。

2,如果监控的事件发生,Selector会将相关的SelectionKey加入selected-keys键集中

以下为对源代码的分析、推测:

Selector作为选择器,保存了所有的Selectionkey(注册的,取消的,触发的),通过上面的AbstractSelectableChannel类的源代码,发现Channel本身也保存了一个自身关联的SelectionKey数组,这看起来是完全没有必要的,但是仔细看一下register方法,能看出些许端倪:

Selector本身维护了3个集合,all-keys,selected-keys和cancel-keys,频繁的注册操作、取消注册将会导致这3个集合频繁的变化,伴随频繁变化的是频繁的加锁,这会严重的降低Selector的性能,毕竟一个Selector会被多个Channel作为选择器使用,本身非阻塞的实现方式就是提高性能的一种解决方式

当注册新的事件时,如果存在该通道相关的SelectionKey,则更新该SelectionKey所关注的事件以及其携带的附加信息,如果不存在,则添加新的SelectionKey

这样做的好处是,比起删除以前的SelectionKey,添加新的SelectionKey,修改SelectionKey所关注的事件以及其携带的附加信息显然是更好的选择,毕竟不需要修改Selector所维护的键集,当然也不需要频繁加锁(通过查看Selector类的api,SelectionKey并不是thread-safe的,显然并没有加锁,但是并没有什么问题),能够提供更好的性能

总之,SelectionKey的哈希码会重复是很正常的,毕竟不是单纯的注册时新建、触发后删除方式,java实现时进行了优化

Java NIO Socket 非阻塞通信的更多相关文章

  1. java nio实现非阻塞Socket通信实例

    服务器 package com.java.xiong.Net17; import java.io.IOException; import java.net.InetSocketAddress; imp ...

  2. Java NIO 同步非阻塞

    同步非阻塞IO (NIO) NIO是基于事件驱动思想的,实现上通常采用Reactor(http://en.wikipedia.org/wiki/Reactor_pattern)模式,从程序角度而言,当 ...

  3. java网络通信之非阻塞通信

    java中提供的非阻塞类主要包含在java.nio,包括: 1.ServerSocketChannel:ServerSocket替代类,支持阻塞与非阻塞: 2.SocketChannel:Socket ...

  4. NIO Socket非阻塞模式

    NIO主要原理和适用 NIO 有一个主要的类Selector,这个类似一个观察者,只要我们把需要探知的socketchannel告诉Selector,我们接着做别的事情,当有 事件发生时,他会通知我们 ...

  5. JAVA NIO使用非阻塞模式实现高并发服务器

    参考:http://blog.csdn.net/zmx729618/article/details/51860699  https://zhuanlan.zhihu.com/p/23488863 ht ...

  6. JAVA基础知识之网络编程——-基于NIO的非阻塞Socket通信

    阻塞IO与非阻塞IO 通常情况下的Socket都是阻塞式的, 程序的输入输出都会让当前线程进入阻塞状态, 因此服务器需要为每一个客户端都创建一个线程. 从JAVA1.4开始引入了NIO API, NI ...

  7. 用Java实现非阻塞通信

    用ServerSocket和Socket来编写服务器程序和客户程序,是Java网络编程的最基本的方式.这些服务器程序或客户程序在运行过程中常常会阻塞.例如当一个线程执行ServerSocket的acc ...

  8. UE4 Socket多线程非阻塞通信

    转自:https://blog.csdn.net/lunweiwangxi3/article/details/50468593 ue4自带的Fsocket用起来依旧不是那么的顺手,感觉超出了我的理解范 ...

  9. java并发之非阻塞算法介绍

    在并发上下文中,非阻塞算法是一种允许线程在阻塞其他线程的情况下访问共享状态的算法.在绝大多数项目中,在算法中如果一个线程的挂起没有导致其它的线程挂起,我们就说这个算法是非阻塞的. 为了更好的理解阻塞算 ...

随机推荐

  1. Cannot proxy target class because CGLIB2 is not available. Add CGLIB to the class path or specify proxy interfaces

    问题解决:缺少jar包 cglib-2.1.3.jar

  2. sdfsdf

    http://www.cocoachina.com/bbs/read.php?tid-234704.html 选择工程->Build Settings -> Code Signing -& ...

  3. .net,sessionState的Session共享问题解决方案

    最近项目因为要负载均衡所以就使用了sessionState的Session共享,但是却发现多台服务器中有个别服务器的Session没有共享,于是就有了这篇文章,下面开始说说. 这个基本上就分两种情况: ...

  4. Chrome已原生支持“Chrome To Mobile”

    完成PC和手机端Chrome的同gmail帐号绑定后,即可按如下操作进行: 已知在版本“19.0.1084.15”中,这个功能默认未开启,需要进入“chrome://flags/”进行手工启用(早几期 ...

  5. 【转】Django+Mysql安装配置详解(Linux)

    参考:http://dmyz.org/archives/110 报错TemplateDoesNotExist at 解决: 新建mysite/articles/article.html文件: 文件内容 ...

  6. CodeBlocks+opencv2.4.4+cmake+MinGW

    /*-----------------------------------------------------------------------------*   *   版权声明:*   可以任意 ...

  7. PHP 201307 月最新手册chm 免费下载

    本次更新两个版本  php_manual_zh_notreview.chm (15MB 不带评论)     php_manual_zh_review.chm    (32MB 带评论)     制作方 ...

  8. xilinx cpld XC95144XL 最小系统板

    手上有几块xilinx的CPLD芯片XC95144,闲着无聊,打样的几块板子回来玩玩. 全部引脚引出,外接4个LED灯和一个Power灯,做成了50mm*50mm的板子,省钱(O(∩_∩)O). 下面 ...

  9. oracle 中v$sqlarea,v$sql,v$session,gv$session,远程连接等问题

    一.分析 (1)使用 least recently used (LRU) algorithm 来管理的,不用的自动踢出,可以使用keep 将需要的语句 保存在 library cache中. 所以应该 ...

  10. sensor的skipping and binning 模式

    关于sensor的缩放存在两种模式 binning mode 和skip mode skipping mode 就是把想要的数据采集上来 把其余的数据扔掉 如下所示 column skip 2 row ...