Java Nio 笔记
网上的很多关于NIO的资料是不正确的,nio 支持阻塞和非阻塞模式
- 关于读写状态切换
在读写状态切换的情况下是不能使用regedit 方法注册,而应该使用以下方式进行
selectionKey.interestOps(SelectionKey.OP_READ|SelectionKey.OP_WRITE);
- setsoTimeout
只是说明允许连接阻塞时间,而不是连接持续时间。
- select(timeOut)
表示选择器阻塞时间,超过该事件关闭全部的channal
- serverSocketChannel.socket().setReuseAddress(true);
该设置应在绑定端口之前,否则该设置无效
如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用 端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息, 抛出“Address already in use: JVM_Bind”。
如果你的服务程序停止后想立即重启,不等60秒,而新套接字依旧 使用同一端口,此时 SO_REUSEADDR 选项非常有用。
- ConcurrentHashMap 与 HashMap 区别
ConcurrentHashMap会自动加锁,避免遍历时,map内容发生变化
*hashmap遍历时,发生插入,remove时会抛出异常
- tcp通讯协议的名词解释
1、send-Q 表示网路发送队列
对方没有收到的数据或者说没有Ack的,还是本地缓冲区.如果发送队列Send-Q不能很快的清零,可能是有应用向外发送数据包过快,或者是对方接收数据包不够快。
这两个值通常应该为0,如果不为0可能是有问题的。packets在两个队列里都不应该有堆积状态。可接受短暂的非0情况
2、recv-q 非由用户进程连接到此socket的复制的总字节数
3、send-q 非由远程主机传送过来的acknowledged总字节数
- nio通讯实例
/**
* 1、socket通讯
* 2、通过BlockingQueue实现多线程共享队列,先进先出,队列满了排队等待
* */
public class CommunicationServer implements Runnable{
private final static Logger log = LoggerFactory.getLogger(CommunicationServer.class); private int DEFAULT_SIZE = 1024;
private boolean isStartListen = false;
private String message = "";
private String serverKey = null;
private String serverIp = null;
private String serverPort = null;
private InetAddress clientIp = null;
private int clientPort = 0; /*事件选择器*/
private Selector selector = null;
private ServerSocketChannel serverSocketChannel;
private BlockingQueue<Attence> attenceQueue;
public CommunicationServer(String serverKey, String serverIP, String port, BlockingQueue<Attence> attenceQueue) {
try{
/*在linux下InetAddress.getLocalHost().getHostAddress()获取到的是127.0.0.1,只能本机监听访问,因此不能使用该代码*/
/*0.0.0.0表示全网监听端口*/
this.serverIp = serverIP;
}catch(Exception e){
e.printStackTrace();
log.error("获取本机Ip地址失败");
this.serverIp = serverIP;
}
this.serverPort = port;
this.serverKey = serverKey;
this.attenceQueue = attenceQueue;
}
@Override
public void run() {
/*启动监听服务器的监听服务*/
startAttenceServer();
listen();
message = "serverKey:"+ serverKey +",serverIp:"+ serverIp +", serverPort:"+ serverPort +",正常关闭数据";
/*停止接收数据服务线程*/
stopAttenceServer(message);
log.info("服务器线程执行完毕停止工作");
}
/*启动监听服务器的监听服务*/
private void startAttenceServer(){
message = "";
/*监听服务器端口*/
if(Common.isInteger(serverPort)){
try{
//创建一个新的selector
selector = Selector.open();
// 创建一个新的serverSocketChannel
serverSocketChannel = ServerSocketChannel.open(); /*
* 该设置应在绑定端口之前,否则该设置无效
* 如果端口忙,但TCP状态位于 TIME_WAIT ,可以重用 端口。如果端口忙,而TCP状态位于其他状态,重用端口时依旧得到一个错误信息, 抛出“Address already in use: JVM_Bind”。
* 如果你的服务程序停止后想立即重启,不等60秒,而新套接字依旧 使用同一端口,此时 SO_REUSEADDR 选项非常有用。
* */
serverSocketChannel.socket().setReuseAddress(true); // 设置为非堵塞模式,异步处理
serverSocketChannel.configureBlocking(false);
// 绑定到端口
serverSocketChannel.socket().bind(new InetSocketAddress("0.0.0.0", Integer.valueOf(serverPort))); // 在选择器里面注册关注这个服务器套接字通道的accept事件
// ServerSocketChannel只有OP_ACCEPT可用,OP_CONNECT,OP_READ,OP_WRITE用于SocketChannel
serverSocketChannel.register(selector,SelectionKey.OP_ACCEPT);
isStartListen = true;
message = "Server:服务已经启动,serverKey:"+ serverKey +",监听IP为"+ serverIp +",监听端口:"+ serverPort;
log.info(message);
}catch(Exception e){
message = "启动服务失败:serverKey:"+ serverKey +",监听serverIp:"+ serverIp +",监听端口serverPort:"+ serverPort +" 原因:"+ e.getMessage();
/*尝试关闭socket通讯*/
stopAttenceServer(message);
log.error(message);
}
}else{
message = "Server:端口错误";
log.info(message);
}
/*标记服务器是否启动成功*/
Server server = new Server();
server.setServerKey(serverKey);
server.setIsStart(isStartListen ? "1" : "0");
server.setIsCanStart(isStartListen ? "1" : "0");
server.setReason(message);
/*将服务器启动状态记录到数据库*/
ServerService serverService = SpringApplicationContextHolder.getBean(ServerService.class);
serverService.updateServerStatus(server);
}
/*启动监听*/
public void listen() {
while (isStartListen) {
try {
if(selector != null){
if(selector.select() == 0){
continue;
}
}else{
continue;
}
Set<SelectionKey> selectedKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectedKeys.iterator();
while (iterator.hasNext()) {
SelectionKey selectionKey = (SelectionKey) iterator.next();
iterator.remove();
handleKey(selectionKey);
}
selectedKeys.clear();
} catch (ClosedChannelException e) {
e.printStackTrace();
log.info(e.getMessage());
} catch (Exception e) {
e.printStackTrace();
log.info(e.getMessage());
}
try{
Thread.sleep(50);
}catch (InterruptedException ie) {
ie.printStackTrace();
log.info("sleep错误:"+ie.getMessage());
}
}
}
/*事件处理*/
private void handleKey(SelectionKey selectionKey){
try{
if(selectionKey.isValid()){
if(selectionKey.isAcceptable() ){
/*新的连接来临*/
/*得到和Selectionkey关联的Channel*/
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey.channel();
/*得到与客户端的套接字通道*/
SocketChannel socketChannel = serverSocketChannel.accept();
clientIp = socketChannel.socket().getInetAddress();
clientPort = socketChannel.socket().getPort();
log.info("serverKey:"+ serverKey +",监听serverIp:"+ serverIp +",监听端口serverPort:"+ serverPort + "接收客户端设备的新连接,来自于:"+ clientIp +":"+ clientPort);
//设置socketChannel为非阻塞的socketChannel
socketChannel.configureBlocking(false);
//在和客户端连接成功之后,为了可以接收到客户端的信息,需要给通道设置读|写的权限。
//同样将于客户端的通道在selector上注册,OP_READ对应可读事件(对方有写入数据),可以通过key获取关联的选择器
//socketChannel.register(selector, SelectionKey.OP_READ|SelectionKey.OP_WRITE);
socketChannel.register(selector, SelectionKey.OP_READ);
/*往客户端会写数据*/
sendDataToClient(socketChannel, clientIp, clientPort);
}else if(selectionKey.isReadable() ){
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
/*读取socket数据*/
byte[] socketData = readData(socketChannel, selectionKey);
/*解析数据*/
if(socketData != null && socketData.length > 0){
clientIp = socketChannel.socket().getInetAddress();
clientPort = socketChannel.socket().getPort();
String hexData = AttenceUtil.bytesToHexString(socketData);
parseData(hexData, socketChannel.getLocalAddress(), String.valueOf(clientIp),String.valueOf(clientPort));
}else{
log.info("serverKey:"+ serverKey +",监听serverIp:"+ serverIp +",监听端口serverPort:"+ serverPort + ";socketData为空");
}
if(selectionKey != null && selectionKey.isValid()){
selectionKey.interestOps(SelectionKey.OP_READ);
}
/*立即回应写内容*/
try{
String responseString = "response";
ByteBuffer buffer = Common.encode(responseString);
long bytes = socketChannel.write(buffer);
log.info("readable读取后立即返回写入字节数:"+ bytes);
}catch(Exception e){
String msg = "客户端设备"+ clientIp + ":"+ clientPort +"数据读取失败,移除连接";
log.info(msg);
try{
socketChannel.socket().close();
socketChannel.close();
}catch(Exception e1){
e1.printStackTrace();
msg = "尝试关闭客户端设备"+ clientIp +":"+ clientPort +"连接失败:"+ e1.getMessage();
log.info(msg);
}
}
}else if(selectionKey.isWritable()){
/*通过回写方式验证是否连接正常*/
selectionKey.interestOps(SelectionKey.OP_READ);
log.info(clientIp + ":"+ clientPort +"进入写状态");
}
}
}catch(IOException e){
e.printStackTrace();
try {
if(selectionKey != null){
selectionKey.cancel();
selectionKey.channel().close();
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
log.info(e1.getMessage());
}
log.info(e.getMessage());
}catch(Exception e){
e.printStackTrace();
try {
if(selectionKey != null){
selectionKey.cancel();
selectionKey.channel().close();
}
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
log.info(e1.getMessage());
}
log.info(e.getMessage());
}
}
/*数据读取*/
private byte[] readData(SocketChannel socketChannel, SelectionKey selectionKey) {
//创建一个用来读取socketChannel的readbuffer
ByteBuffer readbuffer = ByteBuffer.allocate(DEFAULT_SIZE);
readbuffer.clear();
try {
//用readbuffer来读取socketChannel的数据
int nbytes = socketChannel.read(readbuffer);
/*如果read()方法返回-1,则表示底层连接已经关闭,此时需要关闭信道。 关闭信道时,将从选择器的各种集合中移除与该信道关联的键。 */
if(nbytes == -1){
socketChannel.socket().close();
socketChannel.close();
return null;
}
//在readbuffer读取过数据之后,将readbuffer的位置设为0
readbuffer.flip();
byte[]data = new byte[readbuffer.limit()];
readbuffer.get(data, 0, readbuffer.limit());
return data;
} catch(ClosedChannelException e){
e.printStackTrace();
log.info(e.getMessage());
} catch (IOException e) {
e.printStackTrace();
clientIp = socketChannel.socket().getInetAddress();
clientPort = socketChannel.socket().getPort();
message = "serverKey:"+ serverKey +",serverIP:"+ serverIp +",serverPort:"+ serverPort +"。clientIp:"+ clientIp +",clientPort:"+ clientPort +",考勤设备网络连接异常中断,可能被断电:"+ e.getMessage();
log.info(message);
try{
socketChannel.socket().close();
socketChannel.close();
selectionKey.cancel();
}catch(Exception e0){
e0.printStackTrace();
log.info("serverKey:"+ serverKey +",serverIP:"+ serverIp +",serverPort:"+ serverPort +",clientPort:"+ clientPort +",。selectionKey.cancel()时,错误:"+e0.getMessage());
}
} return null;
}
/*数据解析*/
private void parseData(String hexData, SocketAddress socketAddress, String clientIP, String clientPort){
if (hexData != null){
log.info("来自"+ clientIP +":"+ clientPort +"原始数据:"+hexData);
}
}
/*停止服务*/
public void stopAttenceServer(String closeMsg) {
try {
Thread.sleep(1000);
} catch (Exception e) {
e.printStackTrace();
}
/*停止监听*/
this.isStartListen = false;
/*关闭selector*/
if(selector != null && selector.isOpen()){
try {
selector.close();
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
log.info(e.getMessage());
}
}
/*关闭serverSocketChannel*/
if(serverSocketChannel != null && serverSocketChannel.isOpen() ){
try {
serverSocketChannel.close();
} catch (IOException e) {
e.printStackTrace();
log.info(e.getMessage());
}
}
closeMsg = this.serverIp + ":" + this.serverPort + "服务连接关闭:"+closeMsg;
log.info(closeMsg);
/*标记该服务端服务停止运行*/
Server server = new Server();
server.setServerKey(serverKey);
server.setIsStart("0");
server.setIsCanStart("1");
server.setReason(closeMsg);
/*将服务停止运行消息写入数据库*/
ServerService serverService = SpringApplicationContextHolder.getBean(ServerService.class);
serverService.updateServerStatus(server);
}
/*往客户端回写数据*/
public void sendDataToClient(SocketChannel socketChannel, InetAddress clientIp, int clientPort){
String data = "";
ByteBuffer byteBuffer = Common.encode(data);
try {
socketChannel.write(byteBuffer);
} catch (IOException e) {
// TODO Auto-generated catch block
e.printStackTrace();
try {
socketChannel.socket().close();
socketChannel.close();
} catch (IOException e1) {
// TODO Auto-generated catch block
e1.printStackTrace();
message = e1.getMessage();
log.info(message);
}
message = e.getMessage();
log.info(message);
}
log.info(message);
}
}
- 参考资料
http://my.oschina.net/javagg/blog/3361
http://blog.csdn.net/weibing_huang/article/details/7368635
http://blog.csdn.net/rootsuper/article/details/8537498
http://blog.csdn.net/rootsuper/article/details/8537682
http://blog.csdn.net/rootsuper/article/details/8542236
Java Nio 笔记的更多相关文章
- Java nio 笔记:系统IO、缓冲区、流IO、socket通道
一.Java IO 和 系统 IO 不匹配 在大多数情况下,Java 应用程序并非真的受着 I/O 的束缚.操作系统并非不能快速传送数据,让 Java 有事可做:相反,是 JVM 自身在 I/O 方面 ...
- Java NIo 笔记001
1. Channel Channel接口只提供了两个方法: package java.nio.channels; public interface Channel { public boolean i ...
- 【Java nio】java nio笔记
缓冲区操作:缓冲区,以及缓冲区如何工作,是所有I/O的基础.所谓“输入/输出”讲的无非就是把数据移出货移进缓冲区.进程执行I/O操作,归纳起来也就是向操作系统发出请求,让它要么把缓冲区里的数据排干,要 ...
- Java NIO笔记(一):NIO介绍
Java NIO即Java Non-blocking IO(Java非堵塞I/O),由于是在Jdk1.4之后添加的一套新的操作I/O工具包,所以通常会被叫做Java New IO.NIO是为提供I/O ...
- Java NIO 完全学习笔记(转)
本篇博客依照 Java NIO Tutorial翻译,算是学习 Java NIO 的一个读书笔记.建议大家可以去阅读原文,相信你肯定会受益良多. 1. Java NIO Tutorial Java N ...
- Java NIO 核心组件学习笔记
背景知识 同步.异步.阻塞.非阻塞 首先,这几个概念非常容易搞混淆,但NIO中又有涉及,所以总结一下[1]. 同步:API调用返回时调用者就知道操作的结果如何了(实际读取/写入了多少字节). 异步:相 ...
- Java NIO学习笔记
Java NIO学习笔记 一 基本概念 IO 是主存和外部设备 ( 硬盘.终端和网络等 ) 拷贝数据的过程. IO 是操作系统的底层功能实现,底层通过 I/O 指令进行完成. 所有语言运行时系统提供执 ...
- 零拷贝详解 Java NIO学习笔记四(零拷贝详解)
转 https://blog.csdn.net/u013096088/article/details/79122671 Java NIO学习笔记四(零拷贝详解) 2018年01月21日 20:20:5 ...
- Java开发笔记(九十五)NIO配套的文件工具Files
NIO不但引进了高效的文件通道,而且新增了更加好用的文件工具家族,包括路径组工具Paths.路径工具Path.文件组工具Files.先看路径组工具Paths,该工具提供了静态方法get,输入某个文件的 ...
随机推荐
- 李洪强漫谈iOS开发[C语言-031]-逻辑短路
- MySQL源码之Thread cache
MySQL server为每一个connection建立一个thread为其服务,虽然thread create比着fork process代价高,单高并发的情况下,也不可忽略. 所以增加了Threa ...
- 【转】三十三、Android给ListView设置分割线Divider样式
原文网址:http://www.cnblogs.com/linjiqin/archive/2011/11/12/2246349.html 给ListView设置分割线,只需设置如下两个属性: andr ...
- [C# 网络编程系列]专题三:自定义Web服务器
转自:http://www.cnblogs.com/zhili/archive/2012/08/23/2652460.html 前言: 经过前面的专题中对网络层协议和HTTP协议的简单介绍相信大家对网 ...
- (转载)PHP数组传递是值传递而非引用传递
(转载)http://www.fengfly.com/plus/view-212127-1.html 在调用函数时通过将PHP数组作为实参赋给形参,在函数中修改,并不会影响到数组本身. 说明此过程中的 ...
- (转载)PHP 判断常量,变量和函数是否存在
(转载)http://www.jb51.net/article/17881.htm 如果你看懂了上面一句话,那么接下来都是废话,PHP手册写的还是很全的.一句话就把我标题中的问题全部解决了. 还是举几 ...
- (转载)javascript经典例子
(转载)http://www.blogjava.net/hadeslee/archive/2007/11/24/161778.html 一.验证类1.数字验证内1.1 整数1.2 大于0的整数 (用于 ...
- patch与diff的恩怨
一.概述 diff和patch是一对相辅相成的工具,在数学上来说,diff类似于对两个集合的差运算,patch类似于对两个集合的和运算.diff比较两个文件或文件集合的差异,并记录下来,生成一个dif ...
- Code Forces 711C Coloring Trees
C. Coloring Trees time limit per test 2 seconds memory limit per test 256 megabytes input standard i ...
- linq 学习笔记(一)
First: 找到符合条件的第一记录,就返回了,不管后面还有多少数据. Single: 先将记录都梳理一次,再找到符合要求的唯一记录. Single():操作一个集合,同时强要求只有一个对象匹配, ...