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

浙公网安备 33010602011771号