客户端建立链路之前需要有一个Socket实例,操作系统为Socket实例做如下操作
这个数据结构会一直保存在系统,直到链接关闭
创建Socket实例返回之前,需要进行TCP三次握手协议,完成TCP握手协议才算完成Socket创建
/**
* @author liaojiamin
* @Date:Created in 15:11 2022/7/29
*/
public class ServerSelectorDemo {
public void selector() throws IOException {
ByteBuffer byteBuffer = ByteBuffer.allocate(1024);
Selector selector = Selector.open();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.configureBlocking(false);
serverSocketChannel.socket().bind(new InetSocketAddress(8080));
//注册监听事件
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
while (true){
//获取所有key集合
Set selectedKeys = selector.selectedKeys();
Iterator iterator = selectedKeys.iterator();
while (iterator.hasNext()){
SelectionKey selectionKey = (SelectionKey) iterator.next();
if((selectionKey.readyOps() & SelectionKey.OP_ACCEPT) == SelectionKey.OP_ACCEPT){
ServerSocketChannel serverSocketChannel1 = (ServerSocketChannel) selectionKey.channel();
//接受服务端请求
SocketChannel socketChannel = serverSocketChannel1.accept();
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_READ);
iterator.remove();
}else if ((selectionKey.readyOps() & SelectionKey.OP_READ) == SelectionKey.OP_READ){
SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
while (true){
byteBuffer.clear();
//读取数据
int n = socketChannel.read(byteBuffer);
if(n <= 0){
break;
}
byteBuffer.flip();
}
iterator.remove();
}
}
}
}
}
//初始化方法
ByteBuffer(int mark, int pos, int lim, int cap, // package-private
byte[] hb, int offset)
{
super(mark, pos, lim, cap);
this.hb = hb;
this.offset = offset;
}
//super是调用Byffer中的初始化方法
Buffer(int mark, int pos, int lim, int cap) { // package-private
if (cap < 0)
throw new IllegalArgumentException("Negative capacity: " + cap);
this.capacity = cap;
limit(lim);
position(pos);
if (mark >= 0) {
if (mark > pos)
throw new IllegalArgumentException("mark > position: ("
+ mark + " > " + pos + ")");
this.mark = mark;
}
}
当写入4个byte后的示意图

当调用byteBuffer.flip()方法时候,数组的状态如下

此时底层操作系统就可以从缓冲区中正确读取这5个字节数据,并且发送出去,在下一次写入数据之前我们在调用clear()方法,缓冲区的索引又会回到初始位置。
mark的作用:当调用mark()方法时候,会记录当前position的前一个位置,我们需要调用reset时候,position恢复mark记录的值
通过allocate分配的内存,我们会通过Clannel获取I/O 数据,
在这两个区域的复制过程(操作系统缓冲区 ----- 用户缓冲区)是很耗性能的
通过allocateDirect分配的内存:直接操作操作系统缓冲区
DirectByteBuffer(int cap) { // package-private
super(-1, 0, cap, cap);
boolean pa = VM.isDirectMemoryPageAligned();
int ps = Bits.pageSize();
long size = Math.max(1L, (long)cap + (pa ? ps : 0));
Bits.reserveMemory(size, cap);
long base = 0;
try {
base = unsafe.allocateMemory(size);
} catch (OutOfMemoryError x) {
Bits.unreserveMemory(size, cap);
throw x;
}
unsafe.setMemory(base, size, (byte) 0);
if (pa && (base % ps != 0)) {
// Round up to page boundary
address = base + ps - (base & (ps - 1));
} else {
address = base;
}
cleaner = Cleaner.create(this, new Deallocator(base, size, cap));
att = null;
}
| HeapByteBuffer | DirectByteBuffer | |
|---|---|---|
| 存储位置 | java Heap中 | DirectByteBuffer |
| I/O | 需要用户地址空间和操作系统内核地址空间复制数据 | 不需要复制 |
| 内存管理 | Java GC回收,创建 并且 回收开销少 | 通过System.gc()要释放Java对象引用的DirectByteBuffer内存空间,如果Java对象长时间持有引用可能导致Native内存泄露。创建和回收内存开销大 |
| 使用场景 | 并发连接数少于1000,I/O操作较少比时候比较合适 | 数据量大,生命周期长的情况下合适 |
NIO提供了比传统文件访问更好的方法,两个优化方法:FileChannel.transferTO,FileChannel.transferFrom, 另外一个是FileChannel.map
传统的数据访问方式

FileChannel.transferXXX的访问方式

如上图中,可以看到,不管是读还是写的方式,都能减少用户地址空间到内核地址空间数据复制的这一个步骤
FileChannel.map的方式,也同样的能按照一定大小块映射为内存区域。当范问这块内存的时候就是直接操作文件了。这样就省去了数据从内核到用户空间的复制
这种方式适合对大文件的只读操作。比如文件的MD5校验等,
如下一个实现案例。
/**
* @author liaojiamin
* @Date:Created in 14:09 2022/8/1
*/
public class FileChannelMapCopyFile {
public static void main(String[] args) throws FileNotFoundException {
int BUFFER_SIZE = 1024;
String fileName = "E:\\learn\\问题汇总\\MYSQL.md";
long fileLength = new File(fileName).length();
int bufferCount = 1+ (int) (fileLength / BUFFER_SIZE);
MappedByteBuffer[] byteBuffers = new MappedByteBuffer[bufferCount];
long remaining = fileLength;
String fileName_1 = "E:\\learn\\问题汇总\\MYSQL_1.md";
FileOutputStream fileOutputStream = new FileOutputStream(fileName_1);
FileChannel writeChannel = fileOutputStream.getChannel();
for (int i = 0; i < bufferCount; i++) {
RandomAccessFile file;
try {
file = new RandomAccessFile(fileName, "r");
Integer size = (int)Math.min(remaining, BUFFER_SIZE);
byteBuffers[i] = file.getChannel().map(FileChannel.MapMode.READ_ONLY, i * BUFFER_SIZE, size);
ByteBuffer byteBuffers1 = byteBuffers[i].get(new byte[size]);
byteBuffers1.flip();
writeChannel.write(byteBuffers1);
}catch (Exception e){
e.printStackTrace();
}
remaining -= BUFFER_SIZE;
}
}
}