中断和系统调用
BIO---Blocking IO(阻塞I/O模型)
阻塞I/O模型是常见的I/O模型,在读写数据时客户端会发生阻塞。阻塞I/O模型的工作流程为:在用户线程发出I/O请求之后,内核会检查数据是否就绪,此时用户线程一直阻塞等待内存数据就绪;在内存数据就绪后,内核将数据复制到用户线程中,并返回I/O执行结果到用户线程,此时用户线程将解除阻塞状态并开始处理数据。典型的阻塞I/O模型的例子为data = socket.read(),如果内核数据没有就绪,Socket线程就会一直阻塞在read()中等待内核数据就绪。
服务端代码示例
public class Server {
public static void main(String[] args) throws IOException {
ServerSocket serverSocket = new ServerSocket();
serverSocket.bind(new InetSocketAddress("127.0.0.1", 8888));
while (true) {
Socket socket = serverSocket.accept();//阻塞方法
new Thread(() -> {
handle(socket);
}).start();
}
}
static void handle(Socket socket) {
try {
byte[] bytes = new byte[1024];
int len = socket.getInputStream().read(bytes);
System.out.println(new String(bytes, 0, len));
socket.getOutputStream().write(bytes, 0, len);
socket.getOutputStream().flush();
} catch (Exception e) {
e.printStackTrace();
}
}
}
客户端代码示例
public class Client {
public static void main(String[] args) throws Exception {
Socket socket = new Socket("127.0.0.1", 8888);
socket.getOutputStream().write("helloServer".getBytes());
socket.getOutputStream().flush();
byte[] bytes = new byte[1024];
int len = socket.getInputStream().read(bytes);
System.out.println(new String(bytes, 0, len));
socket.close();
}
}
原理:
通过strace -ff -o out java Server追踪系统调用
socket: 为通信创建端点并返回一个文件描述符。这个文件描述符3对应java中的ServerSocket
bind: 给这个返回的文件描述符绑定一个端口
listen: 监听这个端口
accept: 表示要在3这个ServerSocket上接收客户端
当一个客户端连接上来,就会有一个返回值文件描述符5 ,文件描述符5代表新连接进来的客户端
通过调用内核的系统调用clone,得到一个在操作系统中的一个轻量级的进程,clone返回线程的id
读取文件描述符5也即客户端中的数据,没有就阻塞 在主线程中accept,在自己的线程中recv
非阻塞IO(NIO)
服务端代码
public class NioServer {
public static void main(String[] args) throws Exception {
List<SocketChannel> clients = new LinkedList<>();
ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(9999));
serverSocketChannel.configureBlocking(false);
while (true) {
Thread.sleep(1000);
SocketChannel client = serverSocketChannel.accept();
if (client == null) {
//.........
} else {
client.configureBlocking(false);
int port = client.socket().getPort();
System.out.println("client...port = " + port);
clients.add(client);
}
ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
for (SocketChannel c : clients) {
int num = c.read(buffer);
if (num > 0) {
buffer.flip();
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
System.out.println(new String(bytes) + " : " + client.socket().getPort());
buffer.clear();
}
}
}
}
}
原理:
socket提供了一个参数 SOCK_NONBLOCK
可以对服务端设置非阻塞,也可以对建立连接的客户端也建立非阻塞
没有客户端连接,不会阻塞,直接返回-1
当有一个客户端连接进来,会返回一个大于0的文件描述符,这个文件描述符表示客户端
多路复用器
1.多路复用器解决的是IO状态的问题,而不解决读写数据的问题
2.用更少的系统调用,一下询问所有的IO状态,而不是每一个IO独立的询问他的状态,减少了用户态到内核态切换的过
单线程版本示例
//单线程版本
public class MultiplexSingleThread {
private ServerSocketChannel serverSocketChannel;
private Selector selector;//linux中多路复用器(select poll epoll kqueue) nginx event{}
private int port = 8888;
public void init() {
try {
serverSocketChannel = ServerSocketChannel.open();
serverSocketChannel.bind(new InetSocketAddress(port));
serverSocketChannel.configureBlocking(false);
//如果在epoll模型下,open==> epoll create ->fd3 java中意思为得到一个selector
selector = Selector.open();//select poll epoll 优先选择:epoll 但是可以通过-D修正
// serverSocketChannel 约等于 listen状态的fd4
/*
register
如果 select,poll :jvm里开辟一个数组 fd4 放进去
epoll: epoll_ctl(fd3,ADD,fd4,EPOLLIN
*/
serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);
} catch (Exception e) {
e.printStackTrace();
}
}
public void start() {
init();
while (true) {
try {
Set<SelectionKey> keys = selector.keys();
System.out.println("keys.size() = " + keys.size());
//调用多路复用器(select,poll or epoll(epoll_wait))
/**
* select,poll 其实调用内核的 select(fd4) poll(fd4)
* epoll: 其实内核的epoll_wait(),参数可以带时间,没有时间,0:阻塞,有时间设置一个超时
* selector.wakeup() 结果返回0
*/
while (selector.select(500) > 0) {
//返回有状态的fd集合
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();//set 不移除会重复循环处理
if (key.isAcceptable()) {
/**
* 接收一个新的连接并返回新连接的fd
* select,poll,因为他们没有内核空间,那么在jvm中保存这个fd
* epoll:我们希望通过epoll_ctl把新的客户端fd注册到内核空间
*/
acceptHandler(key);
} else if (key.isReadable()) {
// readHandler(key);
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
//处理接收
private void acceptHandler(SelectionKey key) {
try {
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) key.channel();
SocketChannel client = serverSocketChannel.accept();
client.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocateDirect(4096);
client.register(selector, SelectionKey.OP_READ, buffer);
System.out.println("新客户端----" + client.getRemoteAddress());
} catch (Exception e) {
e.printStackTrace();
}
}
}
多线程实现版本
public class MuiltiplexplexThreads {
private ServerSocketChannel server;
private Selector selector1;
private Selector selector2;
private Selector selector3;
public void initServer() {
try {
server = ServerSocketChannel.open();
server.configureBlocking(false);
server.bind(new InetSocketAddress(8888));
selector1 = Selector.open();
selector2 = Selector.open();
selector3 = Selector.open();
server.register(selector1, SelectionKey.OP_ACCEPT);
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
MuiltiplexplexThreads service = new MuiltiplexplexThreads();
service.initServer();
NioThread t1 = new NioThread(service.selector1, 2);
NioThread t2 = new NioThread(service.selector2);
NioThread t3 = new NioThread(service.selector3);
t1.start();
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
t2.start();
t3.start();
System.out.println("服务器启动了...");
try {
System.in.read();
} catch (Exception e) {
e.printStackTrace();
}
}
}
class NioThread extends Thread {
Selector selector;
static int selectors;
int id;
volatile static BlockingQueue<SocketChannel>[] queues;
static AtomicInteger idx = new AtomicInteger();
NioThread(Selector selector, int n) {
this.selector = selector;
this.selectors = n;
queues = new LinkedBlockingQueue[selectors];
for (int i = 0; i < n; i++) {
queues[i] = new LinkedBlockingQueue<>();
}
System.out.println("Boss 启动");
}
NioThread(Selector selector) {
this.selector = selector;
id = idx.getAndIncrement() % selectors;
System.out.println("worker: " + id + " 启动");
}
@Override
public void run() {
try {
while (true) {
while (selector.select(10) > 0) {
Set<SelectionKey> selectionKeys = selector.selectedKeys();
Iterator<SelectionKey> iterator = selectionKeys.iterator();
while (iterator.hasNext()) {
SelectionKey key = iterator.next();
iterator.remove();
if (key.isAcceptable()) {
acceptHandler(key);
} else if (key.isReadable()) {
readHandler(key);
}
}
}
if (!queues[id].isEmpty()) {
ByteBuffer buffer = ByteBuffer.allocateDirect(8192);
SocketChannel client = queues[id].poll();
client.register(selector, SelectionKey.OP_READ, buffer);
System.out.println("----------------------------");
System.out.println("新客户端: " + client.socket().getPort() + " 分配到: " + id);
System.out.println("----------------------------");
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
private void acceptHandler(SelectionKey key) {
try {
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel client = channel.accept();
client.configureBlocking(false);
int num = idx.getAndIncrement() % selectors;
queues[num].add(client);
} catch (Exception e) {
e.printStackTrace();
}
}
private void readHandler(SelectionKey key) {
}
}
select多路复用
原理
允许一个程序监控多个文件描述符,是一个同步的IO多路复用器,多个文件描述符复用了一个系统调用
select文件描述符最多1024个,有限制,上限只能建立1024个连接
epoll多路复用
原理
kafka