BIO:Blocking IO,同步阻塞IO
文件流/网络流
我们常常会这样做
ServerSocket serversocket=new ServerSocket(8888);
Socket clientsocket=serversocket.accept();
socket.IO-----》》》InputStream OutputStream
这段伪代码我也准备了一个例子,很简单,最基础的知识了
import java.net.ServerSocket;
import java.net.Socket;
import java.util.Scanner;
public class BioServer {
public static void main(String[] args) {
try (ServerSocket serversocket=new ServerSocket(8888)){
System.out.println(serversocket.getLocalSocketAddress()+"成功启动");
//不断等待客户端的连接
while(true)
{
Socket clientsocket=serversocket.accept();
System.out.println(clientsocket.getRemoteSocketAddress()+"连接成功");
try(Scanner scanner=new Scanner(clientsocket.getInputStream())){
//针对每个客户端能够进行读写操作
while(true)
{
String request=scanner.nextLine();
if("quit".equals(request))
break;
clientsocket.getOutputStream().write((request+"输出"+"\n").getBytes());
}
}
}
} catch(Exception e)
{
e.printStackTrace();
}
}
}
此时的数据交互就像这样
运行一下程序
然后我们与客户端的一个交互
打开控制台ctrl+r,输入cmd
输入telnet localhost 8888
在cmd控制台输入ctrl+]
回车进入新窗口
输入
这样就完成了一个交互
当然是不能的
我们看上述代码,显然
Socket clientsocket=serversocket.accept(); //第一个阻塞
String request=scanner.nextLine(); //第二个阻塞
想想看,第一个客户端来了,服务器一直等待客户端的输入,堵塞,导致第二个客户端无法连接
我们将第一个客户端断开,第二个才连接成功
那我怎么样能让多个客户端同时与服务器进行交互呢?
没错,用多线程
每来一个客户端,开启一个线程
import java.net.ServerSocket;
import java.net.Socket;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class BioServer {
public static void main(String[] args) {
ExecutorService exe=Executors.newFixedThreadPool(3);
try (ServerSocket serversocket=new ServerSocket(8888)){
System.out.println(serversocket.getLocalSocketAddress()+"成功启动");
while(true)
{
Socket clientsocket=serversocket.accept();
System.out.println(clientsocket.getRemoteSocketAddress()+"连接成功");
exe.submit(new ClientHandler(clientsocket));
}
} catch(Exception e)
{
e.printStackTrace();
}
}
}
每来一个客户端,将数据的交互放在一个线程里处理
exe.submit(new ClientHandler(clientsocket));
import java.net.Socket;
import java.util.Scanner;
public class ClientHandler implements Runnable {
public Socket clientsocket;
public ClientHandler(Socket clientsocket)
{
this.clientsocket=clientsocket;
}
@Override
public void run() {
try(Scanner scanner=new Scanner(clientsocket.getInputStream())){
while(true)
{
String request=scanner.nextLine();
if("quit".equals(request))
break;
clientsocket.getOutputStream().write((request+"输出"+"\n").getBytes());
}
}catch(Exception e)
{
e.printStackTrace();
}
}
}
显然,我们将线程池Executors.newFixedThreadPool(3);,所以可以同时有三个客户端同时连接交互
serversocket.accept()不停的接受客户端
String request=scanner.nextLine(); --》占用当前线程资源,不能完成其他操作
只有当所占用线程的客户端释放资源时,其他客户端才可以连入
那如果来100000个客户端,就要手动创建100000个线程
1、服务器会不会崩掉
2、方便管理,cpu会放在上下文的一个切换上,cpu不去处理逻辑代码,大量的时间去管理线程的切换了
多线程虽好,但仔细去想
小小的String request=scanner.nextLine()阻塞,占用线程资源合适吗?
你这个客户端数据一直不来,又不关掉
如果有数据来,我就处理你,没有数据来,我就不要这个线程
线程的数量就可以控制
就产生了这样的一个思路
每来一个客户端,我就用Map集合保存起来,并保存它的状态
Map
先不创建线程
map.put(clientsocket,“Accepted”);
当客户端A想要传输数据
map.set(clientsocketA,“Readable”);
再创建线程
new Thread(…);
当客户端B想要传输数据
map.set(clientsocketB,“Readable”);
new Thread(…);
这样有效的控制,服务端的线程一定会减少,因为不是每一个客户端都要数据交互
map集合存储了所有的客户端,是不是还要用什么去监听客户端的状态呢?
这样一看,还是BIO呀
jdk1.4之后
java.nio xxxx类 Non-Blocking IO 同步非阻塞 IO不是阻塞的
既然IO不是阻塞的,那是不是就可以完成多个客户端数据读写的操作呢?
如何完成上面的新思路的呢?
仅仅是使用了一个类 Selector
作用就是
IO不是阻塞的,那就不用创建新线程了,上面的思路是不是可以改一改了
改成这样,也就是说IO要改成非阻塞的
其实,java应用程序一定和操作系统打交道,IO的阻塞与否决定于和操作系统打交道的一种方式:阻塞方式和非阻塞方式(废话)
阻塞方式
主程序啥都不能做,只能等待数据传输过来
非阻塞:
不间断的给操作系统发信号,没有数据我就不要了,我去处理别的事了
数据阶段性的传输,就要引入一个buffer
数据交互的IO堵塞处理完了,那么还有个问题
客户端连接服务端的时候会不会阻塞呢?
serverSocket.accept();阻塞
我们换一种通讯方式,不用IO流通讯
用channel方式,面向通道传输–双向读写—异步读写
多路复用:
连接来了,需要数据交互,我再给你分配资源
看到这,没有实际操作,挺懵的,结合实际代码看就好多了
看这一段注释
A channel represents an open connection to an entity such as a hardware
device, a file, a network socket, or a program component that is capable of
performing one or more distinct I/O operations, for example reading or
writing.
代表一种公开的连接,很多种介质都可以,硬件设备,文件,网络socket,程序组件呀,处理更多IO交互的操作
public interface Channel extends Closeable
这个接口有一些实现类,就有代表客户端和服务端的实现类ServerSocketChannel
//服务器端监听一个端口,等待多个客户端数据连接处理
ServerSocketChannel serverChannel=ServerSocketChannel.open();
看open()方法
//通过provider()获得一个服务端的channel
public static ServerSocketChannel open() throws IOException {
return SelectorProvider.provider().openServerSocketChannel();
}
我们再看provider()是什么
public static SelectorProvider provider() {
synchronized (lock) {
if (provider != null)
return provider;
return AccessController.doPrivileged(
new PrivilegedAction<SelectorProvider>() {
public SelectorProvider run() {
if (loadProviderFromProperty())
return provider;
if (loadProviderAsService())
return provider;
provider=sun.nio.ch.DefaultSelectorProvider.create();
return provider;
}
});
}
}
provider=sun.nio.ch.DefaultSelectorProvider.create();
到底是怎么创建的呢?
public static Selector create()
{
return new WindowsSelectorProvider();//provider取决于操作系统
}
provider取决于操作系统,不同的操作系统进行不同获得,通过provider()打开channel
SelectorProvider.provider().openServerSocketChannel();
看下openServerSocketChannel()
public abstract ServerSocketChannel openServerSocketChannel()
找到实现类
public ServerSocketChannel openServerSocketChannel()throws IOException
{
return new ServerSocketChannelImpl(this); }
ServerSocketChannelImpl(this)
ServerSocketChannelImp(SelectorProvider var1)throws IOException
{
super(var1);
this.fd=Net.serverSocket(true);
this.fdval=IOUtil.fdval(fd); //fd文件描述符,找到句柄,获得一个服务端的channel
this.state=0;
}
fd文件描述符,找到句柄,获得一个服务端的channel
//服务
serverChannel.configureBlocking(false);
//服务端channel设置成非阻塞模式
serverChannel.configureBlocking(false);
//服务器端的channel监听8888端口
serverChannel.bind(new InetSocketAddress(8888));
Selector selector=Selector.open();
public static Selector open() throws IOException {
return SelectorProvider.provider().openSelector();//provider----selector
}
看到这,有些熟悉,和Channel有点像
public abstract AbstractSelector openSelector()
throws IOException;
找实现类
public AbstractSelector openSelector()throws IOExcetion
{
return new WindowsSelectorImpl(this);}
找到WindowsSelectorImpl(this)
WindowsSelectorImpl(SelectorProvider var1)throws IOException
{
super(var1);
this.wakeupSourceFd=((SelChImpl)this.wakeopPipe.source()).getFDVal();
SinkChannelImpl var2=(SinkChannelImpl)this.wakeopPipe.sink();
var2.sc.socket().setTcpNoDelay(true);
this.wakeupSinkFd=var2.getFDVal();
this.pollWrapper.addWakeupSocket(this.wakeupSinkFd,0);
}
重点看这句
this.pollWrapper.addWakeupSocket(this.wakeupSinkFd,0);
pollWrapper--------包装器 容器 池子 装什么?selector 装socket
点开pollWrapper,你会发现底层对pollWrapper实例化了
//8个大小的数组 容器
//和操作系统申请内存空间 ,8位大小装socket
private PollArraywrapper pollwrapper=new POllArraywrapper(8);
socket会有一个划分:fdval句柄 events
0-3位存句柄 4-7位 events
我们常常提到的epoll模型应用于此
//将socket注册到selector,默认状态
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
/**
* Registers this channel with the given selector, returning a selection
* key.*/
public final SelectionKey register(Selector sel, int ops)
throws ClosedChannelException
{
return register(sel, ops, null);
}
public abstract SelectionKey register(Selector sel, int ops, Object att)
throws ClosedChannelException;
public final SelectionKey register(Selector sel, int ops,
Object att)
throws ClosedChannelException
{
synchronized (regLock) {
if (!isOpen())
throw new ClosedChannelException();
if ((ops & ~validOps()) != 0)
throw new IllegalArgumentException();
if (blocking)
throw new IllegalBlockingModeException();
SelectionKey k = findKey(sel);
if (k != null) {
k.interestOps(ops);
k.attach(att);
}
//没有注册,就要注册,下面的代码就是注册的关键
if (k == null) {
// New registration
synchronized (keyLock) {
if (!isOpen())
throw new ClosedChannelException();
k = ((AbstractSelector)sel).register(this, ops, att);
addKey(k);
}
}
return k;
}
}
protected abstract SelectionKey register(AbstractSelectableChannel ch,
int ops, Object att);
找它实现类
protected final SelectionKey register(AbstractSelectableChannel var1, int var2, Object var3);
if(!(var1 instanceof SelChImpl))
{
throw new IllegalSelectorException();
}else {
SeletionKeyImpl var4=new SeletionKeyImpl((SelChImpl)var1,this);
var4.attach(var3);
Set var5=this.publicKeys;
synchronized(this.publicKeys){
this.implRegister(var4);} //-------这才是将channel注册到Register上面的代码
var4.interestOps(var2);
return var4;
}
}
再来看
this.implRegister(var4);
protected void implRegister(SelectionKeyImpl var1)
{
Object var2=this.closeLock;
synchronized(this.closeLock){
if(this.pollWrapper==null)
{
throw new CloseSelectorException();
}else{
this.growIfNeeded();
this.channelArray[this.totalChannels]=var1;
var1.setIndex(this.totalChannel);
this.fdMap.put(var1);
this.keys.add(var1);
this.pollWrapper.addEntity(this.totalChannels,var1);
++this.totalChannels;
}
}
}
this.pollWrapper.addEntity(this.totalChannels,var1);
////////////selector 池子
在当前selector中添加socketChannel
void addEntry(int var1,SelectorKeyImpl var2){
this.putDescriptor(var1,var2.channel.getFDVal());
}
void putDescriptor(int var1,int var2)
{
this.pollArray.putInt(SIZE_POLLFD*var1+0,var2);
}
final void putInt(int var1,int var2)
{
unsafe.putInt((long)var1+this.address,var2);
}
public native void putInt(long var1,int var3); //操作系统的native方法,转化为操作系统socket的句柄和状态
int select=selector.select(); //监听
select()底层将返回
return this.lockAndDoSelect(var1==0L?-1L:var1);
private int lockAndDoSelect(long var1)throws IOExcetion
{
synchronized(this)
{
if(!this.isOpen())
{throw new CloseSelectorExcetion();}
}else'{
Set var4=this publickeys;
int var10000;
synchronized(this.publickeys){
var10000=this.doSelect(var1); 看到do...就知道真正在执行select的操作了
}
}
return var10000;
}
protected int doSelect(long var1)throws IOException
{
if(this.channelArray==null){
throw new CloseSelectorException();
}else
{
this.timeout=var1;
this.processDeregisterQueue();
if(this.interruptTriggered){
this.resetWakeupSocket();
return 0;
}else
{
this.adjustThreadsCount();
this.finishLock.reset();
this.startLock.startThreads();
try
{
this.begin();
try
{
this.subSelector.poll();//selector监听的精华,不断循环监听当前selector中的socket的变化, poll()调用poll0()方法 private native int poll0(...........)操作系统完成监听
}catch(IOExceton var7)
{this.finshLock.setExcetion(var7);}
if(this.threads.size()>0){
this.finishLock.waitForHelperThreads();
}
}finally{this.end();}
this,finishLock.checkForExcetion();
this.processDeregistQueue();
int var3=this.updateSelectedKeys();
this.resetWakeupSocket();
return var3;
}
}
}
每个socket通过什么样的内容表示的呢?
/**
* Registers this channel with the given selector, returning a selectionkey*/
* 有多少selectionkey就有多少socket
Set selectionKeys=selector.selectedKeys();
Iterator iterator=selectionKeys.iterator();`
SelectionKey key=iterator.next();
通过key可判断channel的状态
下面是数据交互
//channel想要进行数据的交互了
if(key.isReadable())
{
SocketChannel channel=(SocketChannel)key.channel();
channel.read(buffer); //需要经过buffer缓冲区读数据
String request =new String(buffer.array()).trim();
buffer.clear();
channel.write(ByteBuffer.wrap(request.getBytes())); //写数据也要经过buffer缓冲区
翻看buffer的一个实现类,以FileBuffer为例,最后会发现一个固定
public final Buffer flip()
{
limit=position; //目前position所在的位置就是数据已经读好的长度,用limit限制
mark=-1;
position=0;
return this;
}
position 一直读数据,读完了,用limit等于position。position还要读取其他数据,又回到起点
最终实现代码
import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.nio.channels.SelectionKey;
import java.nio.channels.Selector;
import java.nio.channels.ServerSocketChannel;
import java.nio.channels.SocketChannel;
import java.util.Iterator;
import java.util.Set;
public class BioServer {
public static void main(String[] args)throws Exception {
ServerSocketChannel serverChannel=ServerSocketChannel.open();
serverChannel.configureBlocking(false);
serverChannel.bind(new InetSocketAddress(8888));
System.out.println("服务器启动"+serverChannel.getLocalAddress());
Selector selector=Selector.open();
serverChannel.register(selector, SelectionKey.OP_ACCEPT);
ByteBuffer buffer=ByteBuffer.allocate(1024);
while(true)
{
int select=selector.select();
if(select==0)
{
continue;
}
Set<SelectionKey> selectionKeys=selector.selectedKeys();
Iterator<SelectionKey> iterator=selectionKeys.iterator();
while(iterator.hasNext())
{
SelectionKey key=iterator.next();
if(key.isAcceptable())
{
ServerSocketChannel channel=(ServerSocketChannel)key.channel();
SocketChannel clientChannel=channel.accept();
System.out.println("来自连接"+clientChannel.getRemoteAddress());
clientChannel.configureBlocking(false);
//将当前channel的状态改变
clientChannel.register(selector, SelectionKey.OP_READ);
}
//channel想要进行数据的交互了
if(key.isReadable())
{
SocketChannel channel=(SocketChannel)key.channel();
channel.read(buffer); //需要经过buffer缓冲区读数据
String request =new String(buffer.array()).trim();
buffer.clear();
channel.write(ByteBuffer.wrap(("输出"+request).getBytes())); //写数据也要经过buffer缓冲区
}
iterator.remove();
}
}
}
}
那问题就来了
tomcat是怎么实现nio内容的
netty是怎么封装和优化的
nio的应用场景(netty的应用场景)