共计 7457 个字符,预计需要花费 19 分钟才能阅读完成。
什么是 bio
同步阻塞
式 IO,服务端创立一个 ServerSocket,而后客户端用一个 Socket 去连贯那个 ServerSocket,而后 ServerSocket 接管到一个 Socket 的连贯申请就创立一个 Socket 和一个线程去跟那个 Socket 进行通信。
public class BioServer {public static void main(String[] args) {
// 服务端开启一个端口进行监听
int port = 8080;
ServerSocket serverSocket = null; // 服务端
Socket socket; // 客户端
InputStream in = null;
OutputStream out = null;
try {serverSocket = new ServerSocket(port); // 通过构造函数创立 ServerSocket,指定监听端口,如果端口非法且闲暇,服务器就会监听胜利
// 通过有限循环监听客户端连贯,如果没有客户端接入,则会阻塞在 accept 操作
while (true) {System.out.println("Waiting for a new Socket to establish" + "," + new Date().toString());
socket = serverSocket.accept();// 阻塞 三次握手
in = socket.getInputStream();
byte[] buffer = new byte[1024];
int length = 0;
while ((length = in.read(buffer)) > 0) {// 阻塞
System.out.println("input is:" + new String(buffer, 0, length) + "," + new Date().toString());
out = socket.getOutputStream();
out.write("success".getBytes());
System.out.println("Server end" + "," + new Date().toString());
}
}
} catch (Exception e) {e.printStackTrace();
} finally {
// 必要的清理流动
if (serverSocket != null) {
try {serverSocket.close();
} catch (IOException e) {e.printStackTrace();
}
}
if (in != null) {
try {in.close();
} catch (IOException e) {e.printStackTrace();
}
}
if (out != null) {
try {out.close();
} catch (IOException e) {e.printStackTrace();
}
}
}
}
}
什么是 nio
同步非阻塞
包含 Selector,这是多路复用器,selector 会一直轮询注册的 channel,如果某个 channel 上产生了读写事件,selector 就会将这些 channel 获取进去,咱们通过 SelectionKey 获取有读写事件的 channel,就能够进行 IO 操作。一个 Selector 就通过一个线程,就能够轮询成千上万的 channel,这就意味着你的服务端能够接入成千上万的客户端。
public class NioDemo implements Runnable {
public int id = 100001;
public int bufferSize = 2048;
@Override
public void run() {init();
}
public void init() {
try {
// 创立通道和选择器
ServerSocketChannel socketChannel = ServerSocketChannel.open();
Selector selector = Selector.open();
InetSocketAddress inetSocketAddress = new InetSocketAddress(InetAddress.getLocalHost(), 4700);
socketChannel.socket().bind(inetSocketAddress);
// 设置通道非阻塞 绑定选择器
socketChannel.configureBlocking(false);
socketChannel.register(selector, SelectionKey.OP_ACCEPT).attach(id++);
System.out.println("Server started .... port:4700");
listener(selector);
} catch (Exception e) {}}
public void listener(Selector in_selector) {
try {while (true) {Thread.sleep(1 * 1000);
in_selector.select(); // 阻塞 直到有就绪事件为止
Set<SelectionKey> readySelectionKey = in_selector
.selectedKeys();
Iterator<SelectionKey> it = readySelectionKey.iterator();
while (it.hasNext()) {SelectionKey selectionKey = it.next();
// 判断是哪个事件
if (selectionKey.isAcceptable()) {// 客户申请连贯
System.out.println(selectionKey.attachment()
+ "- 承受申请事件");
// 获取通道 承受连贯,
// 设置非阻塞模式(必须),同时须要注册 读写数据的事件,这样有音讯触发时能力捕捉
ServerSocketChannel serverSocketChannel = (ServerSocketChannel) selectionKey
.channel();
serverSocketChannel
.accept()
.configureBlocking(false)
.register(
in_selector,
SelectionKey.OP_READ
| SelectionKey.OP_WRITE).attach(id++);
System.out
.println(selectionKey.attachment() + "- 已连贯");
// 上面这种写法是有问题的 不应该在 serverSocketChannel 下面注册
/*
* serverSocketChannel.configureBlocking(false);
* serverSocketChannel.register(in_selector,
* SelectionKey.OP_READ);
* serverSocketChannel.register(in_selector,
* SelectionKey.OP_WRITE);
*/
}
if (selectionKey.isReadable()) {// 读数据
System.out.println(selectionKey.attachment()
+ "- 读数据事件");
SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
ByteBuffer receiveBuf = ByteBuffer.allocate(bufferSize);
clientChannel.read(receiveBuf);
System.out.println(selectionKey.attachment()
+ "- 读取数据:" + getString(receiveBuf));
}
if (selectionKey.isWritable()) {// 写数据
System.out.println(selectionKey.attachment()
+ "- 写数据事件");
SocketChannel clientChannel = (SocketChannel) selectionKey.channel();
ByteBuffer sendBuf = ByteBuffer.allocate(bufferSize);
String sendText = "hello\n";
sendBuf.put(sendText.getBytes());
sendBuf.flip(); // 写完数据后调用此办法
clientChannel.write(sendBuf);
}
if (selectionKey.isConnectable()) {System.out.println(selectionKey.attachment()
+ "- 连贯事件");
}
// 必须 removed 否则会持续存在,下一次循环还会进来,
// 留神 removed 的地位,针对一个.next() remove 一次
it.remove();}
}
} catch (Exception e) {System.out.println("Error -" + e.getMessage());
e.printStackTrace();}
}
/**
* ByteBuffer 转换 String
*
* @param buffer
* @return
*/
public static String getString(ByteBuffer buffer) {
String string = "";
try {for (int i = 0; i < buffer.position(); i++) {string += (char) buffer.get(i);
}
return string;
} catch (Exception ex) {ex.printStackTrace();
return "";
}
}
}
什么是 aio
异步非阻塞
每个连贯发送过去的申请,都会绑定一个 buffer,而后告诉操作系统去异步实现读,此时你的程序是会去干别的事儿的,等操作系统实现数据读取之后,就会回调你的接口,给你操作系统异步读完的数据。
public class AIOServer {
public final static int PORT = 9888;
private AsynchronousServerSocketChannel server;
public AIOServer() throws IOException {server = AsynchronousServerSocketChannel.open().bind(new InetSocketAddress(PORT));
}
public void startWithFuture() throws InterruptedException,
ExecutionException, TimeoutException {while (true) {// 循环接管客户端申请
Future<AsynchronousSocketChannel> future = server.accept();
AsynchronousSocketChannel socket = future.get();// get() 是为了确保 accept 到一个连贯
handleWithFuture(socket);
}
}
public void handleWithFuture(AsynchronousSocketChannel channel) throws InterruptedException, ExecutionException, TimeoutException {ByteBuffer readBuf = ByteBuffer.allocate(2);
readBuf.clear();
while (true) {// 一次可能读不完
//get 是为了确保 read 实现,超时工夫能够无效防止 DOS 攻打,如果客户端始终不发送数据,则进行超时解决
Integer integer = channel.read(readBuf).get(10, TimeUnit.SECONDS);
System.out.println("read:" + integer);
if (integer == -1) {break;}
readBuf.flip();
System.out.println("received:" + Charset.forName("UTF-8").decode(readBuf));
readBuf.clear();}
}
public void startWithCompletionHandler() throws InterruptedException,
ExecutionException, TimeoutException {
server.accept(null,
new CompletionHandler<AsynchronousSocketChannel, Object>() {public void completed(AsynchronousSocketChannel result, Object attachment) {server.accept(null, this);// 再此接管客户端连贯
handleWithCompletionHandler(result);
}
@Override
public void failed(Throwable exc, Object attachment) {exc.printStackTrace();
}
});
}
public void handleWithCompletionHandler(final AsynchronousSocketChannel channel) {
try {final ByteBuffer buffer = ByteBuffer.allocate(4);
final long timeout = 10L;
channel.read(buffer, timeout, TimeUnit.SECONDS, null, new CompletionHandler<Integer, Object>() {
@Override
public void completed(Integer result, Object attachment) {System.out.println("read:" + result);
if (result == -1) {
try {channel.close();
} catch (IOException e) {e.printStackTrace();
}
return;
}
buffer.flip();
System.out.println("received message:" + Charset.forName("UTF-8").decode(buffer));
buffer.clear();
channel.read(buffer, timeout, TimeUnit.SECONDS, null, this);
}
@Override
public void failed(Throwable exc, Object attachment) {exc.printStackTrace();
}
});
} catch (Exception e) {e.printStackTrace();
}
}
public static void main(String args[]) throws Exception {// new AIOServer().startWithFuture();
new AIOServer().startWithCompletionHandler();
Thread.sleep(100000);
}
}
什么是 epoll
一种多路复用的技术,能够解决之前 poll 和 select 大量并发连贯状况下 cpu 利用率过高,以及须要遍历整个被侦听的描述符集的问题。epoll 只有遍历那些被内核 IO 事件异步唤醒而退出 Ready 队列的描述符汇合就行了。
什么是 mmap 技术
把一个磁盘文件映射到内存里来,而后把映射到内存里来的数据通过 socket 发送进来
有一种 mmap 技术,也就是内存映射,间接将磁盘文件数据映射到内核缓冲区,这个映射的过程是基于 DMA 引擎拷贝的,同时用户缓 冲区是跟内核缓冲区共享一块映射数据的,建设共享映射之后,就不须要从内核缓冲区拷贝到用户缓冲区了
光是这一点,就能够防止一次拷贝了,然而这个过程中还是会用户态切换到内核态去进行映射拷贝,接着再次从内核态切换到用户态,建设用户缓冲区和内核缓冲区的映射
接着把数据通过 Socket 发送进来,还是要再次切换到内核态
接着间接把内核缓冲区里的数据拷贝到 Socket 缓冲区里去,而后再拷贝到网络协议引擎里,发送进来就能够了,最初切换回用户态
缩小一次拷贝,然而并不缩小切换次数,一共是 4 次切换,3 次拷贝
什么是零拷贝技术
linux 提供了 sendfile,也就是零拷贝技术
这个零拷贝技术,就是先从用户态切换到内核态,在内核态的状态下,把磁盘上的数据拷贝到内核缓冲区,同时从内核缓冲区拷贝一些 offset 和 length 到 Socket 缓冲区;接着从内核态切换到用户态,从内核缓冲区间接把数据拷贝到网络协议引擎里去
同时从 Socket 缓冲区里拷贝一些 offset 和 length 到网络协议引擎里去,然而这个 offset 和 length 的量很少,简直能够疏忽
只有 2 次切换,2 次拷贝,就能够了
说一下 select,poll,epoll 的区别?
select
,poll
实现须要本人一直轮询所有 fd 汇合,直到设施就绪,期间可能要睡眠和唤醒屡次交替。epoll
也须要调用 epoll_wait 一直轮询就绪链表,期间也可能屡次睡眠和唤醒交替,然而它是设施就绪时,调用回调函数,把就绪 fd 放入就绪链表中,并唤醒在 epoll_wait 中进入睡眠的过程。尽管都要睡眠和交替,然而 select 和 poll 在“醒着”的时候要遍历整个 fd 汇合,而 epoll 在“醒着”的时候只有判断一下就绪链表是否为空就行了,这节俭了大量的 CPU 工夫。这就是回调机制带来的性能晋升。
select
,poll
每次调用都要把 fd 汇合从用户态往内核态拷贝一次,并且要把 current 往设施期待队列中挂一次,而 epoll
只有一次拷贝,而且把 current 往期待队列上挂也只挂一次(在 epoll_wait 的开始,留神这里的期待队列并不是设施期待队列,只是一个 epoll 外部定义的期待队列)。这也能节俭不少的开销。