package com.test.upgrade;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.SocketChannel;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Set;
/**
* Created by rain_win on 2017/6/8.
* */
public class Upgrade {
public static void main(String[] args) {
// tcpClient();
nioTcpClient();
}
/**
* 使用nio 建立tcp 连接
*/
private static void nioTcpClient() {
// 1,远程服务器地址(ip:port)
InetSocketAddress serverAddress = new InetSocketAddress("192.168.48.1", 1219);
String localIp = null;
try {
localIp = InetAddress.getLocalHost().getHostAddress();
} catch (UnknownHostException e) {
e.printStackTrace();
}
String fileName = "LX_WIFI_v017-022.img";
String url = "http://" + localIp + ":8080/" + fileName;
String outStr = "SQ" + url;
System.out.println("outStr = " + outStr);
// 1.1 初始化 byteBuffer 用于接收数据
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
try {
// 2,创建SocketChannel
SocketChannel socketChannel = SocketChannel.open();
// 2.1,设置非阻塞:
socketChannel.configureBlocking(false);
// socketChannel.socket().setSoTimeout(20000);
// socketChannel.socket().connect();
// 3,创建 selector
Selector selector = Selector.open();
// 4,socketChannel 注册到 selector中,并且声明,对SelectionKey.OP_CONNECT 感兴趣:
/* 4.1 OP_CONNECT:用于套接字连接操作的操作集位
* 假定在选择操作(select()方法)开始时,
* 选择键的 interest 集合中已包含 OP_CONNECT。如果选择器检测到相应的套接字通道已为完成其连接序列而准备就绪,
* 或者有一个挂起的错误,那么它会向该键的 ready 集合中添加 OP_CONNECT,并将该键添加到已选择键集中。
*
* 4.2 OP_ACCEPT: 用于套接字接受操作的操作集位。
* 假定在选择操作开始时,选择键的 interest 集合中已包含 OP_ACCEPT。如果选择器检测到相应的服务器套接字通道已为接受另一个连接而准备就绪,
* 或者有一个挂起的错误,那么它会向该键的 ready 集合中添加 OP_ACCEPT,并将该键添加到已选择键集中。
* OP_ACCEPT用于Server端,当有客户端接入时,会触发
*
* 4.3 OP_READ: 用于读取操作的操作集位
* 假定在选择操作开始时,选择键的 interest 集合中已包含 OP_READ。如果选择器检测到相应的通道已为读取准备就绪、
* 已经到达流的末尾、已经被远程关闭而无法进行进一步的读取,或者有一个挂起的错误,
* 那么它会向该键的 ready 集合中添加 OP_READ,并将该键添加到已选择键集中
*
* 4.4 OP_WRITE: 用于写入操作的操作集位
* 假定在选择操作开始时,选择键的 interest 集合中已包含 OP_WRITE。如果选择器检测到相应的通道已为写入准备就绪、
* 已经被远程关闭而无法进行进一步的写入,或者有一个挂起的错误,那么它会向该键的 ready 集合中添加 OP_WRITE,并将该键添加到已选择键集中
*
*/
SelectionKey registerKey = socketChannel.register(selector, SelectionKey.OP_CONNECT);
// 5,socketChannel 连接到远程服务器
socketChannel.connect(serverAddress);
// socketChannel.socket().connect(serverAddress,5000);
int i = 0;
boolean hasWrite = false;
boolean disConnect = false;
while (true) {
i++;
System.out.println("=============i : " + i);
/*
* Selects a set of keys whose corresponding channels are ready
*
* selector.select()方法,api解释如下:
* 选择一组键,其相应的通道已为 I/O 操作准备就绪。
* 此方法执行处于阻塞模式的选择操作。仅在至少选择一个通道、调用此选择器的 wakeup 方法,或者当前的线程已中断(以先到者为准)后此方法才返回。
* 返回:
* 已更新其准备就绪操作集的键的数目,该数目可能为零
* 抛出:
* IOException - 如果发生 I/O 错误
* ClosedSelectorException - 如果此选择器已关闭
* 个人理解:
* 一个通道对应一个键:
* socketChannel调用 register方法时,返回一个 SelectionKey。如果是该通道就绪,则selector.select()方法返回的键组中,肯定有一个
* selectionKey 与 register方法返回的 selectionKey相等。
* socketChannel.register(selector, SelectionKey.OP_CONNECT)
* select()返回的是ready的Channel个数,因为此处只注册了一个channel,且没有设置超时是间,所以只可能返回1
* select()方法是阻塞的
*/
int select = selector.select();
System.out.println("select:" + select);
if (select > 0) {
/* get this selector's selected-key set.
* selector.selectedKeys()方法:
* 先看API解析:
*
* 返回此选择器的已选择键集。
* 可从已选择键集中移除键,但是无法直接添加键。试图向该键集中添加对象会导致抛出 UnsupportedOperationException。
* 已选择键集是非线程安全的。
* 返回:
* 此选择器的已选择键集
* 抛出:
* ClosedSelectorException - 如果此选择器已关闭
* 个人理解:
* 注册到 selector 中的所有 chanel,如果有一个channel就绪了,就会返回selectionKey
* 所有的channel 返回的selectionKey 则是 Set<SelectionKey>
*
*/
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
System.out.println("selectionKeys.size:" + selectionKeys.size());
while (iterator.hasNext()) {
SelectionKey selectionKey = iterator.next();
// selectionKey.interestOps() 方法返回的是 interest 集合。
int interestOps = selectionKey.interestOps();
System.out.println("interestOps:" + Integer.toBinaryString(interestOps));
System.out.println("[可写,可读]:["+selectionKey.isWritable() + ","+selectionKey.isReadable()+"]");
// selectionKey.readyOps() 返回的是 ready的 interest 集合。
System.out.println("readyOps:"+Integer.toBinaryString(selectionKey.readyOps()) );
if (selectionKey.isConnectable()) {
System.out.println("tcp 连接成功");
SocketChannel currentChannel = (SocketChannel) selectionKey.channel();
/*
* 3个常用的方法:
* <1> currentChannel.isConnectionPending();
* API解释:
* 判断此通道上是否正在进行连接操作。
* 返回:
* 当且仅当已在此通道上发起连接操作,但是尚未通过调用 finishConnect 方法完成连接时才返回 true
*
* <2> currentChannel.isConnected();
* API解释:
* 判断是否已连接此通道的网络套接字。
* 返回:
* 当且仅当已连接此通道的网络套接字时才返回 true
*
* <3> currentChannel.finishConnect();
* API解释:
* 完成套接字通道的连接过程。
* 通过将套接字通道置于非阻塞模式,然后调用其 connect 方法来发起非阻塞连接操作。
* 一旦建立了连接,或者尝试已失败,该套接字通道就变为可连接的,并且可调用此方法完成连接序列。
* 如果连接操作失败,则调用此方法将导致抛出合适的 IOException。
* 如果已连接了此通道,则不阻塞此方法并且立即返回 true。
* 如果此通道处于非阻塞模式,那么当连接过程尚未完成时,此方法将返回 false。
* 如果此通道处于阻塞模式,则在连接完成或失败之前将阻塞此方法,并且总是返回 true 或抛出描述该失败的、经过检查的异常。
* 可在任意时间调用此方法。如果正在调用此方法时在此通道上调用读取或写入操作,则在此调用完成前将首先阻塞该操作。
* 如果试图发起连接但失败了,也就是说如果调用此方法导致抛出经过检查的异常,则关闭此通道。
* 返回:
* 当且仅当已连接此通道的套接字时才返回 true
* 抛出:
* NoConnectionPendingException - 如果未连接此通道并且尚未发起连接操作
* ClosedChannelException - 如果此通道已关闭
* AsynchronousCloseException - 如果正在进行连接操作时另一个线程关闭了此通道
* ClosedByInterruptException - 如果正在进行连接操作时另一个线程中断了当前线程,因此关闭了该通道并将当前线程设置为中断状态
* IOException - 如果发生其他 I/O 错误
*/
while (!currentChannel.finishConnect()) {
System.out.println("finishConnect:false");
}
selectionKey.interestOps(SelectionKey.OP_READ | SelectionKey.OP_WRITE);
//currentChannel.register(selector, SelectionKey.OP_READ | SelectionKey.OP_WRITE);
System.out.println("key:"+Integer.toBinaryString(selectionKey.interestOps()));
}
if (selectionKey.isReadable()) {
System.out.println("tcp 可读");
SocketChannel currentChannel = (SocketChannel) selectionKey.channel();
/*
* byteBuffer.clear()方法
*
* 清除此缓冲区。将位置设置为 0,将限制设置为容量,并丢弃标记。
* 在使用一系列通道读取或放置 操作填充此缓冲区之前调用此方法。例如:
* buf.clear(); // Prepare buffer for reading
* in.read(buf); // Read data
* 此方法不能实际清除缓冲区中的数据,但从名称来看它似乎能够这样做,这样命名是因为它多数情况下确实是在清除数据时使用。
* 返回:
* 此缓冲区
*
* byteBuffer.flip() 与 clear()方法区别:flip方法,将限制设置为当前置,而clear方法是将限制设置为容量
* 反转此缓冲区。首先将限制设置为当前位置,然后将位置设置为 0。如果已定义了标记,则丢弃该标记。
* 在一系列通道读取或放置 操作之后,调用此方法为一系列通道写入或相对获取 操作做好准备。例如:
* buf.put(magic); // Prepend header 将magic 写入到buf中,现在位置为magic的长度
* in.read(buf); // Read data into rest of buffer 将in中读取的字节写入的buf中,现在buf的位置为magic长度+in中读取字节的长度
* buf.flip(); // Flip buffer 反转后,位置为0,限制位置为上步中的长度
* out.write(buf); // Write header + data to channel
* 当将数据从一个地方传输到另一个地方时,经常将此方法与 compact 方法一起使用。
* 返回:
* 此缓冲区
*
* byteBuffer.compact()
*
* 压缩此缓冲区(可选操作) 相当于释放空间原来 [4,8]的数据移动到 [0,4]
* 将缓冲区的当前位置和界限之间的字节(如果有)复制到缓冲区的开始处。
* 即将索引 p = position() 处的字节复制到索引 0 处,将索引 p + 1 处的字节复制到索引 1 处,
* 依此类推,直到将索引 limit() - 1 处的字节复制到索引 n = limit() - 1 - p 处。
* 然后将缓冲区的位置设置为 n+1,并将其界限设置为其容量。如果已定义了标记,则丢弃它。
*
* 将缓冲区的位置设置为复制的字节数,而不是零,以便调用此方法后可以紧接着调用另一个相对 put 方法。
* 从缓冲区写入数据之后调用此方法,以防写入不完整。例如,以下循环语句通过 buf 缓冲区将字节从一个信道复制到另一个信道:
*
* buf.clear(); // Prepare buffer for use
* while (in.read(buf) >= 0 || buf.position != 0) { // 假如 in读取了 helloWorld 则,buf = "helloWorld".getBytes() position="helloWorld".length()
* buf.flip(); // position = 0 ,limit = "helloWorld".length()
* out.write(buf);
* buf.compact(); // In case of partial write 将"helloWorld".length()处的 字节复制到 索引0,将limit-1处的字节复制到limit() - 1 - "helloWorld".length()
* // 相当于把 发送出去的数据覆盖掉
* }
* 返回:
* 此缓冲区
* 抛出:
* ReadOnlyBufferException - 如果此缓冲区是只读缓冲区
*
*/
byteBuffer.clear();
int read ;
try {
// 读数据到 byteBuffer中:
read = currentChannel.read(byteBuffer);
System.out.println("read : " + read);
if (read > 0) {
System.out.println(Arrays.toString(byteBuffer.array()));
}
if(read == -1){
selectionKey.cancel();
selectionKey.channel().close();
disConnect = true;
break;
}
} catch (IOException e) {
System.out.println("read Exception : " + e.getMessage());
}
}
if (selectionKey.isWritable()) {
System.out.println("tcp 可写");
if (!hasWrite) {
SocketChannel currentChannel = (SocketChannel) selectionKey.channel();
/*
* ByteBuffer.wrap(byte[] array)
* 将 byte 数组包装到缓冲区中。
* 新的缓冲区将由给定的 byte 数组支持;也就是说,缓冲区修改将导致数组修改,反之亦然。
* 新缓冲区的容量和界限将为 array.length,其位置将为零,其标记是不确定的。其底层实现数组将为给定数组,并且其数组偏移量将为零。
* 参数:
* array - 实现此缓冲区的数组
* 返回:
* 新的字节缓冲区
*/
currentChannel.write(ByteBuffer.wrap(outStr.getBytes()));
//currentChannel.register(selector,SelectionKey.OP_READ);
selectionKey.interestOps(SelectionKey.OP_READ);
System.out.println("key:"+Integer.toBinaryString(selectionKey.interestOps()));
hasWrite = true;
}
}
iterator.remove();
}
}
if(disConnect){
break;
}
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("e:" + e.getMessage());
}
System.out.println("end!");
}
/**
* 未使用nio
*/
private static void tcpClient() {
Socket socket = new Socket();
InetSocketAddress serverAddress = new InetSocketAddress("192.168.48.1", 1219);
// InetAddress serverAddress = InetAddress.
try {
String localIp = InetAddress.getLocalHost().getHostAddress();
System.out.println("localIp:" + localIp);
// 设置连接超时时间:timeout为连接超时时间
// socket.connect(serverAddress,timeout)
socket.connect(serverAddress);
// 读取超时:
// socket.setSoTimeout(200);
OutputStream outputStream = socket.getOutputStream();
// http://localhost:8080/think-in-java.pdf
String fileName = "LX_WIFI_v017-022.img";
String url = "http://" + localIp + ":8080/" + fileName;
String outStr = "SQ" + url;
PrintWriter printWriter = new PrintWriter(outputStream);
printWriter.write(outStr);
printWriter.flush();
//printWriter.close();
InputStream inputStream = socket.getInputStream();
byte[] buffer = new byte[1024];
int length;
while ((length = inputStream.read(buffer)) != -1) {
System.out.println("length:"+length);
byte[] temp = new byte[length];
System.arraycopy(buffer, 0, temp, 0, length);
System.out.println(Arrays.toString(temp));
}
} catch (IOException e) {
e.printStackTrace();
System.out.println("e : " + e.getMessage());
}
System.out.println("end!");
}
}
Nio Tcp
最新推荐文章于 2023-09-03 17:12:55 发布