JAVA IO之IO-API
网络IO编程步骤
NIO
- 创建监听,允许客户端发起请求
- 绑定网络Socket端口,绑定后有很重要的一步 看业务看是否需要设置OS是否阻塞
- 死循环接收Client请求 accept()
- 读取数据
public class ServerNIO {
public static void main(String[] args) {
ServerNIO nio = new ServerNIO();
ServerSocketChannel socketChannel = nio.inintServer();
while (true){
nio.getMsg(socketChannel);
}
}
//初始化Server Method
private ServerSocketChannel inintServer(){
ServerSocketChannel server = null;
try {
//NIO Channel通道创建监听
server = ServerSocketChannel.open();
//绑定端口9999
server.bind(new InetSocketAddress(9999));
//设置OS NONBLOCKING
server.configureBlocking(false);
System.out.println("server 启动了");
} catch (IOException e) {
e.printStackTrace();
}
return server;
}
private void getMsg(ServerSocketChannel serverSocketChannel){
try {
//服务端接收客户端请求
SocketChannel client = serverSocketChannel.accept();
if (client !=null){
//设置client为非阻塞
client.configureBlocking(false);
//优化性能增加buffer
ByteBuffer buffer = ByteBuffer.allocateDirect(2048);//缓存区可设置在堆外或者堆内,堆外性能高一点点
int read = client.read(buffer);
if (read>0){
//切换读写模式
buffer.flip();
byte[] aaa = new byte[buffer.limit()];
buffer.get(aaa);
String b = new String(aaa);
System.out.println(client.socket().getPort() + " : " + b);
buffer.clear();
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
进阶版本,多路复用 多路复用的关键是引入了selector 选择器,成批处理IO请求
package nio;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
/**
* @Classname SocketMultiplexingSingleThread
* @Description TODO
* @Date 2020/12/23 10:34 AM
* @Author by lixin
*/
public class SocketMultiplexingSingleThread {
private ServerSocketChannel server =null;
private Selector selector=null;
private void initServer(){
try {
//创建channel打开监听
server =ServerSocketChannel.open();
//绑定端口8888
server.bind(new InetSocketAddress(8888));
//设置OS NONBLOCKING
server.configureBlocking(false);
//创建多路复用器
/*
在java中把select、poll、epoll三个IO模型都抽象成selector
底层优先使用epoll模型
epoll模型会执行命令 epoll_create ->fd
*/
selector = Selector.open();
//server注册到多路复用器中并声明事件。
/*
select,poll:jvm里开辟一个数组 fd 放进去
epoll: epoll_ctl(fd3,ADD,fd4,EPOLLIN
epoll模型在 os中会多开辟两个空间 红黑树(所有 epoll_ctl命令把FD存到红黑树中) 和有状态链表结果集
*/
server.register(selector, SelectionKey.OP_ACCEPT);
} catch (IOException e) {
e.printStackTrace();
}
}
public void getMsg() throws IOException {
initServer();
System.out.println("服务器启动了");
while (true){//死循环一直循环看有没有消息进来
/*
1.selector.select() (可增加阻塞时间)阻塞方法,可以使用selector.wakeup()中断
2.select,poll 其实 内核的select(fd) poll(fd),去内核查找fd是否可以读取数据,然后在内核FDS中遍历
3.epoll 内核调用 epoll_wait() 内核中早已经有有状态的fd链表结果集 直接拿回来使用
*/
while (selector.select()>0){
//从selector中取出所有事件
/*
NIO 自己需要对每一个FD进行调用,浪费资源,多路复用,调用一次select()可以把所有有状态的FD拿回来,进行业务处理
*/
Set<SelectionKey> keys = selector.selectedKeys();
Iterator<SelectionKey> iterator = keys.iterator();
while (iterator.hasNext()){
SelectionKey key = iterator.next();
// ****处理了的事件需要remove,不remove会循环处理。****
iterator.remove();
//接收事件
if (key.isAcceptable()){
//一般来讲接收一个新的连接进来会返回一个新的FD
//select、poll,因为他们在内核没有内存空间,那么在jvm中保存和之前的哪些fd在一个fd数组中
//epoll:我们通过epoll_ctl注册到内核空间中
acceptHandler(key);
}else if (key.isReadable()){
//注意,read 经常会因为各种各样的事情造成阻塞,可能会1分钟甚至1个小时甚至1年等,那么系统就不能被其他人所使用
//so,我们这里引入了一个很重要的思想,IO threads 这个思想在redis中也有体现,redis因为是单线程也会面临这阻塞问题,所以有一个 IO threads的概念
readHandler(key);
}
//。。。。各种事件
}
}
}
}
private void readHandler(SelectionKey key) {
//从客户端中读取数据
SocketChannel client = (SocketChannel) key.channel();
// client.register(selector,SelectionKey.OP_READ,buffer);
//ByteBuffer buffer = (ByteBuffer) key.attachment();
//成对出现的buffer被注册到selector,然后用attachment()读出来
ByteBuffer buffer = (ByteBuffer) key.attachment();
buffer.clear();
int read= 0;
try {
while (true){
read = client.read(buffer);
if (read>0){
buffer.flip();
while (buffer.hasRemaining()) {
client.write(buffer);
}
buffer.clear();
}else if (read == 0) {
break;
} else {
client.close();
break;
}
}
} catch (IOException e) {
e.printStackTrace();
}
}
private void acceptHandler(SelectionKey key) {
//获取服务端通道
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
try {
//从服务端接收新的client
SocketChannel client = serverSocketChannel.accept();
client.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(8192);
//测试到selector中
client.register(selector,SelectionKey.OP_READ,buffer);
System.out.println("-------------------------------------------");
System.out.println("新客户端:" + client.getRemoteAddress());
System.out.println("-------------------------------------------");
} catch (IOException e) {
e.printStackTrace();
}
}
public static void main(String[] args) throws IOException {
SocketMultiplexingSingleThread sevice = new SocketMultiplexingSingleThread();
sevice.getMsg();
}
}
究极版本多路复用精华api,给大家讲一下思路
- 为了充分利用多核CPU的特性,我们可以尽可能的让CPU不闲着,一般线程数设置比较理想的数是cpu核数 或者2倍的cpu核数
- 我们可以利用多线程,创建多个selector,每一个线程一个selector
- 我们可以mix处理事件,也可以指定一个selector只处理接收client
- 另外的selector处理W/R 事件,这个思想和netty很接近了如果我们能够自己手写出来那么为我们之后学习netty会打下很好的基础