Java NIO Socket 非阻塞通信
相对于非阻塞通信的复杂性,通常客户端并不需要使用非阻塞通信以提高性能,故这里只有服务端使用非阻塞通信方式实现
客户端:
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 非阻塞通信的更多相关文章
- java nio实现非阻塞Socket通信实例
服务器 package com.java.xiong.Net17; import java.io.IOException; import java.net.InetSocketAddress; imp ...
- Java NIO 同步非阻塞
同步非阻塞IO (NIO) NIO是基于事件驱动思想的,实现上通常采用Reactor(http://en.wikipedia.org/wiki/Reactor_pattern)模式,从程序角度而言,当 ...
- java网络通信之非阻塞通信
java中提供的非阻塞类主要包含在java.nio,包括: 1.ServerSocketChannel:ServerSocket替代类,支持阻塞与非阻塞: 2.SocketChannel:Socket ...
- NIO Socket非阻塞模式
NIO主要原理和适用 NIO 有一个主要的类Selector,这个类似一个观察者,只要我们把需要探知的socketchannel告诉Selector,我们接着做别的事情,当有 事件发生时,他会通知我们 ...
- JAVA NIO使用非阻塞模式实现高并发服务器
参考:http://blog.csdn.net/zmx729618/article/details/51860699 https://zhuanlan.zhihu.com/p/23488863 ht ...
- JAVA基础知识之网络编程——-基于NIO的非阻塞Socket通信
阻塞IO与非阻塞IO 通常情况下的Socket都是阻塞式的, 程序的输入输出都会让当前线程进入阻塞状态, 因此服务器需要为每一个客户端都创建一个线程. 从JAVA1.4开始引入了NIO API, NI ...
- 用Java实现非阻塞通信
用ServerSocket和Socket来编写服务器程序和客户程序,是Java网络编程的最基本的方式.这些服务器程序或客户程序在运行过程中常常会阻塞.例如当一个线程执行ServerSocket的acc ...
- UE4 Socket多线程非阻塞通信
转自:https://blog.csdn.net/lunweiwangxi3/article/details/50468593 ue4自带的Fsocket用起来依旧不是那么的顺手,感觉超出了我的理解范 ...
- java并发之非阻塞算法介绍
在并发上下文中,非阻塞算法是一种允许线程在阻塞其他线程的情况下访问共享状态的算法.在绝大多数项目中,在算法中如果一个线程的挂起没有导致其它的线程挂起,我们就说这个算法是非阻塞的. 为了更好的理解阻塞算 ...
随机推荐
- Hadoop中的各种排序
本篇博客是金子在学习hadoop过程中的笔记的整理,不论看别人写的怎么好,还是自己边学边做笔记最好了. 1:shuffle阶段的排序(部分排序) shuffle阶段的排序可以理解成两部分,一个是对sp ...
- MS-DOS 7.10完整安装版(含图文安装程序)
大家知道,要想学习或使用DOS,安装一个DOS并进行实际操作是非常必要的.MS-DOS 7.10是一个非常好且强大实用的操作系统,而且兼容性和性能都十分强.要在系统中安装MS-DOS 7.10,可以使 ...
- Linux 的档案权限与目录配置
档案权限 Linux最优秀的地方之一,就在于他的多人多任务环境. 而为了让各个使用者具有较保密的档案数据,因此档案的权限 管理就变的很重要了. Linux一般将档案可存取的身份分为三个类别,分别是 o ...
- MVC中的扩展点(六)ActionResult
ActionResult是控制器方法执行后返回的结果类型,控制器方法可以返回一个直接或间接从ActionResult抽象类继承的类型,如果返回的是非ActionResult类型,控制器将会将结果转换为 ...
- Chrome的隐身模式
先来说说隐身模式的启用方法吧 1.键盘快捷:Ctrl + Shift + N. 2.在Windows7下的任务栏处,右击“Chrome”图标,会出一个下拉菜单,点击“新建隐身窗口”. 3.你还可以在一 ...
- (转载)file_get_contents("php://input")
(转载)http://taoshi.blog.51cto.com/1724747/1165499 $data = file_get_contents("php://input"); ...
- Windows中APACHE开启fastcgi后无法连接数据库
环境:Windows server 2003 x64Apache 2.2.14mod_fcgid-2.2b-w32.zipPHP VC9 x86 Non Thread Safe(用Visual C++ ...
- Red5 1.0.5安装过程记录
Red5从旧的服务器切换到了github上后,截至20150702仍未更新文档.为了搭建Red5开发环境,我像无头苍蝇一样乱转了很多博客和StackOverflow.藉此记录这次安装过程,希望能够帮助 ...
- bat 批处理 字符串 截取
由于项目中配置项太多,经常有同事在配置xml的时候,讲 配置的路径搞错,先需要搞一个脚本,可以自动将路径截取出来, 晚上收集了点资料,暂时先上几个 bat 后面留着 ,具体实现. @echo off ...
- Visual Studio 2013中的新项目对话框
在Visual Studio 2013,我们推出了添加新的项目对话框. 此对话框取代了是曾在2012年这个的对话框作品,所有ASP.NET项目(MVC,Web窗体和Web API). 这就是我们如何提 ...