标签:复杂度 socket lis 非阻塞 isa pac stat flip hand
java的阻塞模型,阻塞的点有两个,就是accept,和接受用户的输入的时候
package network;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class SocketIO {
public static void main(String[] args) throws IOException {
ServerSocket server = new ServerSocket(9090);
System.out.println("服务端启动");
Socket socket = server.accept(); //阻塞
System.out.println("客户端的端口号是:"+socket.getPort());
InputStream is = socket.getInputStream();
BufferedReader br = new BufferedReader(new InputStreamReader(is));
System.out.println(br.readLine()); //阻塞
while (true){
}
}
}
然后尝试连接
但是此时服务器只能处理一个请求,如果这时候再添加一个连接,是没有效果的
客户端连接到java 的服务端是需要先和内核进行tcp三次握手创建连接的
java中的一些代码其实有的是调用的是内核的封装好的方法
new ServerSocket(9090);
相当于
socket() = 6fd
bind(6fd, 9090)
listen(6fd)
Socket client(7fd) = server.accept();
相当于 accept(6fd) ==> 7fd
对于这种情况,通常是每来一个连接,创建一个线程来专门进行IO处理accept,这是最早的BIO
但是线程的连接由于java本身的原因是不可以很多个的,也就没法收到很多的连接
这个时候对于内核进行了升级,出现了NIO
也就是内核允许在BIO出现的两个阻塞 不再进行阻塞
package network;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.LinkedList;
public class SocketNIO {
public static void main(String[] args) throws IOException, InterruptedException {
LinkedList<SocketChannel> clients = new LinkedList<>();
ServerSocketChannel ss = ServerSocketChannel.open();
ss.bind(new InetSocketAddress(9090));
ss.configureBlocking(false); //设置非阻塞
while(true){
Thread.sleep(1000);
SocketChannel client = ss.accept(); //这个时候不会阻塞
if(client == null){
System.out.println("null----");
} else {
client.configureBlocking(false);
int port = client.socket().getPort();
System.out.println("port: "+port);
clients.add(client);
}
//buffer这里是串行化公用一个buffer了,一般每个channel都有鸽子的ByteBuffer
ByteBuffer buffer = ByteBuffer.allocateDirect(4096); //为读写的区域指定大小,这块区域可以读写,可以在堆里,也可以在堆外
for(SocketChannel sc: clients){
int num = sc.read(buffer); //先把读到的东西放进buffer里面,这里也不会阻塞
if(num > 0){
buffer.flip();
byte[] bytes = new byte[buffer.limit()];
buffer.get(bytes);
String res = new String(bytes);
System.out.println(sc.socket().getPort()+": "+res);
buffer.clear();
}
}
}
}
}
这里面现在就可以只有一个主线程来操作这个,其实也可以分为两个线程来
一个主线程来不停地接受accept,通常较boss,
另一个线程来不断地遍历这些客户端,处理他们的输入输出,通常较works
NIO的问题:不断地遍历所有的客户来看他们是否有输入输出,假设现在有10000个连接,那么每次循环你都要循环这些连接,看他们有没有输入,但是当这些连接只有少数的给你发数据,这就会造成资源浪费
这个时候如果有东西可以告诉你有哪些连接给你发数据就好了,没有发数据的就不管
内核提供了这样的功能,就是多路复用器,比如select,poll,epoll.kqueue
多路复用器的作用就是可以理解为监听那些连接给你有发数据,这样程序的复杂度就会少很多,
因为这样的事情我交给了内核来做,内核来管理,java程序这边我只要一句话就行
package network;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.*;
import java.util.Iterator;
import java.util.Set;
public class Select {
private ServerSocketChannel server = null;
private Selector selector = null;
private int port = 9090;
public void initServer() throws IOException {
server = ServerSocketChannel.open();
server.configureBlocking(false);
server.bind(new InetSocketAddress(port));
//将这个server注册到selector里面,并且制定了我想得到的东西
server.register(selector, SelectionKey.OP_ACCEPT);
}
public void start() throws IOException {
initServer();
System.out.println("服务器启动了。。。");
while(true){
while(selector.select(0) > 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();
}
}
}
}
}
public void acceptHandler(SelectionKey key) throws IOException {
ServerSocketChannel channel = (ServerSocketChannel) key.channel();
SocketChannel client = channel.accept();
client.configureBlocking(false);
ByteBuffer buffer = ByteBuffer.allocate(8192);
client.register(selector, SelectionKey.OP_READ, buffer); //设置可以读
System.out.println("新客户端 "+ client.getRemoteAddress());
}
public void readHandler(SelectionKey key) throws IOException {
//通过这个key,我可以取到曾经的buffer和曾经的clinet
SocketChannel client = (SocketChannel) key.channel();
ByteBuffer buffer = (ByteBuffer) key.attachment();
buffer.clear();
int read = 0;
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;
}
}
}
public static void main(String[] args) throws IOException {
Select s = new Select();
s.start();
}
}
标签:复杂度 socket lis 非阻塞 isa pac stat flip hand
原文地址:https://www.cnblogs.com/wpbing/p/14386045.html