BIO NIO AIO
BIO(同步阻塞)
- 传统的网络编程模型中采用C/S模型,即客户端/服务端模型。这时候通常采用的是BIO(Blocking Input Output)即同步阻塞的。
- 优点:实现简单,对于低并发、低访问量的需求适合。
- 缺点:其访问模型是针对一个客户端,在服务端对应一个线程去处理。在极端情况下,很容易出现线程数量过大(访问的客户数量过多,高并发情况)使得系统资源耗尽的情况。即客户端:服务端线程=1:1
- 优化方案:采用线程池统一管理线程。但仍然不能从根本上改进在高并发情况下线程数目过大导致的系统资源耗尽问题
Server端代码并启动
package NetWorkProgramming;
import jdk.internal.util.xml.impl.Input;
import javax.naming.ldap.SortKey;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.ServerSocket;
import java.net.Socket;
public class Server {
public static int a = 1;
//单例
private ServerSocket serverSocket;
private Socket clientSocket;
//本机回路
private static final String ipAddress = "127.0.0.1";
//默认监听9999端口
private static final int port = 9999;
public void startServer() throws IOException {
serverSocket = getServerSocket(port);
System.out.println("服务器套接字成功初始化....." + "端口为" + port);
//一直监听端口信号
}
private ServerSocket getServerSocket(int port) throws IOException {
return new ServerSocket(port);
}
public static void main(String[] args) throws IOException, InterruptedException {
//启动服务端 BIO 同步阻塞模式
Server server = new Server();
server.startServer();
Thread serverThread = new Thread(new Runnable() {
@Override
public void run() {
//读取套接字传输的数据
BufferedReader in;
try {
while (true) {
System.out.println("阻塞中....");
server.clientSocket = server.serverSocket.accept();
in = new BufferedReader(new InputStreamReader(server.clientSocket.getInputStream()));
System.out.println("阻塞结束 ");
String expr;
//套接字中的数据
while (true) {
if ((expr = in.readLine()) == null) break;
System.out.println("套接字中数据是: " + expr);
}
}
} catch (IOException e) {
System.out.println("服务端套接字初始化失败...." + "端口号是:" + port);
e.printStackTrace();
} finally {
try {
server.serverSocket.close();
server.clientSocket.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
});
serverThread.start();
//保证完全初始化 启动
Thread.sleep(300);
//启动客户端线程
Thread clientThread = new Thread(new Runnable() {
@Override
public void run() {
Client client = new Client();
//注意这个地方 加上换行符才能正确结束readLine()函数
client.startClient(port, ipAddress, "这是第一次套接字测试哦!\n");
}
});
clientThread.start();
}
}
Client端代码
package NetWorkProgramming;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.net.Socket;
public class Client {
private Socket clientSocket;
private void newInstanceClientSocket(int port, String ipAddress) {
try {
clientSocket = new Socket(ipAddress, port);
} catch (IOException e) {
System.out.println("初始化客户端套接字失败...." + "Ip为" + ipAddress);
e.printStackTrace();
}
}
public void startClient(int port, String ipAddress, String info) {
newInstanceClientSocket(port, ipAddress);
try {
OutputStream output = clientSocket.getOutputStream();
//写入数据
output.write(info.getBytes());
} catch (IOException e) {
e.printStackTrace();
}
}
}
问题总结
- BufferReader的readLine()方法是阻塞函数,即如果没有读取到末尾,无换行符会一直阻塞,在使用的时候要格外小心。笔者在实现Server端代码main函数中注释有提及
- 这里的代码其实写的不严谨。正确的应该是在中间过程使用一个新的Handler类作为代理来处理,这样才能满足BIO的线程:客户端=1:1的情况
NIO(New I/O 或者称之为 Non-Block I/O)
- 简介:出现在JDK4 用于解决BIO模式在高并发情况下性能不佳、可用性差的问题。采用多路复用技术。
- 主要相关类及接口:Buffer(缓冲区,相当于BIO中流中的数据)、Channel(通道,相当于BIO的流,但是通道是全双工的,更接近操作系统底层)、Selector(多路复用器,通过令牌轮询的方式访问已经就绪的通道,达到多路复用异步的效果)
- 优点:非阻塞,提高了在高并发情况下的可用性和效率
- 缺点:实现复杂
AIO(Asynchornous I/O 或者称之为 NIO 2.0)
- 简介:JDK7 才出现的特性,利用了Linux epoll方法,实现了真正的异步非阻塞。
- 主要相关类及接口:AsynchronousServerSocketChannel(异步服务端套接字通道) CompletionHandler,Future(分别对应accept的两种返回。因为AIO是真正异步的,所以不会阻塞客户端。通常使用第一种,自己实现通道的回调函数,在调用成功/失败时额分别触发相应的completion和failed方法)
- 优点:基于Linux 2.6内核,在操作系统上实现了真·异步操作,性能好,可用性强
缺点:实现较为复杂
套接字编程 Select Poll Epoll
Select
- 简介:select是最早出现套接字I/O模型。在Linux下,系统认为所有的设备都是文件,并对每个设备都使用文件描述符(FD)进行描述。而在Linux内核中,将select和poll模式下的FD大小都硬编码为1024。Select通过遍历fd_set集合取得需要读写的操作。
- 优点:实现简单
缺点:(1)由于FD的最大数1024的限制,限制了服务端接入设备的最大限制,不适应现在的高并发情况。(2)采用了遍历FD集合的方式,效率低下。(3)对进程通信采用了内存拷贝的方式,效率低
Poll
简介:相对于select进行改进了的一种模式,对文件集合采用的pollfd而非前面的fd_set,本质上并无太大改变。
- 优点:同上
- 缺点:同上
Epoll
- 简介:目前高并发情况下最常用的I/O多路复用模型。有三个函数epoll_create、epoll_ctl、epoll_wait,前面两个select/poll模型都只有一个函数。